mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4bc87bfe1 | ||
|
|
d8dd12426c |
No files matched your search
@@ -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.
|
||||
@@ -1,93 +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@v6
|
||||
|
||||
- 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@v1
|
||||
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@v6
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
|
||||
cmake --install . --config Release --prefix ../mold-install
|
||||
cd ..
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
$version = $Env:TAG -replace '^v', ''
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: win-x86_64
|
||||
path: dist/mold-*.*
|
||||
@@ -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@v6
|
||||
|
||||
- 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@v1
|
||||
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
|
||||
+78
-125
@@ -1,172 +1,125 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
MOLD_SUPPRESS_MACHO_WARNING: 1
|
||||
jobs:
|
||||
build-asan-tsan:
|
||||
build-clang:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
# Disable PCH for the default configuration. This prevents relying on implicit includes.
|
||||
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
- '-DMOLD_USE_TSAN=On'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- uses: rui314/setup-mold@staging
|
||||
- run: sudo ./install-build-deps.sh
|
||||
- name: build
|
||||
- name: install-build-deps
|
||||
run: sudo ./install-build-deps.sh update
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
ctest . -j$(nproc)
|
||||
|
||||
build-msan:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: redhat-actions/podman-login@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: .github/workflows/run-msan.sh
|
||||
|
||||
build-multi-archs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
sudo ./install-build-deps.sh
|
||||
sudo ./install-cross-tools.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-multi-archs
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf gcc-10-powerpc64-linux-gnu gcc-10-powerpc64le-linux-gnu gcc-10-s390x-linux-gnu gcc-10-sparc64-linux-gnu gcc-10-m68k-linux-gnu g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf g++-10-powerpc64-linux-gnu g++-10-powerpc64le-linux-gnu g++-10-s390x-linux-gnu g++-10-sparc64-linux-gnu g++-10-m68k-linux-gnu
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
ln -sf /usr/bin/powerpc64-linux-gnu-gcc-10 /usr/bin/powerpc64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/powerpc64-linux-gnu-g++-10 /usr/bin/powerpc64-linux-gnu-g++
|
||||
ln -sf /usr/bin/powerpc64le-linux-gnu-gcc-10 /usr/bin/powerpc64le-linux-gnu-gcc
|
||||
ln -sf /usr/bin/powerpc64le-linux-gnu-g++-10 /usr/bin/powerpc64le-linux-gnu-g++
|
||||
ln -sf /usr/bin/s390x-linux-gnu-gcc-10 /usr/bin/s390x-linux-gnu-gcc
|
||||
ln -sf /usr/bin/s390x-linux-gnu-g++-10 /usr/bin/s390x-linux-gnu-g++
|
||||
ln -sf /usr/bin/sparc64-linux-gnu-gcc-10 /usr/bin/sparc64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/sparc64-linux-gnu-g++-10 /usr/bin/sparc64-linux-gnu-g++
|
||||
ln -sf /usr/bin/m68k-linux-gnu-gcc-10 /usr/bin/m68k-linux-gnu-gcc
|
||||
ln -sf /usr/bin/m68k-linux-gnu-g++-10 /usr/bin/m68k-linux-gnu-g++
|
||||
|
||||
build-distros:
|
||||
strategy:
|
||||
matrix:
|
||||
distro:
|
||||
- alpine
|
||||
- archlinux
|
||||
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
|
||||
- fedora
|
||||
- gentoo/stage3
|
||||
- opensuse/tumbleweed
|
||||
- ubuntu:22.04
|
||||
- ubuntu:25.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
container: ${{ matrix.distro }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: ./install-build-deps.sh
|
||||
- name: build
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
mkdir /usr/local/rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.08.26/riscv32-glibc-ubuntu-20.04-nightly-2022.08.26-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
|
||||
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-objdump /usr/bin/riscv32-linux-gnu-objdump
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/usr/local/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 /usr/bin/riscv32-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: sanitize distro name
|
||||
if: failure()
|
||||
id: sanitize
|
||||
run: echo "name=$(echo '${{ matrix.distro }}' | tr '/:' '--')" >> "$GITHUB_OUTPUT"
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-${{ steps.sanitize.outputs.name }}
|
||||
name: test-results
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
runs-on: macos-11
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
# Disable PCH for the default configuration. This prevents relying on implicit includes.
|
||||
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: build
|
||||
- uses: actions/checkout@v3
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake ${{ matrix.target }} ..
|
||||
cmake --build . -j$(sysctl -n hw.physicalcpu)
|
||||
ctest . -j$(sysctl -n hw.physicalcpu)
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
|
||||
|
||||
build-msys:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
|
||||
- name: build
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
|
||||
cmake --build . -j $(nproc)
|
||||
|
||||
build-freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build and test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
run: |
|
||||
./install-build-deps.sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest --output-on-failure -j$(nproc)
|
||||
@@ -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
|
||||
@@ -1,104 +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@v6
|
||||
|
||||
- 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@v1
|
||||
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@v6
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
|
||||
cmake --install . --config Release --prefix ../mold-install
|
||||
cd ..
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
$version = $Env:TAG -replace '^v', ''
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-x86_64-windows.zip
|
||||
|
||||
- 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@v6
|
||||
|
||||
- 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'
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
cd "$(dirname $0)"/../..
|
||||
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-msan
|
||||
image_build="podman build -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created container image in GitHub's container repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-msan
|
||||
image_build="podman build -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
|
||||
fi
|
||||
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/ubuntu:24.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc g++ wget ca-certificates make cmake ninja-build python3 clang-20 llvm-20-dev libclang-20-dev libclang-rt-20-dev libtbb-dev mold && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
RUN mkdir /llvm && \
|
||||
cd /llvm && \
|
||||
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-21.1.1.tar.gz | tar xzf - --strip-components=1 && \
|
||||
cmake -S ./runtimes -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi;libunwind' -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DLLVM_USE_SANITIZER=MemoryWithOrigins && \
|
||||
cmake --build build -j\$(nproc) && \
|
||||
cmake --install build --prefix /msan && \
|
||||
rm -rf /llvm
|
||||
EOF
|
||||
|
||||
# Build mold and run its tests
|
||||
podman run -it --rm --pull=never -v "$(pwd):/mold:ro" --security-opt seccomp=unconfined $image bash -c "
|
||||
set -e
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DMOLD_USE_MSAN=ON -DMOLD_STDLIB_PREFIX=/msan -DMOLD_USE_MIMALLOC=0 -DMOLD_USE_SYSTEM_TBB=1 -DMOLD_USE_MOLD=1 /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
setarch -R ctest --output-on-failure -j\$(nproc)
|
||||
"
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Update manpage
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/mold.md'
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-manpage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- 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)
|
||||
+8
-3
@@ -1,11 +1,16 @@
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
/mold
|
||||
/dist
|
||||
+152
-238
@@ -1,31 +1,31 @@
|
||||
# You can customize a build by specifying CMake options. An option may be
|
||||
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
|
||||
# means true, while `OFF` or `0` means false.
|
||||
# means true while `OFF` or `0` means false.
|
||||
#
|
||||
# Here are a couple of common cmake options:
|
||||
#
|
||||
# -DCMAKE_C_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C compiler name to use. The default value is `cc`.
|
||||
# Specifies a C compiler name to use. The default value is `cc`.
|
||||
#
|
||||
# -DCMAKE_CXX_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C++ compiler name to use. The default value is `c++`.
|
||||
# Specifies a C++ compiler name to use. The default value is `c++`.
|
||||
#
|
||||
# -DCMAKE_INSTALL_PREFIX=<directory>
|
||||
#
|
||||
# Specifies the install target directory. The default value is `/usr/local`.
|
||||
# Specifies an install target directory. The default value is `/usr/local`.
|
||||
#
|
||||
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
|
||||
#
|
||||
# Specifies the build type. The default is `Release`, which is the right
|
||||
# Specifies a build type. The default is `Release` which is the right
|
||||
# option unless you are debugging mold.
|
||||
#
|
||||
# An example of a cmake command line is shown below:
|
||||
#
|
||||
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
|
||||
#
|
||||
# where `..` refers to this directory.
|
||||
# where `..` refers this directory.
|
||||
#
|
||||
# With cmake, you may run `cmake --install .` instead of `make install` to
|
||||
# install build artifacts to system directories. If you want to install
|
||||
@@ -39,62 +39,33 @@
|
||||
# how to build mold. However, as a policy, we do not provide a way to
|
||||
# enable/disable any individual mold's feature. In other words, we do not
|
||||
# provide options like `--enable-foo` or `--disable-foo`. The motivation
|
||||
# behind this is build reproducibility. We want to guarantee that all builds
|
||||
# of the mold linker of the same version will have the exact same set of
|
||||
# behind it is build reproducibility. We want to guarantees that all builds
|
||||
# of the mold linker of the same version will have the exactly same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.41.0)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(mold VERSION 1.7.1)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
|
||||
if(MINGW)
|
||||
target_link_libraries(mold PRIVATE dl)
|
||||
else()
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold)
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Deb")
|
||||
target_link_options(mold PRIVATE -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
|
||||
endif()
|
||||
add_link_options(-fuse-ld=mold)
|
||||
endif()
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
|
||||
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
target_compile_options(mold PRIVATE
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
-Wno-unused-function)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
@@ -117,76 +88,44 @@ if(MOLD_USE_TSAN)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Enable MemorySanitizer if -DMOLD_USE_MSAN=ON
|
||||
option(MOLD_USE_MSAN "Build mold with MemorySanitizer" OFF)
|
||||
if(MOLD_USE_MSAN)
|
||||
set(MOLD_STDLIB_PREFIX /usr CACHE FILEPATH "prefix of instrumented stdlib")
|
||||
target_compile_options(mold PRIVATE
|
||||
-g -Og -fsanitize=memory -fsanitize-memory-track-origins
|
||||
-nostdinc++ -isystem ${MOLD_STDLIB_PREFIX}/include/c++/v1
|
||||
-DENABLE_MSAN_UNPOISON
|
||||
)
|
||||
target_link_options(mold PRIVATE
|
||||
-fsanitize=memory
|
||||
-nostdlib++ -L ${MOLD_STDLIB_PREFIX}/lib -lc++
|
||||
-Wl,-rpath,${MOLD_STDLIB_PREFIX}/lib
|
||||
)
|
||||
endif()
|
||||
|
||||
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
|
||||
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
|
||||
#
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
# need nor want to set this to ON.
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
target_link_libraries(mold PRIVATE libcrypto.a)
|
||||
endif()
|
||||
|
||||
# Find zlib. If libz.so is not found, we compile a bundled one and
|
||||
# statically-link it to mold.
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
if(ZLIB_FOUND)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
set(ZLIB_BUILD_EXAMPLES OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
target_include_directories(zlibstatic INTERFACE third-party/zlib
|
||||
$<TARGET_PROPERTY:zlibstatic,BINARY_DIR>)
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
|
||||
# is not found, we compile a bundled one and statically-link it to mold.
|
||||
find_package(BLAKE3 QUIET)
|
||||
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE BLAKE3::blake3)
|
||||
else()
|
||||
function(mold_add_blake3)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
endfunction()
|
||||
|
||||
mold_add_blake3()
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. If zstd.h is not found, we compile a
|
||||
# bundled one and statically-link it to mold.
|
||||
# Find zstd compression library. Just like zlib, if libzstd.so is not
|
||||
# found, we compile a bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
if(HAVE_ZSTD_H)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_CONTRIB OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(libzstd_static PRIVATE
|
||||
ZSTD_BUILD_STATIC=1
|
||||
ZSTD_BUILD_SHARED=0
|
||||
ZSTD_BUILD_PROGRAMS=0
|
||||
ZSTD_MULTITHREAD_SUPPORT=0
|
||||
ZSTD_BUILD_TESTS=0)
|
||||
target_include_directories(mold PUBLIC third-party/zstd/lib)
|
||||
target_link_libraries(mold PRIVATE libzstd_static)
|
||||
endif()
|
||||
@@ -198,10 +137,12 @@ endif()
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles(
|
||||
"static_assert(sizeof(void *) == 4); int main() {}" P32)
|
||||
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
@@ -213,11 +154,11 @@ if(MOLD_USE_MIMALLOC)
|
||||
if(MOLD_USE_SYSTEM_MIMALLOC)
|
||||
find_package(mimalloc REQUIRED)
|
||||
target_link_libraries(mold PRIVATE mimalloc)
|
||||
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
|
||||
set(MI_NO_OPT_ARCH ON CACHE INTERNAL "")
|
||||
set(MI_BUILD_STATIC ON)
|
||||
option(MI_BUILD_TESTS "Build test executables" OFF)
|
||||
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
|
||||
target_link_libraries(mold PRIVATE mimalloc-static)
|
||||
@@ -234,7 +175,7 @@ endif()
|
||||
# to mold. If you want to link to the system's libtbb2.so, pass
|
||||
# -DMOLD_USE_SYSTEM_TBB=ON.
|
||||
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
|
||||
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
|
||||
if(MOLD_USE_SYSTEM_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
else()
|
||||
@@ -252,16 +193,12 @@ endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR
|
||||
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
if(MINGW)
|
||||
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
|
||||
target_link_libraries(mold PRIVATE bcrypt)
|
||||
endif()
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
@@ -278,46 +215,44 @@ if(NOT APPLE AND NOT WIN32)
|
||||
# Remove the default `lib` prefix
|
||||
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
|
||||
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
|
||||
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
|
||||
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
|
||||
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
|
||||
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles("#include <atomic>
|
||||
check_cxx_source_compiles("#include <cstdint>
|
||||
int main() {
|
||||
std::atomic_uint8_t a;
|
||||
std::atomic_uint16_t b;
|
||||
std::atomic_uint32_t c;
|
||||
std::atomic_uint64_t d;
|
||||
return ++a + ++b + ++c + ++d;
|
||||
}" HAVE_FULL_ATOMIC_SUPPORT)
|
||||
uint64_t x = 1;
|
||||
__atomic_add_fetch(&x, 0, __ATOMIC_RELAXED);
|
||||
return x;
|
||||
}" HAVE_ATOMIC_ADD_FETCH)
|
||||
|
||||
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
if(NOT HAVE_ATOMIC_ADD_FETCH)
|
||||
target_link_libraries(mold PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT MSVC)
|
||||
target_compile_options(mold PRIVATE -pthread)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
|
||||
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
|
||||
|
||||
# Create a .cc file containing the current git hash for `mold --version`.
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
|
||||
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
|
||||
DEPENDS lib/update-git-hash.cmake
|
||||
BYPRODUCTS mold-git-hash.h
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
|
||||
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
|
||||
DEPENDS update-git-hash.cmake
|
||||
BYPRODUCTS git-hash.cc
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
# Create config.h file
|
||||
configure_file(config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
# (e.g. X86_64) as its type parameter. Since we suport more than 10
|
||||
# targets, compiling a single source file for all the targets is very
|
||||
@@ -326,168 +261,147 @@ add_dependencies(mold git_hash)
|
||||
# As a workaround, we create a .cc file for each target and spawn many
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
#
|
||||
# You can build mold for a specific set of targets by passing, e.g.,
|
||||
# -DMOLD_TARGETS='X86_64;ARM64LE', though this is strongly discouraged
|
||||
# for build reproducibility. Use this option only if you build mold
|
||||
# frequently for your personal use; otherwise, always build mold with all
|
||||
# targets enabled. We provide this flag in the trust that you will not
|
||||
# abuse it, so please don’t betray that trust.
|
||||
set(MOLD_TARGETS
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
|
||||
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
|
||||
LOONGARCH32 LOONGARCH64
|
||||
CACHE STRING "List of supported targets")
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE
|
||||
PPC64V1 PPC64V2 S390X SPARC64 M68K)
|
||||
|
||||
list(GET MOLD_TARGETS 0 MOLD_FIRST_TARGET)
|
||||
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/arch-arm32.cc
|
||||
src/arch-arm64.cc
|
||||
src/arch-i386.cc
|
||||
src/arch-loongarch.cc
|
||||
src/arch-m68k.cc
|
||||
src/arch-ppc32.cc
|
||||
src/arch-ppc64v1.cc
|
||||
src/arch-ppc64v2.cc
|
||||
src/arch-riscv.cc
|
||||
src/arch-s390x.cc
|
||||
src/arch-sh4.cc
|
||||
src/arch-sparc64.cc
|
||||
src/arch-x86-64.cc
|
||||
src/archive-file.cc
|
||||
src/cmdline.cc
|
||||
src/error.cc
|
||||
src/filetype.cc
|
||||
src/gc-sections.cc
|
||||
src/gdb-index.cc
|
||||
src/icf.cc
|
||||
src/input-files.cc
|
||||
src/input-sections.cc
|
||||
src/linker-script.cc
|
||||
src/main.cc
|
||||
src/mapfile.cc
|
||||
src/output-chunks.cc
|
||||
src/passes.cc
|
||||
src/relocatable.cc
|
||||
src/shrink-sections.cc
|
||||
src/thunks.cc
|
||||
src/tls.cc
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/cmdline.cc
|
||||
elf/dwarf.cc
|
||||
elf/gc-sections.cc
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
elf/linker-script.cc
|
||||
elf/lto.cc
|
||||
elf/main.cc
|
||||
elf/mapfile.cc
|
||||
elf/output-chunks.cc
|
||||
elf/passes.cc
|
||||
elf/relocatable.cc
|
||||
elf/subprocess.cc
|
||||
elf/thunks.cc
|
||||
)
|
||||
|
||||
if(WIN32 AND NOT MINGW)
|
||||
list(APPEND MOLD_TEMPLATE_FILES src/lto-win32.cc)
|
||||
else()
|
||||
list(APPEND MOLD_TEMPLATE_FILES src/lto-unix.cc)
|
||||
endif()
|
||||
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/output-file-win32.cc
|
||||
src/subprocess-win32.cc
|
||||
)
|
||||
else()
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/output-file-unix.cc
|
||||
src/subprocess-unix.cc
|
||||
list(APPEND MOLD_MACHO_TEMPLATE_FILES
|
||||
macho/cmdline.cc
|
||||
macho/dead-strip.cc
|
||||
macho/input-files.cc
|
||||
macho/input-sections.cc
|
||||
macho/lto.cc
|
||||
macho/main.cc
|
||||
macho/mapfile.cc
|
||||
macho/output-chunks.cc
|
||||
macho/tapi.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
if(NOT EXISTS ${PATH})
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
#define MOLD_TARGET ${TARGET}
|
||||
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
|
||||
")
|
||||
endif()
|
||||
target_sources(mold PRIVATE ${PATH})
|
||||
endfunction()
|
||||
|
||||
foreach (SOURCE IN LISTS MOLD_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_TARGETS)
|
||||
foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
foreach (SOURCE IN LISTS MOLD_MACHO_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_MACHO_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
set(HAVE_TARGET_${TARGET} 1)
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
lib/aho-corasick.cc
|
||||
lib/compress.cc
|
||||
lib/crc32.cc
|
||||
lib/demangle.cc
|
||||
lib/filepath.cc
|
||||
lib/glob.cc
|
||||
lib/hyperloglog.cc
|
||||
lib/perf.cc
|
||||
lib/random.cc
|
||||
lib/tar.cc
|
||||
src/elf.cc
|
||||
src/entry.cc
|
||||
compress.cc
|
||||
demangle.cc
|
||||
elf/arch-arm32.cc
|
||||
elf/arch-arm64.cc
|
||||
elf/arch-i386.cc
|
||||
elf/arch-m68k.cc
|
||||
elf/arch-ppc64v1.cc
|
||||
elf/arch-ppc64v2.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/arch-s390x.cc
|
||||
elf/arch-sparc64.cc
|
||||
elf/arch-x86-64.cc
|
||||
filepath.cc
|
||||
git-hash.cc
|
||||
glob.cc
|
||||
hyperloglog.cc
|
||||
macho/arch-arm64.cc
|
||||
macho/arch-x86-64.cc
|
||||
macho/yaml.cc
|
||||
main.cc
|
||||
multi-glob.cc
|
||||
perf.cc
|
||||
tar.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
uuid.cc
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(mold PRIVATE
|
||||
src/jobs-win32.cc
|
||||
src/mapped-file-win32.cc
|
||||
src/signal-win32.cc
|
||||
)
|
||||
else()
|
||||
target_sources(mold PRIVATE
|
||||
src/jobs-unix.cc
|
||||
src/mapped-file-unix.cc
|
||||
src/signal-unix.cc
|
||||
)
|
||||
# Add frequently included header files for pre-compiling.
|
||||
# target_precompile_headers is supported by CMake 3.16.0 or newer.
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
|
||||
target_precompile_headers(mold PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/macho/mold.h>")
|
||||
|
||||
# ccache needs this flag along with `sloppiness = pch_defines,time_macros`
|
||||
# to enable caching
|
||||
target_compile_options(mold PRIVATE -fpch-preprocess)
|
||||
endif()
|
||||
|
||||
# Create config.h file
|
||||
configure_file(lib/config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Test configs
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Create the ld symlinks required for testing
|
||||
# Create the ld and ld64 symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
|
||||
BYPRODUCTS ld
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
|
||||
BYPRODUCTS ld ld64
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
if(${UNIX})
|
||||
add_subdirectory(test)
|
||||
if(${APPLE})
|
||||
add_subdirectory(test/macho)
|
||||
elseif(${UNIX})
|
||||
add_subdirectory(test/elf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX} ABSOLUTE)
|
||||
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
|
||||
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
|
||||
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
|
||||
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
|
||||
message(STATUS \"Installing symlink: \$ENV{DESTDIR}/\${NEW_ABS} -> \${OLD_REL}\")
|
||||
file(MAKE_DIRECTORY \$ENV{DESTDIR}/\${NEW_DIR})
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}/\${NEW_ABS} SYMBOLIC)")
|
||||
endfunction()
|
||||
|
||||
if(NOT WIN32)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
endif()
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld64.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
endif()
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "clang_cl_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# Contributing to mold
|
||||
|
||||
We request all patches to mold to be released under the dual license
|
||||
of the GNU AGPLv3 and the MIT license. Please read the following and
|
||||
sign-off your patch as an indication of agreement.
|
||||
|
||||
> ## 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 of the GNU
|
||||
> AGPLv3 and the MIT 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.
|
||||
|
||||
If you agree, 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.
|
||||
|
||||
We assume that any contribution that's 10 lines or less does not meet
|
||||
the threshold of originality and therefore copyright does not apply.
|
||||
You don't need to add a "Signed-off-by" line to such small patches.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ docker buildx create --use
|
||||
# $ docker buildx build --platform x86_64,aarch64,arm,ppc64le,s390x -t rui314/mold-builder:latest --push .
|
||||
|
||||
FROM ubuntu:18.04
|
||||
ENV TZ=Europe/London
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
\
|
||||
mkdir /openssl && cd /openssl && \
|
||||
wget -O- -q https://www.openssl.org/source/openssl-3.0.7.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./Configure --prefix=/usr/local && \
|
||||
make -j$(nproc) && \
|
||||
make -j$(nproc) install && \
|
||||
\
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install
|
||||
@@ -1,117 +1,122 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
mold is a faster drop-in replacement for existing Unix linkers. It is several
|
||||
times quicker than the LLVM lld linker, the second-fastest open-source linker,
|
||||
which I initially developed a few years ago. mold aims to enhance developer
|
||||
productivity by minimizing build time, particularly in rapid
|
||||
debug-edit-rebuild cycles.
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is designed to increase developer productivity by reducing
|
||||
build time, especially in rapid debug-edit-rebuild cycles.
|
||||
|
||||
Here is a performance comparison of GNU ld, GNU gold, LLVM lld, and
|
||||
mold when linking final debuginfo-enabled executables for major large
|
||||
programs on a simulated 16-core, 32-thread machine.
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
| Program (linker output size) | GNU ld | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|--------|----------|----------|------
|
||||
| MySQL 8.3 (0.47 GiB) | 10.84s | 7.47s | 1.64s | 0.46s
|
||||
| Clang 19 (1.56 GiB) | 42.07s | 33.13s | 5.20s | 1.35s
|
||||
| Chromium 124 (1.35 GiB) | N/A | 27.40s | 6.10s | 1.52s
|
||||
| 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
|
||||
|
||||
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
|
||||
machine. If you find that mold is not faster than other linkers, feel
|
||||
free to [file a bug report](https://github.com/rui314/mold/issues).
|
||||
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.
|
||||
|
||||
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
|
||||
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
|
||||
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
|
||||
RISC-V, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian PowerPC ELFv2,
|
||||
s390x, SPARC64 and m68k.
|
||||
|
||||
## Why does linking speed matter?
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
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.
|
||||
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 to combine them into a single executable
|
||||
or a shared library file.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Installation
|
||||
## Install
|
||||
|
||||
Binary packages for the following systems are currently available:
|
||||
Binary packages for the following systems are currently available.
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
## How to Build
|
||||
## How to build
|
||||
|
||||
mold is written in C++20, so if you build mold yourself, you will need a
|
||||
recent version of a C++ compiler and a C++ standard library. We recommend GCC
|
||||
10.2 or Clang 16.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
|
||||
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 (or later) as well as libstdc++ 10 or libc++ 7 (or
|
||||
later) are recommended.
|
||||
|
||||
### Install Dependencies
|
||||
### Install dependencies
|
||||
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It will detect your Linux distribution and attempt to install the
|
||||
necessary packages. You may need to run it as root.
|
||||
directory. It recognizes your Linux distribution and tries to install
|
||||
necessary packages. You may want to run it as root.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone --branch stable https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
./install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
|
||||
cmake --build build -j$(nproc)
|
||||
sudo cmake --build build --target install
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v1.7.1
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
sudo cmake --install .
|
||||
```
|
||||
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
example above, `c++` is passed. If that doesn't work for you, try a specific
|
||||
version of a compiler, such as `g++-10` or `clang++-12`.
|
||||
You may need to pass a C++20 compiler command name to `cmake`.
|
||||
In the above case, `c++` is passed. If it doesn't work for you,
|
||||
try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change the
|
||||
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
|
||||
For other cmake options, see the comments in `CMakeLists.txt`.
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change
|
||||
that by passing `-DCMAKE_INSTALL_PREFIX=<directory>`. For other cmake
|
||||
options, see the comments in `CMakeLists.txt`.
|
||||
|
||||
If you are not using a recent enough Linux distribution, or if `cmake` does
|
||||
not work for you for any reason, you can use Podman to build mold in a
|
||||
container. To do so, run `./dist.sh` in this directory instead of using
|
||||
`cmake`. The shell script will pull a container image, build mold and auxiliary
|
||||
files inside it, and package them into a single tar file named
|
||||
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use the mold executable in it.
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`cmake` 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 `./dist.sh` in this
|
||||
directory instead of `cmake`. The shell script pulls a Docker image,
|
||||
builds mold and auxiliary files inside it, and packs them into a
|
||||
single tar file `mold-$version-$arch-linux.tar.gz`. You can extract
|
||||
the tar file anywhere and use `mold` executable in it.
|
||||
|
||||
## 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 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 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 the `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 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>
|
||||
|
||||
@@ -120,83 +125,85 @@ 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. In the
|
||||
above example, we use `clang` as a linker driver as it can always take
|
||||
the `-fuse-ldd` option. If your GCC is recent enough to recognize the
|
||||
option, you may be able to remove the `linker = "clang"` line.
|
||||
|
||||
```toml
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/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>
|
||||
If you are using macOS, you can modify `config.toml` in a similar manner.
|
||||
Here is an example with `mold` installed via [Homebrew](https://brew.sh).
|
||||
|
||||
<details><summary>If you are using Nim</summary>
|
||||
```toml
|
||||
[target.x86_64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
Create `config.nims` in your project directory with the following:
|
||||
|
||||
```nim
|
||||
when findExe("mold").len > 0 and defined(linux):
|
||||
switch("passL", "-fuse-ld=mold")
|
||||
[target.aarch64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-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.bf`, `ld.gold`, or `ld.lld`.
|
||||
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>On macOS</summary>
|
||||
|
||||
mold/macOS is available as an alpha version. It can be used to build not
|
||||
only macOS apps but also iOS apps because their binary formats are the same.
|
||||
|
||||
The command name of mold/macOS is `ld64.mold`. If you build mold on macOS,
|
||||
it still produces `mold` and `ld.mold`, but these executables are useful
|
||||
only for cross compilation (i.e. building Linux apps on macOS.)
|
||||
|
||||
If you find any issue with mold/macOS, please file it to
|
||||
<a href=https://github.com/rui314/mold/issues>our GitHub Issues</a>.
|
||||
|
||||
</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.
|
||||
You can use our <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action to speed up GitHub-hosted continuous build. GitHub Actions
|
||||
runs on a two-core machine, but mold is still significantly faster than
|
||||
the default GNU linker there especially when a program being linked is
|
||||
large.
|
||||
|
||||
</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
|
||||
@@ -207,69 +214,66 @@ String dump of section '.comment':
|
||||
[ 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>
|
||||
|
||||
<details><summary>Online manual</summary>
|
||||
|
||||
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`.
|
||||
Since mold is a drop-in replacement, you should be able to use it
|
||||
without reading its manual. But just in case you need it, it's available
|
||||
online at <a href=https://rui314.github.io/mold.html>here</a>.
|
||||
You can also read the same manual by `man mold`.
|
||||
|
||||
</details>
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
One reason is that it utilizes faster algorithms and more efficient data
|
||||
structures compared to other linkers. Another reason is that mold is highly
|
||||
parallelized.
|
||||
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.
|
||||
|
||||
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
|
||||
mold (right), linking the same program, a Chromium executable.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
As you can see, mold uses all available cores throughout its execution and
|
||||
finishes quickly. In contrast, lld fails to utilize available cores most of
|
||||
the time. In this demo, the maximum parallelism is artificially capped at 16,
|
||||
so that the bars fit in the GIF.
|
||||
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.
|
||||
|
||||
For details, please see the [design notes](docs/design.md).
|
||||
For details, please read [design notes](docs/design.md).
|
||||
|
||||
## Sponsors
|
||||
## License
|
||||
|
||||
It is taken for granted nowadays that compiler toolchains can be easily
|
||||
installed and used for free, and people may not think too much about the
|
||||
individuals behind these "free tools". mold supports many projects, but it
|
||||
is essentially a one-person project. This situation is similar to the one
|
||||
depicted in the following xkcd illustration.
|
||||
mold is available under AGPL. Note that that does not mean that you
|
||||
have to license your program under AGPL if you use mold to link your
|
||||
program. An output of the mold linker is a derived work of the object
|
||||
files and libraries you pass to the linker but not a derived work of
|
||||
the mold linker itself.
|
||||
|
||||
[](https://xkcd.com/2347)
|
||||
Besides that, you can also buy a commercial, non-AGPL license with
|
||||
technical support from our company, Blue Whale Systems PTE LTD. If you
|
||||
are a big company, please consider obtaining it before making hundreds
|
||||
or thousands of developers of your company to depend on mold. mold is
|
||||
mostly a single-person open-source project, and just like other
|
||||
open-source projects, we are not legally obligated to keep maintaining
|
||||
it. A legally-binding commercial license contract addresses the
|
||||
concern. By purchasing a license, you are guaranteed that mold will be
|
||||
maintained for you. Please [contact us](mailto:[email protected])
|
||||
for a commercial license inquiry.
|
||||
|
||||
If you think that the "Nebraska guy" should be rewarded, please consider
|
||||
becoming our [GitHub sponsor](https://github.com/sponsors/rui314)!
|
||||
## Acknowledgement
|
||||
|
||||
We thank everyone who sponsors our 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
|
||||
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
|
||||
and [OpenCollective](https://opencollective.com/mold-linker).
|
||||
We thank you to everybody who sponsors our project. In particular,
|
||||
we'd like to acknowledge the following people and organizations who
|
||||
have sponsored $128/month or more:
|
||||
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Mercury](https://github.com/MercuryTechnologies)
|
||||
- [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)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
@@ -20,14 +20,16 @@
|
||||
// that provides functions and variables used in your program get
|
||||
// linked. To make this efficient, static library functions are
|
||||
// usually separated to each object file in an archive file. You can
|
||||
// see the contents of libc.a by running `ar t
|
||||
// see the contents of libc.a by runnning `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "filetype.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
namespace {
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
@@ -73,14 +75,13 @@ struct ArHdr {
|
||||
return {ar_name, sizeof(ar_name)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
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 *> vec;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mf->size) {
|
||||
@@ -115,20 +116,20 @@ read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
continue;
|
||||
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (path_dirname(mf->name) / name).string();
|
||||
vec.push_back(must_open_file(ctx, path));
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile<C>::must_open(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
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 *> vec;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mf->size - data >= 2) {
|
||||
@@ -162,27 +163,17 @@ read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
std::string_view str = mf->get_contents();
|
||||
|
||||
if (str.starts_with("!<arch>\n"))
|
||||
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);
|
||||
|
||||
assert(str.starts_with("!<thin>\n"));
|
||||
return read_thin_archive_members(ctx, mf);
|
||||
case FileType::THIN_AR:
|
||||
return read_thin_archive_members(ctx, mf);
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context<E> &, MappedFile *);
|
||||
|
||||
template std::vector<MappedFile *>
|
||||
read_fat_archive_members(Context<E> &, MappedFile *);
|
||||
|
||||
template std::vector<MappedFile *>
|
||||
read_archive_members(Context<E> &, MappedFile *);
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,76 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
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;
|
||||
}
|
||||
|
||||
static inline std::string_view string_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);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -14,7 +14,7 @@
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "lib.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
@@ -30,41 +30,42 @@ namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
|
||||
Compressor::~Compressor() {
|
||||
for (std::span<u8> shard : shards)
|
||||
delete[] shard.data();
|
||||
}
|
||||
static std::vector<std::string_view> split(std::string_view input) {
|
||||
std::vector<std::string_view> shards;
|
||||
|
||||
static std::vector<std::span<u8>> split(std::span<u8> input) {
|
||||
std::vector<std::span<u8>> vec;
|
||||
while (!input.empty()) {
|
||||
i64 sz = std::min<i64>(SHARD_SIZE, input.size());
|
||||
vec.push_back(input.subspan(0, sz));
|
||||
input = input.subspan(sz);
|
||||
while (input.size() >= SHARD_SIZE) {
|
||||
shards.push_back(input.substr(0, SHARD_SIZE));
|
||||
input = input.substr(SHARD_SIZE);
|
||||
}
|
||||
return vec;
|
||||
if (!input.empty())
|
||||
shards.push_back(input);
|
||||
return shards;
|
||||
}
|
||||
|
||||
static std::span<u8> zlib_compress(std::span<u8> input, int level) {
|
||||
static std::vector<u8> zlib_compress(std::string_view input) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, the default level is 1.
|
||||
z_stream strm = {};
|
||||
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
strm.next_in = input.data();
|
||||
strm.next_in = (u8 *)input.data();
|
||||
|
||||
// Set an output buffer. deflateBound() returns an upper bound
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
i64 bufsize = deflateBound(&strm, strm.avail_in) + 16;
|
||||
u8 *buf = new u8[bufsize];
|
||||
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits can be held to be written to the
|
||||
// buffer.
|
||||
strm.avail_out = bufsize;
|
||||
strm.next_out = buf;
|
||||
strm.avail_out = buf.size();
|
||||
strm.next_out = buf.data();
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
@@ -83,26 +84,29 @@ static std::span<u8> zlib_compress(std::span<u8> input, int level) {
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
int nbits = 0;
|
||||
int nbits;
|
||||
deflatePending(&strm, Z_NULL, &nbits);
|
||||
if (nbits == 5)
|
||||
CHECK(deflatePrime(&strm, 10, 2));
|
||||
CHECK(deflate(&strm, Z_SYNC_FLUSH));
|
||||
|
||||
assert(strm.avail_out > 0);
|
||||
buf.resize(buf.size() - strm.avail_out);
|
||||
buf.shrink_to_fit();
|
||||
deflateEnd(&strm);
|
||||
return {buf, (size_t)(bufsize - strm.avail_out)};
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
std::vector<u32> adlers(inputs.size());
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
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) {
|
||||
std::span<u8> in = inputs[i];
|
||||
adlers[i] = adler32(1, in.data(), in.size());
|
||||
shards[i] = zlib_compress(in, level);
|
||||
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
@@ -112,7 +116,7 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
|
||||
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::span<u8> &shard : shards)
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
@@ -123,40 +127,47 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 2; // +2 for the header
|
||||
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());
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
static std::span<u8> zstd_compress(std::span<u8> input, int level) {
|
||||
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
|
||||
static std::vector<u8> zstd_compress(std::string_view input) {
|
||||
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
|
||||
constexpr int level = 3; // compression level; must be between 1 to 22
|
||||
|
||||
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
|
||||
level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
return {buf, sz};
|
||||
buf.resize(sz);
|
||||
buf.shrink_to_fit();
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
shards[i] = zstd_compress(inputs[i], level);
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::span<u8> &shard : shards)
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
@@ -167,7 +178,7 @@ void ZstdCompressor::write_to(u8 *buf) {
|
||||
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());
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string_view demangle(std::string_view name) {
|
||||
static thread_local char *p;
|
||||
if (p)
|
||||
free(p);
|
||||
|
||||
// Try to demangle as a Rust symbol. Since legacy-style Rust symbols
|
||||
// are also valid as a C++ mangled name, we need to call this before
|
||||
// cpp_demangle.
|
||||
p = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (p)
|
||||
return p;
|
||||
|
||||
// Try to demangle as a C++ symbol.
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
return *s;
|
||||
return name;
|
||||
}
|
||||
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
|
||||
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
|
||||
#ifndef _WIN32
|
||||
if (name.starts_with("_Z")) {
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,247 +1,33 @@
|
||||
#!/bin/bash
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# 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.40.0-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# This script aims to produce reproducible outputs. That means each time
|
||||
# it's run on the same git commit, it generates a bit-for-bit identical
|
||||
# binary file regardless of when or where it's executed. 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. We use it to construct a container image pinned to a
|
||||
# particular timestamp. snapshot.debian.org is known to be very slow,
|
||||
# but that shouldn't be a big problem for us because we only need that
|
||||
# site the first time.
|
||||
#
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++, but dynamically linked to glibc, libm and a few other
|
||||
# libraries, as these libraries are almost always available on any Linux
|
||||
# system. 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.
|
||||
#
|
||||
# We prefer to build mold with Clang rather than GCC because mold's
|
||||
# Identical Code Folding works best with the LLVM address significance
|
||||
# table (.llvm_addrsig). Building a release binary with GCC produces a
|
||||
# slightly larger binary than with Clang.
|
||||
#
|
||||
# We need a recent version of Clang to build mold. If it's not available
|
||||
# via apt-get, we'll build it ourselves.
|
||||
#
|
||||
# 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.
|
||||
# The mold executable created by this script is statically-linked to
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
|
||||
set -e -x
|
||||
cd "$(dirname $0)"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
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
|
||||
|
||||
# Create a Podman image.
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created container image in GitHub's container repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
|
||||
fi
|
||||
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 9 (Stretch) released in June 2017.
|
||||
#
|
||||
# We use a Google-provided mirror (gcr.io) instead of the official Docker
|
||||
# Hub (docker.io) because docker.io has a strict rate limit policy.
|
||||
#
|
||||
# The toolchain in Debian 9 is too old to build mold, so we rebuild it
|
||||
# from source. We download source archives from official sites and build
|
||||
# them locally, rather than downloading pre-built binaries from somewhere
|
||||
# else, to avoid relying on unverifiable third-party binary blobs. Podman
|
||||
# caches the result of each RUN command, so rebuilding is done only once
|
||||
# per host.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
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 wget file make gcc g++ zlib1g-dev libssl-dev ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
arch=$1
|
||||
echo $arch | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
|
||||
# Build CMake 3.27
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
./bootstrap --parallel=\$(nproc) && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GCC 14
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir gmp mpc mpfr && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
|
||||
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GNU binutils 2.43
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
|
||||
./configure --prefix=/usr && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -fr /build
|
||||
|
||||
# Build Python 3.12.7
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
|
||||
./configure && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build LLVM 20
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-20.1.3.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir b && \
|
||||
cd b && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
|
||||
cmake --build . -j\$(nproc) && \
|
||||
cmake --install . --strip && \
|
||||
rm -rf /build
|
||||
EOF
|
||||
;;
|
||||
aarch64 | arm | ppc64le | s390x)
|
||||
# Debian 11 (Bullseye) released in August 2021
|
||||
#
|
||||
# We don't want to build Clang for these targets on QEMU becuase it
|
||||
# would take an extremely long time. Also, I believe old Linux boxes
|
||||
# are typically x86-64.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
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 gcc-10 g++-10 clang-16 cmake && \
|
||||
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
riscv64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
|
||||
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 gcc-14 g++-14 clang-18 cmake && \
|
||||
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
loongarch64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e 's!http[^ ]*!http://snapshot.debian.org/archive/debian-ports/20250620T014755Z!g' /etc/apt/sources.list && \
|
||||
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 gcc-14 g++-14 clang-19 cmake && \
|
||||
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
set -e -x
|
||||
|
||||
# Source tarballs available on GitHub don't contain .git directory.
|
||||
# Clone the repo if missing.
|
||||
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
|
||||
|
||||
# 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. That confuses BLAKE3's cmake script and
|
||||
# erroneously enables NEON SIMD instructions. `setarch` can be used to
|
||||
# change the output of `uname -m`.
|
||||
setarch=
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
|
||||
mkdir -p dist
|
||||
|
||||
# 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.
|
||||
# We use it to control how the compiler expands the C/C++ __DATE__ and
|
||||
# __TIME__ macros.
|
||||
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
|
||||
$setarch bash -c "
|
||||
set -e
|
||||
export SOURCE_DATE_EPOCH=$timestamp
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
cmake --install .
|
||||
cmake -DMOLD_USE_MOLD=1 .
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest --output-on-failure -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date=@$timestamp
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
|
||||
cp mold /dist
|
||||
sha256sum /dist/$dest.tar.gz
|
||||
"
|
||||
docker run --platform linux/$arch -it --rm -v "$(pwd):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
|
||||
bash -c "mkdir /tmp/build &&
|
||||
cd /tmp/build &&
|
||||
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
|
||||
cmake --build . -j\$(nproc) &&
|
||||
[ $arch = arm ] || ctest -j\$(nproc) &&
|
||||
cmake --install . --prefix $dest --strip &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold mold-wrapper.so /mold &&
|
||||
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
|
||||
@@ -1 +0,0 @@
|
||||
<svg version="1.1" viewBox="0.0 0.0 600.0 371.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" width="600" height="371" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M0 0L600.0 0L600.0 371.0L0 371.0L0 0Z" fill-rule="nonzero"/><path stroke="#333333" stroke-width="1.0" stroke-linecap="butt" d="M82.5 275.5L581.5 275.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 231.5L581.5 231.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 186.5L581.5 186.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 142.5L581.5 142.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 98.5L581.5 98.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 53.5L581.5 53.5" fill-rule="nonzero"/><clipPath id="id_0"><path d="M82.55 53.916668L581.45 53.916668L581.45 275.45L82.55 275.45L82.55 53.916668Z" clip-rule="nonzero"/></clipPath><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroLine truncated
|
||||
|
Before Width: | Height: | Size: 99 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,8 +1,3 @@
|
||||
[This document was written in 2020, and the contents are outdated.
|
||||
Specifically, we no longer believe that object preloading is a good
|
||||
idea. That being said, most of the points in this document still hold
|
||||
even today. Therefore, I'll keep this document as-is.]
|
||||
|
||||
## Design and implementation of mold
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
+4
-4
@@ -23,10 +23,10 @@ process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respects that
|
||||
controlled by a bit in an executable, and the loader respect that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
GCC had (and still has) a feature that depends on the executable
|
||||
GCC had (and still have) a feature that depends on the executable
|
||||
stack, so they invented a way to tell the linker to mark the stack
|
||||
executable. Specifically, if an object file contains a
|
||||
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
|
||||
@@ -48,8 +48,8 @@ feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beware
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beaware
|
||||
that this will significantly weaken your program's security.
|
||||
|
||||
If you don't want to pass `-z execstack`, rewrite your code so that
|
||||
your code does not depend on the executable stack.
|
||||
your code does not depends on the executable stack.
|
||||
@@ -1,179 +0,0 @@
|
||||
# Instrumenting mold with MemorySanitizer
|
||||
|
||||
## Introduction
|
||||
|
||||
Per <https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo>:
|
||||
|
||||
> If you want MemorySanitizer to work properly and not produce any false
|
||||
> positives, you must ensure that all the code in your program and in
|
||||
> libraries it uses is instrumented (i.e. built with `-fsanitize=memory`).
|
||||
> In particular, you would need to link against an MSan-instrumented C++
|
||||
> standard library. We recommend to use [libc++](https://libcxx.llvm.org/)
|
||||
> for that purpose.
|
||||
|
||||
## Building instrumented libc++
|
||||
|
||||
Build an MSan-instrumented libc++ from source:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone https://github.com/llvm/llvm-project
|
||||
cd llvm-project
|
||||
cmake -S ./runtimes -B build-libcxx -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins
|
||||
cmake --build build-libcxx -- cxx cxxabi
|
||||
export LIBCXX="$HOME/llvm-project/build-libcxx" # for subsequent build steps
|
||||
```
|
||||
|
||||
Upon success, `./build-libcxx/{include,lib}` will contain the resulting
|
||||
headers and shared libraries.
|
||||
|
||||
## Linking mold against instrumented libc++
|
||||
|
||||
Use the `MOLD_USE_MSAN` and `MOLD_STDLIB_PREFIX` cmake variables to link
|
||||
mold against the instrumented build of libc++:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DMOLD_USE_MSAN=ON \
|
||||
-DMOLD_STDLIB_PREFIX="$LIBCXX" \
|
||||
-DMOLD_USE_MIMALLOC=OFF \
|
||||
-DMOLD_USE_SYSTEM_TBB=ON
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Most of mold's tests (except those for `-flto`) should work at this
|
||||
point. Run them like normal:
|
||||
|
||||
```sh
|
||||
ctest --test-dir build
|
||||
```
|
||||
|
||||
Any resulting MemorySanitizer errors should be visible in
|
||||
`./build/Temporary/Testing/LastTest.log`.
|
||||
|
||||
## Building instrumented LTO plugin (experimental)
|
||||
|
||||
Exercising `-flto` with MemorySanitizer (and without false positives)
|
||||
requires instrumenting the transitive dependencies of `lto-unix.cc`:
|
||||
|
||||
- `libiberty.a`
|
||||
- `liblto_plugin.so`
|
||||
- `LLVMgold.so`
|
||||
|
||||
> [!NOTE]
|
||||
> This is more involved and time-consuming than building only libc++ from
|
||||
> source. The cost-benefit ratio of this additional instrumentation and
|
||||
> test coverage may be unfavorable in many cases.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The following steps are experimental and unlikely to work exactly as-is
|
||||
> under CI runners and individual developer environments. Consider the
|
||||
> following a starting point rather than a complete HOWTO.
|
||||
|
||||
Build an MSan-instrumented GNU libiberty:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone [email protected]:gcc-mirror/gcc.git
|
||||
cd gcc/libiberty
|
||||
export CC=clang
|
||||
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
|
||||
export LDFLAGS="-fsanitize=memory"
|
||||
./configure
|
||||
make -j$(nproc)
|
||||
unset CC CFLAGS LDFLAGS
|
||||
```
|
||||
|
||||
Install the resulting `./libiberty.a` into the build toolchain being
|
||||
tested with mold. If the system image is ephemeral or disposable (e.g. a
|
||||
short-lived VM or container), a quick-and-dirty install could look like:
|
||||
|
||||
```sh
|
||||
sudo cp /usr/lib/libiberty.a{,.bak}
|
||||
sudo cp ./libiberty.a /usr/lib/
|
||||
```
|
||||
|
||||
Build an MSan-instrumented GCC LTO plugin library:
|
||||
|
||||
```sh
|
||||
cd ~/gcc/lto-plugin # use already-cloned repo from earlier step
|
||||
export CC=clang
|
||||
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
|
||||
export LDFLAGS="-fsanitize=memory"
|
||||
./configure --with-libiberty=/usr/lib
|
||||
make -j$(nproc)
|
||||
unset CC CFLAGS LDFLAGS
|
||||
```
|
||||
|
||||
Install the resulting `liblto_plugin.so`. With the same caveats discussed
|
||||
above, a simple install into an ephemeral environment could look like:
|
||||
|
||||
```sh
|
||||
export GCC_VERSION="$(gcc -dumpversion)"
|
||||
sudo cp /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/liblto_plugin.so{,.bak}
|
||||
sudo cp .libs/liblto_plugin.so /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/
|
||||
```
|
||||
|
||||
Build an MSan-instrumented LLVM LTO plugin library:
|
||||
|
||||
```sh
|
||||
cd ~/llvm-project # use already-cloned repo from earlier step
|
||||
|
||||
CF="-nostdinc++ -isystem $LIBCXX/include/c++/v1"
|
||||
INCDIR="$(find /usr -name plugin-api.h -type f | head -1 | xargs dirname)"
|
||||
|
||||
function configure
|
||||
{
|
||||
cmake -S ./llvm -B build-plugin -G Ninja \
|
||||
-DLLVM_ENABLE_PROJECTS=clang \
|
||||
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_FLAGS="$CF" \
|
||||
-DCMAKE_CXX_FLAGS="$CF" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="$1" \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DLLVM_BINUTILS_INCDIR="$INCDIR" \
|
||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins
|
||||
}
|
||||
|
||||
# workaround for linker issues: configure twice with different LDFLAGS
|
||||
configure "-nostdlib++ -L $LIBCXX/lib -Wl,--rpath=$LIBCXX/lib"
|
||||
configure "-nostdlib++ -L $LIBCXX/lib -lc++ -Wl,--rpath=$LIBCXX/lib"
|
||||
|
||||
cmake --build build-plugin -- LLVMgold.so
|
||||
```
|
||||
|
||||
Install the resulting `LLVMgold.so`. With the same caveats discussed
|
||||
above, a simple install into an ephemeral environment could look like:
|
||||
|
||||
```sh
|
||||
sudo cp /usr/lib/LLVMgold.so{,.bak}
|
||||
sudo cp ./build-plugin/lib/LLVMgold.so /usr/lib/
|
||||
```
|
||||
|
||||
mold's tests for `-flto` should now (mostly) work, though there do seem
|
||||
to be issues around `gcc` inferring `-flto` when not explicitly specified.
|
||||
One workaround is to use `clang` as `TEST_CC`:
|
||||
|
||||
```sh
|
||||
cd ~/mold # use already-cloned and built repo from earlier step
|
||||
TEST_CC=clang TEST_CXX=clang++ ctest --test-dir build
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- <https://github.com/google/sanitizers/wiki/MemorySanitizerBootstrappingClang>
|
||||
- <https://llvm.org/docs/GoldPlugin.html#lto-how-to-build>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
+1209
-574
File diff suppressed because it is too large.
Load diff
-985
@@ -1,985 +0,0 @@
|
||||
mold(1) -- a modern linker
|
||||
==========================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`mold` [_option_...] _file_...
|
||||
|
||||
## DESCRIPTION
|
||||
`mold` is a faster drop-in replacement for the default GNU ld(1).
|
||||
|
||||
### How to use
|
||||
See <https://github.com/rui314/mold#how-to-use>.
|
||||
|
||||
### Compatibility
|
||||
|
||||
`mold` is designed to be a drop-in replacement for the GNU linkers for
|
||||
linking user-land programs. If your user-land program cannot be built due to
|
||||
missing command-line options, please file a bug at
|
||||
<https://github.com/rui314/mold/issues>.
|
||||
|
||||
`mold` supports a very limited set of linker script features, which is just
|
||||
sufficient to read `/usr/lib/x86_64-linux-gnu/libc.so` on Linux systems (on
|
||||
Linux, that file is contrary to its name not a shared library but an ASCII
|
||||
linker script that loads a real `libc.so` file.)
|
||||
|
||||
Beyond that, we have no plan to support any additional linker script features.
|
||||
The linker script is an ad-hoc, over-designed, complex language which we
|
||||
believe needs to be replaced by a simpler mechanism. We have a plan to add a
|
||||
replacement for the linker script to `mold` instead.
|
||||
|
||||
### Archive symbol resolution
|
||||
|
||||
Traditionally, Unix linkers are sensitive to the order in which input files
|
||||
appear on the command line. They process input files from the first (leftmost)
|
||||
file to the last (rightmost) file one-by-one. While reading input files, they
|
||||
maintain sets of defined and undefined symbols. When visiting an archive file
|
||||
(`.a` files), they pull out object files to resolve as many undefined symbols
|
||||
as possible and move on to the next input file. Object files that weren't
|
||||
pulled out will never have a chance for a second look.
|
||||
|
||||
Due to this behavior, you usually have to add archive files at the end of a
|
||||
command line, so that when a linker reaches archive files, it knows what
|
||||
symbols remain as undefined.
|
||||
|
||||
If you put archive files at the beginning of a command line, a linker doesn't
|
||||
have any undefined symbols, and thus no object files will be pulled out from
|
||||
archives. You can change the processing order by using the `--start-group` and
|
||||
`--end-group` options, though they make a linker slower.
|
||||
|
||||
`mold`, as well as the LLVM lld(1) linker, takes a different approach. They
|
||||
remember which symbols can be resolved from archive files instead of
|
||||
forgetting them after processing each archive. Therefore, `mold` and lld(1)
|
||||
can "go back" in a command line to pull out object files from archives if they
|
||||
are needed to resolve remaining undefined symbols. They are not sensitive to
|
||||
the input file order.
|
||||
|
||||
`--start-group` and `--end-group` are still accepted by `mold` and lld(1)
|
||||
for compatibility with traditional linkers, but they are silently ignored.
|
||||
|
||||
### Dynamic symbol resolution
|
||||
|
||||
Some Unix linker features are difficult to understand without comprehending
|
||||
the semantics of dynamic symbol resolution. Therefore, even though it's not
|
||||
specific to `mold`, we'll explain it here.
|
||||
|
||||
We use "ELF module" or just "module" as a collective term to refer to an
|
||||
executable or a shared library file in the ELF format.
|
||||
|
||||
An ELF module may have lists of imported symbols and exported symbols, as well
|
||||
as a list of shared library names from which imported symbols should be
|
||||
imported. The point is that imported symbols are not bound to any specific
|
||||
shared library until runtime.
|
||||
|
||||
Here is how the Unix dynamic linker resolves dynamic symbols. Upon the start
|
||||
of an ELF program, the dynamic linker constructs a list of ELF modules which,
|
||||
as a whole, consist of a complete program. The executable file is always at
|
||||
the beginning of the list followed by its dependent shared libraries. An
|
||||
imported symbol is searched from the beginning of the list to the end. If two
|
||||
or more modules define the same symbol, the one that appears first in the list
|
||||
takes precedence over the others.
|
||||
|
||||
This Unix semantics are contrary to systems such as Windows that have a
|
||||
two-level namespace for dynamic symbols. On Windows, for example, dynamic
|
||||
symbols are represented as a tuple of (`symbol-name`, `shared-library-name`),
|
||||
so that each dynamic symbol is guaranteed to be resolved from some specific
|
||||
library.
|
||||
|
||||
Typically, an ELF module that exports a symbol also imports the same symbol.
|
||||
Such a symbol is usually resolved to itself, but that's not the case if a
|
||||
module that appears before it in the symbol search list provides another
|
||||
definition of the same symbol.
|
||||
|
||||
Let's take `malloc` as an example. Assume that you define your version of
|
||||
`malloc` in your main executable file. Then, all `malloc` calls from any
|
||||
module are resolved to your function instead of the one in libc, because the
|
||||
executable is always at the beginning of the dynamic symbol search list. Note
|
||||
that even `malloc` calls within libc are resolved to your definition since
|
||||
libc exports and imports `malloc`. Therefore, by defining `malloc` yourself,
|
||||
you can overwrite a library function, and the `malloc` in libc becomes dead
|
||||
code.
|
||||
|
||||
These Unix semantics are tricky and sometimes considered harmful. For example,
|
||||
assume that you accidentally define `atoi` as a global function in your
|
||||
executable that behaves completely differently from the one in the C standard.
|
||||
Then, all `atoi` function calls from any modules (even function calls within
|
||||
libc) are redirected to your function instead of the one in libc, which will
|
||||
very likely cause a problem. That is a somewhat surprising consequence for an
|
||||
accidental name conflict. On the other hand, this semantic is sometimes useful
|
||||
because it allows users to override library functions without rebuilding
|
||||
modules containing them.
|
||||
|
||||
Whether good or bad, you should keep these semantics in mind to understand
|
||||
Unix linkers' behaviors.
|
||||
|
||||
### Build reproducibility
|
||||
|
||||
`mold`'s output is deterministic. That is, if you pass the same object files
|
||||
and the same command-line options to the same version of `mold`, it is
|
||||
guaranteed that `mold` produces the bit-for-bit identical output. The linker's
|
||||
internal randomness, such as the timing of thread scheduling or iteration
|
||||
orders of hash tables, doesn't affect the output.
|
||||
|
||||
`mold` does not have any host-specific default settings. This is contrary to the
|
||||
GNU linkers, for which some configurable values, such as system-dependent
|
||||
library search paths, are hard-coded. `mold` depends only on its command-line
|
||||
arguments.
|
||||
|
||||
## OPTION NOTATIONS
|
||||
|
||||
Multi-letter long options may precede either a single dash or double dashes,
|
||||
except for those starting with the letter "o". For historical reasons, long
|
||||
options beginning with "o" must precede double dashes.
|
||||
|
||||
For example, you can spell `--as-needed` as `-as-needed`, but `--omagic` must
|
||||
not be spelled as `-omagic`. `-omagic` will be interpreted not as `--omagic`
|
||||
but as `-o magic`.
|
||||
|
||||
## MOLD-SPECIFIC OPTIONS
|
||||
|
||||
* `--chroot`=_dir_:
|
||||
Set _dir_ as the root directory.
|
||||
|
||||
* `--color-diagnostics`=[ _auto_ | _always_ | _never_ ]:
|
||||
Show diagnostic messages in color using ANSI escape sequences. `auto` means
|
||||
that `mold` prints out messages in color only if the standard output is
|
||||
connected to a TTY. Default is `auto`.
|
||||
|
||||
* `--color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=auto`.
|
||||
|
||||
* `--no-color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=never`.
|
||||
|
||||
* `--detach`, `--no-detach`:
|
||||
Permit or do not permit mold to create a debug info file in the background.
|
||||
|
||||
* `--fork`, `--no-fork`:
|
||||
Spawn a child process and let it do the actual linking. When linking a large
|
||||
program, the OS kernel can take a few hundred milliseconds to terminate a
|
||||
`mold` process. `--fork` hides that latency. By default, it does fork.
|
||||
|
||||
Note that `--fork` also hides the resource usage statistics reported by
|
||||
time(2), since it doesn't call waitpid(2) on the child process. If you
|
||||
need those statistics, pass `--no-fork`.
|
||||
|
||||
* `--perf`:
|
||||
Print performance statistics.
|
||||
|
||||
* `--print-dependencies`:
|
||||
Print out dependency information for input files.
|
||||
|
||||
Each line of the output for this option shows which file depends on which
|
||||
file to use a specific symbol. This option is useful for debugging why some
|
||||
object file in a static archive got linked or why some shared library is
|
||||
kept in an output file's dependency list even with `--as-needed`.
|
||||
|
||||
* `--relocatable-merge-sections`:
|
||||
By default, `mold` doesn't merge input sections by name when merging input
|
||||
object files into a single output object file for `-r`. For example,
|
||||
`.text.foo` and `.text.bar` aren't merged for `-r` even though they are
|
||||
merged into `.text` based on the default section merging rules.
|
||||
|
||||
This option changes the behavior so that `mold` merges input sections by
|
||||
name by the default section merging rules.
|
||||
|
||||
* `--repro`:
|
||||
Archive input files, as well as a text file containing command line options,
|
||||
in a tar file so that you can run `mold` with the exact same inputs again.
|
||||
This is useful for reporting a bug with a reproducer. The output filename is
|
||||
`path/to/output.tar`, where `path/to/output` is an output filename specified
|
||||
by `-o`.
|
||||
|
||||
* `--reverse-sections`:
|
||||
Reverse the order of input sections before assigning them the offsets in the
|
||||
output file.
|
||||
|
||||
This option is useful for finding bugs that depend on the initialization
|
||||
order of global objects. In C++, constructors of global objects in a single
|
||||
source file are guaranteed to be executed in the source order, but there's
|
||||
no such guarantee across compilation units. Usually, constructors are
|
||||
executed in the order given to the linker, but depending on it is a mistake.
|
||||
|
||||
By reversing the order of input sections using `--reverse-sections`, you can
|
||||
easily test that your program works in the reversed initialization order.
|
||||
|
||||
* `--run` _command_ _arg_...:
|
||||
Run _command_ with `mold` as `/usr/bin/ld`. Specifically, `mold` runs a
|
||||
given command with the `LD_PRELOAD` environment set to intercept exec(3)
|
||||
family functions and replaces `argv[0]` with itself if it is `ld`,
|
||||
`ld.gold`, or `ld.lld`.
|
||||
|
||||
* `--separate-debug-file`, `--separate-debug-file`=_file_:
|
||||
Bundle debug info sections into a separate file instead of embedding them in
|
||||
an output executable or a shared library. mold creates a debug info file in
|
||||
the background by default, so that you can start running your executable as
|
||||
soon as possible.
|
||||
|
||||
By default, the debug info file is created in the same directory as is the
|
||||
output file, with the `.dbg` file extension. That filename is embedded into
|
||||
the output file so that `gdb` can automatically find the debug info file for
|
||||
the output file. For more info about gdb features related to separate debug
|
||||
files, see
|
||||
<https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html>.
|
||||
|
||||
mold holds a file lock with flock(2) while creating a debug info file in the
|
||||
background.
|
||||
|
||||
If you don't want to create a debug info file in the background, pass the
|
||||
`--no-detach` option.
|
||||
|
||||
* `--shuffle-sections`, `--shuffle-sections`=_number_:
|
||||
Randomize the output by shuffling the order of input sections before
|
||||
assigning them the offsets in the output file. If a _number_ is given, it's
|
||||
used as a seed for the random number generator, so that the linker produces
|
||||
the same output for the same seed. If no seed is given, a random number is
|
||||
used as a seed.
|
||||
|
||||
This option is useful for benchmarking. Modern CPUs are sensitive to a
|
||||
program's memory layout. A seemingly benign change in program layout, such
|
||||
as a small size increase of a function in the middle of a program, can
|
||||
affect the program's performance. Therefore, even if you write new code and
|
||||
get a good benchmark result, it is hard to say whether the new code improves
|
||||
the program's performance; it is possible that the new memory layout happens
|
||||
to perform better.
|
||||
|
||||
By running a benchmark multiple times with randomized memory layouts using
|
||||
`--shuffle-sections`, you can isolate your program's real performance number
|
||||
from the randomness caused by memory layout changes.
|
||||
|
||||
* `--spare-program-headers`=_number_:
|
||||
Append the given number of `PT_NULL` entries to the end of the program
|
||||
header, so that post-link processing tools can easily add new segments by
|
||||
overwriting the null entries.
|
||||
|
||||
Note that ELF requires all `PT_LOAD` segments to be sorted by `p_vaddr`.
|
||||
Therefore, if you add a new LOAD segment, you may need to sort the entire
|
||||
program header.
|
||||
|
||||
* `--stats`:
|
||||
Print input statistics.
|
||||
|
||||
* `--thread-count`=_count_:
|
||||
Use _count_ number of threads.
|
||||
|
||||
* `--threads`, `--no-threads`:
|
||||
Use multiple threads. By default, `mold` uses as many threads as the number of
|
||||
cores or 32, whichever is smaller. The reason it is capped at 32 is because
|
||||
`mold` doesn't scale well beyond that point. To use only one thread, pass
|
||||
`--no-threads` or `--thread-count=1`.
|
||||
|
||||
* `--quick-exit`, `--no-quick-exit`:
|
||||
Use or do not use `quick_exit` to exit.
|
||||
|
||||
* `--zero-to-bss`:
|
||||
Convert all-zero data sections into BSS.
|
||||
|
||||
When this option is enabled, `mold` scans input data sections that are not
|
||||
of type `SHT_NOBITS` and checks whether their contents consist solely of
|
||||
zero bytes. Such sections are then converted into BSS (`SHT_NOBITS`)
|
||||
sections. This reduces the size of the output file, since BSS sections
|
||||
occupy no space in the file image.
|
||||
|
||||
This behavior is especially useful for user-defined sections created with
|
||||
`__attribute__((section(".sectname")))` that contain uninitialized global
|
||||
variables. GCC and Clang do not automatically mark such sections as BSS
|
||||
even if their contents are entirely zero, and instead emit them as regular
|
||||
data sections.
|
||||
|
||||
For example, consider `__attribute__((section(".sectname"))) int vec[256];`.
|
||||
|
||||
By default, this results in a `.sectname` section of type `SHT_PROGBITS`
|
||||
filled with zeros. With `--zero-to-bss`, the linker will recognize it as
|
||||
empty data and convert it to a `SHT_NOBITS` section, reducing the output
|
||||
file size without changing runtime semantics.
|
||||
|
||||
* `-z rewrite-endbr`, `-z norewrite-endbr`:
|
||||
As a security measure, some CPU instruction sets have recently gained a
|
||||
feature to protect control flow integrity by disallowing indirect branches
|
||||
by default. If the feature is enabled, the instruction that is executed
|
||||
immediately after an indirect branch must be an branch target marker
|
||||
instruction, or a CPU-level fault will raise. The marker instruction is also
|
||||
known as "landing pad" instruction, to which indirect branches can land.
|
||||
This feature makes ROP attacks harder to conduct.
|
||||
|
||||
To use the feature, a function whose pointer is taken needs to begin with a
|
||||
landing pad because a function call via a function pointer is compiled to an
|
||||
indirect branch. On the other hand, if a function is called only directly
|
||||
(i.e. referred to only by _direct_ branch instructions), it doesn't have to
|
||||
begin with it.
|
||||
|
||||
By default, the compiler always emits a landing pad at the beginning of each
|
||||
global function because it doesn't know whether or not the function's
|
||||
pointer is taken in another translation unit. As a result, the resulting
|
||||
binary has more attack surface than necessary.
|
||||
|
||||
If `--rewrite-endbr` is given, mold conducts a whole program analysis
|
||||
to identify functions whose addresses are actually taken and rewrites
|
||||
landing pads with no-ops for non-address-taken functions, reducing the
|
||||
attack surface.
|
||||
|
||||
This feature is currently available only on x86-64.
|
||||
|
||||
## GNU-COMPATIBLE OPTIONS
|
||||
|
||||
* `--help`:
|
||||
Report usage information to stdout and exit.
|
||||
|
||||
* `-v`, `--version`:
|
||||
Report version information to stdout.
|
||||
|
||||
* `-V`:
|
||||
Report version and target information to stdout.
|
||||
|
||||
* `-E`, `--export-dynamic`, `--no-export-dynamic`:
|
||||
When creating an executable, using the `-E` option causes all global symbols
|
||||
to be put into the dynamic symbol table, so that the symbols are visible
|
||||
from other ELF modules at runtime.
|
||||
|
||||
By default, or if `--no-export-dynamic` is given, only symbols that are
|
||||
referenced by DSOs at link-time are exported from an executable.
|
||||
|
||||
* `-F` _libname_, `--filter`=_libname_:
|
||||
Set the `DT_FILTER` dynamic section field to _libname_.
|
||||
|
||||
* `-I` _file_, `--dynamic-linker`=_file_, `--no-dynamic-linker`:
|
||||
Set the dynamic linker path to _file_. If no `-I` option is given, or if
|
||||
`--no-dynamic-linker` is given, no dynamic linker path is set to an output
|
||||
file. This is contrary to the GNU linkers which set a default dynamic linker
|
||||
path in that case. This difference doesn't usually make any difference
|
||||
because the compiler driver always passes `-I` to the linker.
|
||||
|
||||
* `-L` _dir_, `--library-path`=_dir_:
|
||||
Add _dir_ to the list of library search paths from which `mold` searches
|
||||
libraries for the `-l` option.
|
||||
|
||||
Unlike the GNU linkers, `mold` does not have default search paths. This
|
||||
difference doesn't usually make any difference because the compiler driver
|
||||
always passes all necessary search paths to the linker.
|
||||
|
||||
* `-M`, `--print-map`:
|
||||
Write a map file to stdout.
|
||||
|
||||
* `-N`, `--omagic`, `--no-omagic`:
|
||||
Force `mold` to emit an output file with an old-fashioned memory layout.
|
||||
First, it makes the first data segment not aligned to a page boundary.
|
||||
Second, text segments are marked as writable if the option is given.
|
||||
|
||||
* `-S`, `--strip-debug`:
|
||||
Omit `.debug_*` sections from the output file.
|
||||
|
||||
* `-T` _file_, `--script`=_file_:
|
||||
Read linker script from _file_.
|
||||
|
||||
* `-X`, `--discard-locals`:
|
||||
Discard temporary local symbols to reduce the sizes of the symbol table and
|
||||
the string table. Temporary local symbols are local symbols starting with
|
||||
`.L`. Compilers usually generate such symbols for unnamed program elements
|
||||
such as string literals or floating-point literals.
|
||||
|
||||
* `-e` _symbol_, `--entry`=_symbol_:
|
||||
|
||||
Use _symbol_ as the entry point symbol instead of the default entry
|
||||
point symbol _start.
|
||||
|
||||
* `-f` _shlib_, `--auxiliary`=_shlib_:
|
||||
Set the `DT_AUXILIARY` dynamic section field to _shlib_.
|
||||
|
||||
* `-h` _libname_, `--soname`=_libname_:
|
||||
Set the `DT_SONAME` dynamic section field to _libname_. This option is used
|
||||
when creating a shared object file. Typically, when you create `libfoo.so`,
|
||||
you want to pass `--soname=foo` to a linker.
|
||||
|
||||
* `-l` _libname_:
|
||||
Search for `lib`_libname_`.so` or `lib`_libname_`.a` from library search
|
||||
paths.
|
||||
|
||||
* `-m` _target_:
|
||||
Choose a _target_.
|
||||
|
||||
* `-o` _file_, `--output`=_file_:
|
||||
Use _file_ as the output file name instead of the default name `a.out`.
|
||||
|
||||
* `-r`, `--relocatable`:
|
||||
Instead of generating an executable or a shared object file, combine input
|
||||
object files to generate another object file that can be used as an input to
|
||||
a linker.
|
||||
|
||||
* `-s`, `--strip-all`:
|
||||
Omit `.symtab` section from the output file.
|
||||
|
||||
* `-u` _symbol_, `--undefined`=_symbol_:
|
||||
If _symbol_ remains as an undefined symbol after reading all object files,
|
||||
and if there is a static archive that contains an object file defining
|
||||
_symbol_, pull out the object file and link it so that the output file
|
||||
contains a definition of _symbol_.
|
||||
|
||||
* `-y` _symbol_, `--trace-symbol`=_symbol_:
|
||||
Trace references to _symbol_.
|
||||
|
||||
* `--Bdynamic`:
|
||||
Link against shared libraries.
|
||||
|
||||
* `--Bstatic`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--Bsymbolic`:
|
||||
When creating a shared library, make global symbols export-only (i.e. do not
|
||||
import the same symbol). As a result, references within a shared library are
|
||||
always resolved locally, negating symbol override at runtime. See "Dynamic
|
||||
symbol resolution" for more information about symbol imports and exports.
|
||||
|
||||
* `--Bsymbolic-functions`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for function
|
||||
symbols. Data symbols remain being both imported and exported.
|
||||
|
||||
* `--Bsymbolic-non-weak`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for non-weak
|
||||
symbols. Weak symbols remain being both imported and exported.
|
||||
|
||||
* `--Bsymbolic-non-weak-functions`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for non-weak
|
||||
function symbols. Data symbols and weak function symbols remain being both
|
||||
imported and exported.
|
||||
|
||||
* `--Bno-symbolic`:
|
||||
Cancel `--Bsymbolic`, `--Bsymbolic-functions`, `--Bsymbolic-non-weak` and
|
||||
`--Bsymbolic-non-weak-functions`.
|
||||
|
||||
* `--Map`=_file_:
|
||||
Write map file to _file_.
|
||||
|
||||
* `--Tbss`=_address_:
|
||||
Alias for `--section-start=.bss=`_address_.
|
||||
|
||||
* `--Tdata`=_address_:
|
||||
Alias for `--section-start=.data=`_address_.
|
||||
|
||||
* `--Ttext`=_address_:
|
||||
Alias for `--section-start=.text=`_address_.
|
||||
|
||||
* `--allow-multiple-definition`:
|
||||
Normally, the linker reports an error if there are more than one definition
|
||||
of a symbol. This option changes the default behavior so that it doesn't
|
||||
report an error for duplicate definitions and instead use the first
|
||||
definition.
|
||||
|
||||
* `--allow-shlib-undefined`, `--no-allow-shlib-undefined`:
|
||||
Even if mold succeeds in linking a main executable without undefined symbol
|
||||
errors, you may still encounter symbol lookup errors at runtime because the
|
||||
dynamic linker cannot find some symbols in shared libraries in any ELF
|
||||
module. This occurs because mold ignores undefined symbols in shared
|
||||
libraries by default.
|
||||
|
||||
If you pass `--no-allow-shlib-undefined`, mold verifies that undefined
|
||||
symbols in shared libraries given to the linker can be resolved at
|
||||
link-time. In other words, this converts the runtime error to a link-time
|
||||
error.
|
||||
|
||||
Note that you need to pass all shared libraries, including indirectly
|
||||
dependent ones, to the linker as arguments for `-l`. If a shared library
|
||||
depends on a library that's not passed to the linker, the verification will
|
||||
be skipped for that file.
|
||||
|
||||
* `--as-needed`, `--no-as-needed`:
|
||||
By default, shared libraries given to the linker are unconditionally added
|
||||
to the list of required libraries in an output file. However, shared
|
||||
libraries after `--as-needed` are added to the list only when at least one
|
||||
symbol is actually used by the output file. In other words, shared libraries
|
||||
after `--as-needed` are not added to the list of needed libraries if they
|
||||
are not needed by a program.
|
||||
|
||||
The `--no-as-needed` option restores the default behavior for subsequent
|
||||
files.
|
||||
|
||||
* `--build-id`=[ `md5` | `sha1` | `sha256` | `fast` | `uuid` | `0x`_hexstring_ | `none` ]:
|
||||
Create a `.note.gnu.build-id` section containing a byte string to uniquely
|
||||
identify an output file. `sha256` compute a 256-bit cryptographic hash of an
|
||||
output file and set it to build-id. `md5` and `sha1` compute the same hash
|
||||
but truncate it to 128 and 160 bits, respectively, before setting it to
|
||||
build-id. `uuid` sets a random 128-bit UUID. `0x`_hexstring_ sets
|
||||
_hexstring_. `fast` is a synonym for `sha256`.
|
||||
|
||||
* `--build-id`:
|
||||
Synonym for `--build-id=sha256`.
|
||||
|
||||
* `--no-build-id`:
|
||||
Synonym for `--build-id=none`.
|
||||
|
||||
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zlib:`_N_ | `zstd` | `zstd:`_N_ | `none` ]:
|
||||
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
|
||||
compression algorithm. `zlib-gabi` is an alias for `zlib`. `zlib:`_N_
|
||||
selects the zlib compression level, where _N_ is between 0 and 9 (0
|
||||
means no compression), and `zstd:`_N_ selects the zstd compression
|
||||
level, where _N_ is between 1 and 22. Higher levels achieve better
|
||||
compression ratios but are slower. `zlib` is equivalent to `zlib:1`
|
||||
and `zstd` is equivalent to `zstd:3`.
|
||||
|
||||
* `--defsym`=_symbol_=_value_:
|
||||
Define _symbol_ as an alias for _value_.
|
||||
|
||||
_value_ is either an integer (in decimal or hexadecimal with `0x` prefix) or
|
||||
a symbol name. If an integer is given as a value, _symbol_ is defined as an
|
||||
absolute symbol with the given value.
|
||||
|
||||
* `--default-symver`:
|
||||
Use soname as a symbol version and append that version to all symbols.
|
||||
|
||||
* `--demangle`, `--no-demangle`:
|
||||
Demangle C++ and Rust symbols in log messages.
|
||||
|
||||
* `--dependency-file`=_file_:
|
||||
Write a dependency file to _file_. The contents of the written file is
|
||||
readable by make(1), which defines only one rule with the linker's output
|
||||
file as a target and all input files as its prerequisites. Users are
|
||||
expected to include the generated dependency file into a Makefile to
|
||||
automate the dependency management. This option is analogous to the
|
||||
compiler's `-MM -MF` options.
|
||||
|
||||
* `--dynamic-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_. Same as
|
||||
`--export-dynamic-symbol-list`, except that it implies `--Bsymbolic`. If
|
||||
_file_ does not exist in the current directory, it is searched from library
|
||||
search paths for the sake of compatibility with GNU ld.
|
||||
|
||||
* `--eh-frame-hdr`, `--no-eh-frame-hdr`:
|
||||
Create `.eh_frame_hdr` section.
|
||||
|
||||
* `--emit-relocs`:
|
||||
The linker usually "consumes" relocation sections. That is, the linker
|
||||
applies relocations to other sections, and relocation sections themselves
|
||||
are discarded.
|
||||
|
||||
The `--emit-relocs` instructs the linker to leave relocation sections in the
|
||||
output file. Some post-link binary analysis or optimization tools such as
|
||||
LLVM Bolt need them.
|
||||
|
||||
* `--enable-new-dtags`, `--disable-new-dtags`:
|
||||
By default, `mold` emits `DT_RUNPATH` for `--rpath`. If you pass
|
||||
`--disable-new-dtags`, `mold` emits `DT_RPATH` for `--rpath` instead.
|
||||
|
||||
* `--execute-only`:
|
||||
|
||||
Traditionally, setting the executable bit to 1 for a memory page implies
|
||||
that the page also become readable, which allows machine code to be read
|
||||
as data at runtime. That is actually what an attacker often does after
|
||||
gaining a limited control of a process to find pieces of machine code
|
||||
they can use to gain the full control of the process. As a mitigation,
|
||||
recent processors including some ARM64 ones allows "execute-only" pages.
|
||||
If a page is execute-only, you can call a function there as long as you
|
||||
know its address but can't read it as data.
|
||||
|
||||
This option marks text segments as execute-only by setting just the "X"
|
||||
bit instead of "RX". Note that on most systems, the absence of the "R"
|
||||
bit in the text segment serves just as a hint. If you run a program
|
||||
linked with `--execute-only` on a processor that doesn't support
|
||||
execute-only pages, your executable will likely still function normally,
|
||||
but the text segment will remain readable.
|
||||
|
||||
* `--exclude-libs`=_libraries_ ...:
|
||||
Mark all symbols in the given _libraries_ hidden.
|
||||
|
||||
* `--export-dynamic-symbol`=_symbol_:
|
||||
Put symbols matching _symbol_ in the dynamic symbol table. _symbol_ may be a
|
||||
glob pattern in the same syntax as for the `--export-dynamic-symbol-list` or
|
||||
`--version-script` options.
|
||||
|
||||
* `--export-dynamic-symbol-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_.
|
||||
|
||||
* `--fatal-warnings`, `--no-fatal-warnings`:
|
||||
Treat warnings as errors.
|
||||
|
||||
* `--fini`=_symbol_:
|
||||
Call _symbol_ at unload-time.
|
||||
|
||||
* `--gc-sections`, `--no-gc-sections`:
|
||||
Remove unreferenced sections.
|
||||
|
||||
* `--gdb-index`:
|
||||
Create a `.gdb_index` section to speed up GNU debugger. To use this, you
|
||||
need to compile source files with the `-ggnu-pubnames` compiler flag.
|
||||
|
||||
* `--hash-style`=[ `sysv` | `gnu` | `both` | `none` ]:
|
||||
Set hash style.
|
||||
|
||||
* `--icf`=[ `safe` | `all` | `none` ], `--no-icf`:
|
||||
It is not uncommon for a program to contain many identical functions that
|
||||
differ only in name. For example, a C++ template `std::vector` is very
|
||||
likely to be instantiated to the identical code for `std::vector<int>` and
|
||||
`std::vector<unsigned>` because the container cares only about the size of
|
||||
the parameter type. Identical Code Folding (ICF) is a size optimization to
|
||||
identify and merge such identical functions.
|
||||
|
||||
If `--icf=all` is given, `mold` tries to merge all identical functions. This
|
||||
reduces the size of the output most, but it is not a "safe" optimization. It
|
||||
is guaranteed in C and C++ that two pointers pointing two different
|
||||
functions will never be equal, but `--icf=all` breaks that assumption as two
|
||||
identical functions have the same address after merging. So a care must be
|
||||
taken when you use this flag that your program does not depend on the
|
||||
function pointer uniqueness.
|
||||
|
||||
`--icf=safe` is a flag to merge functions only when it is safe to do so.
|
||||
That is, if a program does not take an address of a function, it is safe to
|
||||
merge that function with other function, as you cannot compare a function
|
||||
pointer with something else without taking an address of a function.
|
||||
|
||||
`--icf=safe` needs to be used with a compiler that supports `.llvm_addrsig`
|
||||
section which contains the information as to what symbols are address-taken.
|
||||
LLVM/Clang supports that section by default. Since GCC does not support it
|
||||
yet, you cannot use `--icf=safe` with GCC (it doesn't do any harm but can't
|
||||
optimize at all.)
|
||||
|
||||
`--icf=none` and `--no-icf` disables ICF.
|
||||
|
||||
* `--ignore-data-address-equality`:
|
||||
Make ICF to merge not only functions but also data. This option should be
|
||||
used in combination with `--icf=all`.
|
||||
|
||||
* `--image-base`=_addr_:
|
||||
Set the base address to _addr_.
|
||||
|
||||
* `--init`=_symbol_:
|
||||
Call _symbol_ at load-time.
|
||||
|
||||
* `--no-undefined`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `--noinhibit-exec`:
|
||||
Create an output file even if errors occur.
|
||||
|
||||
* `--package-metadata`=_percent-encoded-string_:
|
||||
Embed a specified string into the `.note.package` section. This option
|
||||
is designed for build scripts that generate binary packages, such as
|
||||
`.rpm` or `.deb`, to include package metadata in each executable. It
|
||||
simplifies the process of identifying the corresponding package for a
|
||||
given executable or core file.
|
||||
|
||||
An argument to this option is treated as percent-encoded and decoded
|
||||
before being inserted into the section, allowing you to avoid the use of
|
||||
the comma (`,`) character in the argument. This is useful because the
|
||||
compiler replaces all occurrences of commas in `-Wl,` with spaces before
|
||||
forwarding them to the linker. Note that `mold` always interprets the
|
||||
argument as percent-encoded, so you also need to escape all occurrences
|
||||
of `%` as `%25`.
|
||||
|
||||
* `--pack-dyn-relocs`=[ `relr` | `android` | `android+relr` | `none` ]:
|
||||
Compress dynamic relocations in the output file.
|
||||
|
||||
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
|
||||
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
|
||||
`.relr.dyn` section uses a space-efficient encoding scheme, specifying this
|
||||
flag can reduce the size of the output. This is typically most effective for
|
||||
position-independent executable.
|
||||
|
||||
If `android` is specified, all dynamic relocations are encoded in Android's
|
||||
packed relocation format (APS2) and stored in the `.rel.dyn` or `.rela.dyn`
|
||||
section, whose section type is changed to `SHT_ANDROID_REL` or
|
||||
`SHT_ANDROID_RELA`. The dynamic table is updated to use `DT_ANDROID_REL`
|
||||
/ `DT_ANDROID_RELA` and their `SZ` variants in place of the standard tags.
|
||||
|
||||
`android+relr` combines both: `R_*_RELATIVE` relocations go to `.relr.dyn`
|
||||
and the rest are APS2-encoded. This is the configuration used by most
|
||||
Android system binaries.
|
||||
|
||||
Note that a runtime loader has to support `.relr.dyn` to run executables or
|
||||
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2026, glibc
|
||||
(since 2.36), musl (since 1.2.4), Android, ChromeOS, and Fuchsia all
|
||||
support it. The APS2 format, on the other hand, is only handled by
|
||||
Android's bionic loader; the encoder itself is target-agnostic, but the
|
||||
produced binary can only be loaded on platforms where bionic (or a
|
||||
compatible loader) handles `DT_ANDROID_REL(A)`.
|
||||
|
||||
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
|
||||
Create a position-independent executable.
|
||||
|
||||
* `--print-gc-sections`, `--no-print-gc-sections`:
|
||||
Print removed unreferenced sections.
|
||||
|
||||
* `--print-icf-sections`, `--no-print-icf-sections`:
|
||||
Print folded identical sections.
|
||||
|
||||
* `--push-state`, `--pop-state`:
|
||||
`--push-state` saves the current values of `--as-needed`, `--whole-archive`,
|
||||
`--static`, and `--start-lib`. The saved values can be restored by
|
||||
pop-state.
|
||||
|
||||
`--push-state` and `--pop-state` pairs can nest.
|
||||
|
||||
These options are useful when you want to construct linker command line
|
||||
options programmatically. For example, if you want to link `libfoo.so` by
|
||||
as-needed basis but don't want to change the global state of `--as-needed`,
|
||||
you can append `--push-state --as-needed -lfoo --pop-state` to the linker
|
||||
command line options.
|
||||
|
||||
* `--relax, --no-relax`:
|
||||
Rewrite machine instructions with more efficient ones for some relocations.
|
||||
The feature is enabled by default.
|
||||
|
||||
* `--require-defined`=_symbol_:
|
||||
Like `--undefined`, except the new symbol must be defined by the end of the
|
||||
link.
|
||||
|
||||
* `--retain-symbols-file`=_file_:
|
||||
Keep only symbols listed in _file_. _file_ is a text file containing a
|
||||
symbol name on each line. `mold` discards all local symbols as well as
|
||||
global symbol that are not in _file_. Note that this option removes symbols
|
||||
only from `.symtab` section and does not affect `.dynsym` section, which is
|
||||
used for dynamic linking.
|
||||
|
||||
* `--rpath`=_dir_:
|
||||
Add _dir_ to runtime search paths.
|
||||
|
||||
* `--section-start`=_section_=_address_:
|
||||
Set _address_ to section. _address_ is a hexadecimal number that may start
|
||||
with an optional `0x`.
|
||||
|
||||
* `--shared`, `--Bshareable`:
|
||||
Create a share library.
|
||||
|
||||
* `--spare-dynamic-tags`=_number_:
|
||||
Append the given number of `DT_NULL` entries to the end of the `.dynamic`
|
||||
section, so that post-link processing tools can easily add new dynamic tags
|
||||
by overwriting the null entries.
|
||||
|
||||
* `--start-lib`, `--end-lib`:
|
||||
Handle object files between `--start-lib` and `--end-lib` as if they were in
|
||||
an archive file. That means object files between them are linked only when
|
||||
they are needed to resolve undefined symbols. The options are useful if you
|
||||
want to link object files only when they are needed but want to avoid the
|
||||
overhead of running ar(3).
|
||||
|
||||
* `--static`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--sysroot`=_dir_:
|
||||
Set target system root directory to _dir_.
|
||||
|
||||
* `--trace`:
|
||||
Print name of each input file.
|
||||
|
||||
* `--undefined-glob`=_pattern_:
|
||||
Synonym for `--undefined`, except that `--undefined-glob` takes a glob
|
||||
pattern instead of just a single symbol name.
|
||||
|
||||
* `--undefined-version`, `--no-undefined-version`:
|
||||
By default, `mold` warns on a symbol specified by a version script or by
|
||||
`--export-dynamic-symbol` if it is not defined. You can silence the warning
|
||||
by `--undefined-version`.
|
||||
|
||||
* `--unique`=_pattern_:
|
||||
Don't merge input sections that match the given glob pattern _pattern_.
|
||||
|
||||
* `--unresolved-symbols`=[ `report-all` | `ignore-all` | `ignore-in-object-files` | `ignore-in-shared-libs` ]:
|
||||
How to handle undefined symbols.
|
||||
|
||||
* `--version-script`=_file_:
|
||||
Read version script from _file_. If _file_ does not exist in the current
|
||||
directory, it is searched from library search paths for the sake of
|
||||
compatibility with GNU ld.
|
||||
|
||||
* `-w`, `--no-warnings`:
|
||||
Suppress warnings and cancel `--fatal-warnings`.
|
||||
|
||||
* `--warn-common`, `--no-warn-common`:
|
||||
Warn about common symbols.
|
||||
|
||||
* `--warn-once`:
|
||||
Only warn once for each undefined symbol instead of warn for each relocation
|
||||
referring an undefined symbol.
|
||||
|
||||
* `--warn-unresolved-symbols`, `--error-unresolved-symbols`:
|
||||
Normally, the linker reports an error for unresolved symbols.
|
||||
`--warn-unresolved-symbols` option turns it into a warning.
|
||||
`--error-unresolved-symbols` option restores the default behavior.
|
||||
|
||||
* `--whole-archive`, `--no-whole-archive`:
|
||||
When archive files (`.a` files) are given to the linker, only object files
|
||||
that are needed to resolve undefined symbols are extracted from them and
|
||||
linked to an output file. `--whole-archive` changes that behavior for
|
||||
subsequent archives so that the linker extracts all object files and links
|
||||
them to an output. For example, if you are creating a shared object file and
|
||||
you want to include all archive members to the output, you should pass
|
||||
`--whole-archive`. `--no-whole-archive` restores the default behavior for
|
||||
subsequent archives.
|
||||
|
||||
* `--wrap`=_symbol_:
|
||||
Make _symbol_ be resolved to `__wrap_`_symbol_. The original symbol can be
|
||||
resolved as `__real_`_symbol_. This option is typically used for wrapping an
|
||||
existing function.
|
||||
|
||||
* `-z cet-report`=[ `warning` | `error` | `none` ]:
|
||||
Intel Control-flow Enforcement Technology (CET) is a new x86 feature
|
||||
available since Tiger Lake which is released in 2020. It defines new
|
||||
instructions to harden security to protect programs from control hijacking
|
||||
attacks. You can tell the compiler to use the feature by specifying the
|
||||
`-fcf-protection` flag.
|
||||
|
||||
`-z cet-report` flag is used to make sure that all object files were
|
||||
compiled with a correct `-fcf-protection` flag. If `warning` or `error` are
|
||||
given, `mold` prints out a warning or an error message if an object file was
|
||||
not compiled with the compiler flag.
|
||||
|
||||
`mold` looks for `GNU_PROPERTY_X86_FEATURE_1_IBT` bit and
|
||||
`GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in `.note.gnu.property` section to
|
||||
determine whether or not an object file was compiled with `-fcf-protection`.
|
||||
|
||||
* `-z now`, `-z lazy`:
|
||||
By default, functions referring to other ELF modules are resolved by the
|
||||
dynamic linker when they are called for the first time. `-z now` marks an
|
||||
executable or a shared library file so that all dynamic symbols are resolved
|
||||
when a file is loaded to memory. `-z lazy` restores the default behavior.
|
||||
|
||||
* `-z origin`:
|
||||
Mark object requiring immediate `$ORIGIN` processing at runtime.
|
||||
|
||||
* `-z ibt`:
|
||||
Turn on `GNU_PROPERTY_X86_FEATURE_1_IBT` bit in `.note.gnu.property` section
|
||||
to indicate that the output uses IBT-enabled PLT. This option implies `-z
|
||||
ibtplt`.
|
||||
|
||||
* `-z ibtplt`:
|
||||
Generate Intel Branch Tracking (IBT)-enabled PLT which is the default on
|
||||
x86-64. This is the default.
|
||||
|
||||
* `-z execstack`, `-z noexecstack`:
|
||||
By default, the pages for the stack area (i.e. the pages where local
|
||||
variables reside) are not executable for security reasons. `-z execstack`
|
||||
makes it executable. `-z noexecstack` restores the default behavior.
|
||||
|
||||
* `-z keep-text-section-prefix`, `-z nokeep-text-section-prefix`:
|
||||
Keep `.text.hot`, `.text.unknown`, `.text.unlikely`, `.text.startup`, and
|
||||
`.text.exit` as separate sections in the final binary instead of merging
|
||||
them as `.text`.
|
||||
|
||||
* `-z rodynamic`:
|
||||
Make the `.dynamic` section read-only.
|
||||
|
||||
* `-z relro`, `-z norelro`:
|
||||
Some sections such as `.dynamic` have to be writable only during a module is
|
||||
being loaded to memory. Once the dynamic linker finishes its job, such
|
||||
sections won't be mutated by anyone. As a security mitigation, it is
|
||||
preferred to make such segments read-only during program execution.
|
||||
|
||||
`-z relro` puts such sections into a special segment called `relro`. The
|
||||
dynamic linker makes a relro segment read-only after it finishes its job.
|
||||
|
||||
By default, `mold` generates a relro segment. `-z norelro` disables the
|
||||
feature.
|
||||
|
||||
* `-z sectionheader`, `-z nosectionheader`:
|
||||
`-z nosectionheader` tell the linker to omit the section header.
|
||||
By default, the linker does not omit the section header.
|
||||
|
||||
* `-z separate-loadable-segments`, `-z separate-code`, `-z noseparate-code`:
|
||||
If one memory page contains multiple segments, the page protection bits are
|
||||
set in such a way that the needed attributes (writable or executable) are
|
||||
satisfied for all segments. This usually happens at a boundary of two
|
||||
segments with two different attributes.
|
||||
|
||||
`separate-loadable-segments` adds paddings between segments with different
|
||||
attributes so that they do not share the same page.
|
||||
|
||||
`separate-code` adds paddings only between executable and non-executable
|
||||
segments. This is the default.
|
||||
|
||||
`noseparate-code` does not add any paddings between segments.
|
||||
|
||||
* `-z defs`, `-z nodefs`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `-z shstk`:
|
||||
Enforce shadow stack by turning `GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in
|
||||
`.note.gnu.property` output section. Shadow stack is part of Intel
|
||||
Control-flow Enforcement Technology (CET), which is available since Tiger
|
||||
Lake (2020).
|
||||
|
||||
* `-z start_stop_visibility`=[ `hidden` | `protected` ]:
|
||||
If a section name is valid as a C identifier (i.e., it matches
|
||||
`/^[_a-zA-Z][_a-zA-Z0-9]*$/`), mold creates `__start_SECNAME` and
|
||||
`__stop_SECNAME` symbols to mark the beginning and end of the section,
|
||||
where `SECNAME` is the section name.
|
||||
|
||||
You can make these marker symbols visible from other ELF modules by passing
|
||||
`-z start_stop_visibility=protected`. Default is `hidden`.
|
||||
|
||||
* `-z text`, `-z notext`, `-z textoff`:
|
||||
`mold` by default reports an error if dynamic relocations are created in
|
||||
read-only sections. If `-z notext` or `-z textoff` are given, `mold` creates
|
||||
such dynamic relocations without reporting an error. `-z text` restores the
|
||||
default behavior.
|
||||
|
||||
* `-z max-page-size`=_number_:
|
||||
Some CPU ISAs support multiple memory page sizes. This option specifies the
|
||||
maximum page size that an output binary can run on. In general, binaries
|
||||
built for a larger page size can run on a system with a smaller page size,
|
||||
but not vice versa. The default value is 4 KiB for i386, x86-64, and RISC-V,
|
||||
and 64 KiB for ARM64.
|
||||
|
||||
* `-z nodefaultlib`:
|
||||
Make the dynamic loader ignore default search paths.
|
||||
|
||||
* `-z nodelete`:
|
||||
Mark DSO non-deletable at runtime.
|
||||
|
||||
* `-z nodlopen`:
|
||||
Mark DSO not available to dlopen(3). This option makes it possible for the
|
||||
linker to optimize thread-local variable accesses by rewriting instructions
|
||||
for some targets.
|
||||
|
||||
* `-z nodump`:
|
||||
Mark DSO not available to dldump(3).
|
||||
|
||||
* `-z nocopyreloc`:
|
||||
Do not create copy relocations.
|
||||
|
||||
* `-z initfirst`:
|
||||
Mark DSO to be initialized first at runtime.
|
||||
|
||||
* `-z interpose`:
|
||||
Mark object to interpose all DSOs but executable.
|
||||
|
||||
* `-(`, `-)`, `-EL`, `-O`_number_, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-copy-dt-needed-entries`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
|
||||
Ignored
|
||||
|
||||
## ENVIRONMENT VARIABLES
|
||||
|
||||
* `MOLD_JOBS`:
|
||||
If this variable is set to `1`, only one `mold` process will run at a time.
|
||||
If a new mold process is initiated while another is already active, the new
|
||||
process will wait until the active one completes before starting.
|
||||
|
||||
The primary reason for this environment variable is to minimize peak memory
|
||||
usage. Since mold is designed to operate with high parallelism, running
|
||||
multiple mold instances simultaneously may not be beneficial. If you execute
|
||||
N instances of mold concurrently, it could require N times the time and N
|
||||
times the memory. On the other hand, running them one after the other might
|
||||
still take N times longer, but the peak memory usage would be the same as
|
||||
running just a single instance.
|
||||
|
||||
If your build system invokes multiple linker processes simultaneously and
|
||||
some of them often get killed due to out-of-memory errors, you might
|
||||
consider setting this environment variable to `1` to see if it addresses the
|
||||
OOM issue.
|
||||
|
||||
Currently, any value other than `1` is silently ignored.
|
||||
|
||||
* `MOLD_DEBUG`:
|
||||
If this variable is set to a non-empty string, `mold` embeds its
|
||||
command-line options in the output file's `.comment` section.
|
||||
|
||||
* `MOLD_REPRO`:
|
||||
Setting this variable to a non-empty string has the same effect as passing
|
||||
the `--repro` option.
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
gold(1), ld(1), elf(5), ld.so(8)
|
||||
|
||||
## AUTHOR
|
||||
|
||||
Rui Ueyama <ruiu@cs.stanford.edu>
|
||||
|
||||
## BUGS
|
||||
|
||||
Report bugs to <https://github.com/rui314/mold/issues>.
|
||||
@@ -0,0 +1,651 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
//
|
||||
// You can distinguish Thumb functions from ARM functions by looking
|
||||
// at the least significant bit (LSB) of its "address". If LSB is 0,
|
||||
// it's ARM; otherwise, Thumb.
|
||||
//
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, `foo` is a function using Thumb instructions whose address
|
||||
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
|
||||
// aligned). Likewise, if a function pointer has value 0x2001, it
|
||||
// refers a Thumb function at 0x2000.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/BL--BLX--immediate-
|
||||
u32 sign = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ sign;
|
||||
u32 J2 = !I2 ^ sign;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (sign << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOVT
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
static const ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0x0000'0000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
// ARM and Thumb branch instructions can jump within ±16 MiB.
|
||||
static bool is_jump_reachable(i64 val) {
|
||||
return sign_extend(val, 24) == val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
i64 trampoline_idx = 0;
|
||||
|
||||
auto get_trampoline_addr = [&](u64 addr) {
|
||||
for (; trampoline_idx < output_section->thunks.size(); trampoline_idx++) {
|
||||
RangeExtensionThunk<E> &thunk = *output_section->thunks[trampoline_idx];
|
||||
i64 disp = output_section->shdr.sh_addr + thunk.offset - addr;
|
||||
if (is_jump_reachable(disp))
|
||||
return disp;
|
||||
}
|
||||
unreachable();
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto 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 sym.get_addr(ctx)
|
||||
#define A this->get_addend(rel)
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define T (sym.get_addr(ctx) & 1)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
auto get_thumb_thunk_addr = [&] {
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
assert(ref.thunk_idx != -1);
|
||||
u64 addr = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx);
|
||||
assert(is_jump_reachable(addr + A - P));
|
||||
return addr;
|
||||
};
|
||||
|
||||
auto get_arm_thunk_addr = [&] {
|
||||
return get_thumb_thunk_addr() + 4;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(val, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL: {
|
||||
// Just like THM_CALL, ARM_CALL relocation refers either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
|
||||
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T)
|
||||
*(ul32 *)loc = 0xfa00'0000 | (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
else
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
|
||||
// takes an immediate; it takes only a register. So if mode switch
|
||||
// is required, we jump to a linker-synthesized thunk which constructs
|
||||
// a branch destination in a register and branches to that address.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || T)
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP11: {
|
||||
assert(T);
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 19), 1 << 19);
|
||||
|
||||
// sign:J2:J1:imm6:imm11:'0'
|
||||
u32 sign = bit(val, 20);
|
||||
u32 J2 = bit(val, 19);
|
||||
u32 J1 = bit(val, 18);
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(ul16 *)loc &= 0b1111'1011'1100'0000;
|
||||
*(ul16 *)loc |= (sign << 10) | imm6;
|
||||
|
||||
*(ul16 *)(loc + 2) &= 0b1101'0000'0000'0000;
|
||||
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
} else {
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || !T)
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
}
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_mov_imm(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_PREL31: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | ((val) & 0x7fff'ffff);
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_mov_imm(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov_imm(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
if (A & 1)
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
|
||||
else
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
|
||||
} else {
|
||||
*(ul32 *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
|
||||
} else {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0xe320'f000;
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_trampoline_addr(P + 4), 4);
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else {
|
||||
// BL -> NOP.W
|
||||
*(ul32 *)loc = 0x8000'f3af;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef T
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : this->get_addend(rel))
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_TARGET1:
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31:
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_THM_JUMP19:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
case R_ARM_V4BX:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
static ul32 hdr[] = {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
};
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
const u8 entry[] = {
|
||||
// .thumb
|
||||
0xfc, 0x46, // mov ip, pc
|
||||
0x60, 0x47, // bx ip # jumps to the following `ldr` insn
|
||||
// .arm
|
||||
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, 2f
|
||||
0x0f, 0xc0, 0x8c, 0xe0, // 1: add ip, ip, pc
|
||||
0x1c, 0xff, 0x2f, 0xe1, // bx ip
|
||||
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
memcpy(buf, hdr, sizeof(hdr));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
|
||||
memcpy(loc, entry, sizeof(entry));
|
||||
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr) + i * sizeof(entry);
|
||||
*(ul32 *)(loc + 16) = S - P - 16;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// I don't know why only ARM uses the different mechanism, but it's
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function sorts .ARM.exidx records.
|
||||
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant bit is set in (2) but
|
||||
// not in (3). So we can distinguished them just by looking at a value.
|
||||
const u32 EXIDX_CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
ul32 addr;
|
||||
ul32 val;
|
||||
};
|
||||
|
||||
if (osec.shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
|
||||
Entry *ent = (Entry *)(ctx.buf + osec.shdr.sh_offset);
|
||||
i64 num_entries = osec.shdr.sh_size / sizeof(Entry);
|
||||
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by addresses, we first translate them so that the addresses
|
||||
// are relative to the beginning of the section.
|
||||
auto is_relative = [](u32 val) {
|
||||
return val != EXIDX_CANTUNWIND && !(val & 0x8000'0000);
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
|
||||
// Make addresses relative to themselves.
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
}
|
||||
|
||||
void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_arm_exidx_section");
|
||||
|
||||
auto find_exidx = [&]() -> OutputSection<E> * {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
OutputSection<E> *exidx = find_exidx();
|
||||
if (!exidx)
|
||||
return;
|
||||
|
||||
// Sort .ARM.exidx contents
|
||||
sort_exidx(ctx, *exidx);
|
||||
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (ctx.shdr) {
|
||||
auto find_text = [&]() -> OutputSection<E> * {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->name == ".text")
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (OutputSection<E> *text = find_text()) {
|
||||
exidx->shdr.sh_link = text->shndx;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,513 @@
|
||||
// This file contains ARM64-specific code. Being new, the ARM64's ELF
|
||||
// psABI doesn't have anything peculiar. ARM64 is a clean RISC
|
||||
// instruction set that supports PC-relative load/store instructions.
|
||||
//
|
||||
// Unlike ARM32, instructions length doesn't vary. All ARM64
|
||||
// instructions are 4 bytes long.
|
||||
//
|
||||
// Branch instructions used for function call can jump within ±128 MiB.
|
||||
// We need to create range extension thunks to support binaries whose
|
||||
// .text is larger than that.
|
||||
//
|
||||
// Unlike most other targets, the TLSDESC access model is used by default
|
||||
// for -fPIC to access thread-local variables instead of the less
|
||||
// efficient GD model. You can still enable GD but it needs the
|
||||
// -mtls-dialect=trad flag. Since GD is used rarely, we don't need to
|
||||
// implement GD → LE relaxation.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM64;
|
||||
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 32, 14);
|
||||
u32 lo = bits(val, 13, 12);
|
||||
*(ul32 *)buf &= 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf |= (lo << 29) | (hi << 5);
|
||||
}
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 20, 2);
|
||||
u32 lo = bits(val, 1, 0);
|
||||
*(ul32 *)buf &= 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf |= (lo << 29) | (hi << 5);
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x9000'0010, // adrp x16, .got.plt[2]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[2]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, .got.plt[n]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[n]
|
||||
0xd61f'0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(gotplt) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(got) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
*(ul64 *)loc = val;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto 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 sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(val, 31, 16) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(val, 47, 32) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
}
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
i64 val = S + A - P;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(ul32 *)loc |= (val >> 2) & 0x03ff'ffff;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(val, 20, 2) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
check(val, 0, 1 << 15);
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, 0, 1LL << 24);
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tp_addr);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
|
||||
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
|
||||
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
} else {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// blr x2 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
}
|
||||
break;
|
||||
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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto 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 << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_AARCH64_ABS32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
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:
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
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 (!relax_tlsdesc(ctx, sym))
|
||||
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_LD_PREL_LO19:
|
||||
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:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
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:
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const ul32 data[] = {
|
||||
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f'0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
|
||||
|
||||
u8 *loc = buf + i * E::thunk_size;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adrp(loc, page(S) - page(P));
|
||||
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,528 @@
|
||||
// i386 is similar to x86-64 but lacks PC-relative memory access
|
||||
// instructions. So it's not straightforward to support position-
|
||||
// independent code (PIC) on that target.
|
||||
//
|
||||
// If an object file is compiled with -fPIC, a function that needs to load
|
||||
// a value from memory first obtains its own address with the following
|
||||
// code
|
||||
//
|
||||
// call __x86.get_pc_thunk.bx
|
||||
//
|
||||
// where __x86.get_pc_thunk.bx is defined as
|
||||
//
|
||||
// __x86.get_pc_thunk.bx:
|
||||
// mov (%esp), %ebx # move the return address to %ebx
|
||||
// ret
|
||||
//
|
||||
// . With the function's own address (or, more precisely, the address
|
||||
// immediately after the call instruction), the function can compute an
|
||||
// absolute address of a variable with its address + link-time constant.
|
||||
//
|
||||
// Executing call-mov-ret isn't very cheap, and allocating one register to
|
||||
// store PC isn't cheap too, especially given that i386 has only 8
|
||||
// general-purpose registers. But that's the cost of PIC on i386. You need
|
||||
// to pay it when creating a .so and a position-independent executable.
|
||||
//
|
||||
// When a position-independent function calls another function, it sets
|
||||
// %ebx to the address of .got. Position-independent PLT entries use that
|
||||
// register to load values from .got.plt/.got.
|
||||
//
|
||||
// If we are creating a position-dependent executable (PDE), we can't
|
||||
// assume that %ebx is set to .got. For PDE, we need to create position-
|
||||
// dependent PLT entries which don't use %ebx.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = I386;
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0x51, // push %ecx
|
||||
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 7) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0x51, // push %ecx
|
||||
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_386_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_got32x(u8 *loc) {
|
||||
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
|
||||
if (loc[0] == 0x8b)
|
||||
return 0x8d00 | loc[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto 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 sym.get_addr(ctx)
|
||||
#define A this->get_addend(rel)
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
if (sym.has_got(ctx)) {
|
||||
*(ul32 *)loc = G + A;
|
||||
} else {
|
||||
u32 insn = relax_got32x(loc - 2);
|
||||
assert(insn);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
}
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_386_TLS_IE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $val, %eax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $val, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%eax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto 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 << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : this->get_addend(rel))
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
scan_rel(ctx, sym, rel, absrel_table);
|
||||
break;
|
||||
case R_386_32:
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_GOT32X: {
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_got32x(loc - 2);
|
||||
if (!do_relax)
|
||||
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:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsld(ctx))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
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
|
||||
@@ -1,10 +1,10 @@
|
||||
// This file contains code for the Motorola 68000 series microprocessors,
|
||||
// which is often abbreviated as m68k. Running a Unix-like system on a
|
||||
// m68k-based machine today is a retro-computing hobby activity, but the
|
||||
// processor was a popular choice to build Unix computers during '80s.
|
||||
// Early Sun workstations for example used m68k. Macintosh until 1994 were
|
||||
// based on m68k as well until they switched to PowerPC (and then to x86
|
||||
// and to ARM.)
|
||||
// m68k-based machine today is probably a retro-computing hobby activity,
|
||||
// but the processor was a popular choice to build Unix computers during
|
||||
// '80s. Early Sun workstations for example used m68k. Macintosh until
|
||||
// 1994 were based on m68k as well until they switched to PowerPC (and
|
||||
// then to x86 and to ARM.)
|
||||
//
|
||||
// From the linker's point of view, it is not hard to support m68k. It's
|
||||
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
|
||||
@@ -12,13 +12,11 @@
|
||||
// access instructions and therefore can support position-independent
|
||||
// code without too much hassle.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/m68k.pdf
|
||||
|
||||
#if MOLD_M68K
|
||||
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
namespace mold::elf {
|
||||
|
||||
using E = M68K;
|
||||
|
||||
@@ -54,17 +52,15 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
*(ub32 *)(buf + 4) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_68K_32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
@@ -80,6 +76,11 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
@@ -88,14 +89,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
@@ -118,8 +116,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
#define S sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
@@ -176,13 +181,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write8(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(ub32 *)loc = S + A - ctx.tls_begin - E::tls_dtp_offset;
|
||||
break;
|
||||
case R_68K_TLS_LDO16:
|
||||
write16s(S + A - ctx.dtp_addr);
|
||||
write16s(S + A - ctx.tls_begin - E::tls_dtp_offset);
|
||||
break;
|
||||
case R_68K_TLS_LDO8:
|
||||
write8s(S + A - ctx.dtp_addr);
|
||||
write8s(S + A - ctx.tls_begin - E::tls_dtp_offset);
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
@@ -205,6 +210,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,18 +225,23 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
@@ -238,33 +254,46 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
|
||||
#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<const 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_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_68K_16:
|
||||
case R_68K_8:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
scan_rel(ctx, sym, rel, absrel_table);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PC16:
|
||||
case R_68K_PC8:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
case R_68K_GOTPCREL16:
|
||||
@@ -295,22 +324,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_68K_TLS_IE8:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_32:
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
} // namespace mold::elf
|
||||
@@ -5,15 +5,14 @@
|
||||
//
|
||||
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
|
||||
// endianness. The most notable difference is, in ELFv1, a function
|
||||
// pointer doesn't directly refer to the entry point of a function but
|
||||
// instead refers to a data structure so-called "function descriptor".
|
||||
// pointer doesn't directly refer the entry point of a function but
|
||||
// instead refers a data structure so-called "function descriptor".
|
||||
//
|
||||
// The function descriptor is essentially a pair of a function entry point
|
||||
// address and a value that should be set to %r2 before calling that
|
||||
// function. There is also a third member for "the environment pointer for
|
||||
// languages such as Pascal and PL/1" according to the psABI, but it looks
|
||||
// like no one acutally uses it. In total, the function descriptor is 24
|
||||
// bytes long. Here is why we need it.
|
||||
// function. There is actually a third member, but we don't need to know
|
||||
// what that is. In total, the function descriptor is 24 bytes long. Here
|
||||
// is why we need it.
|
||||
//
|
||||
// PPC generally lacks PC-relative data access instructions. Position-
|
||||
// independent code sets GOT + 0x8000 to %r2 and access global variables
|
||||
@@ -26,8 +25,8 @@
|
||||
//
|
||||
// In this way, you can't call a function just by knowing the function's
|
||||
// entry point address. You also need to know a proper %r2 value for the
|
||||
// function. This is why a function pointer refers to a tuple of an
|
||||
// address and a %r2 value.
|
||||
// function. This is why a function pointer refers a tuple of an address
|
||||
// and a %r2 value.
|
||||
//
|
||||
// If a function call is made through PLT, PLT takes care of restoring %r2.
|
||||
// Therefore, the caller has to restore %r2 only for function calls
|
||||
@@ -40,27 +39,34 @@
|
||||
// few different addresses for different purposes. It may not only have an
|
||||
// entry point address but may also have PLT and/or GOT addresses.
|
||||
// In PPCV1, it may have an OPD address in addition to these. OPD address
|
||||
// is used for relocations that refers to the address of a function as a
|
||||
// is used for relocations that refers the address of a function as a
|
||||
// function pointer.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
|
||||
|
||||
#if MOLD_PPC64V1
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
namespace mold::elf {
|
||||
|
||||
using E = PPC64V1;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
|
||||
static constexpr ScanAction toc_table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
|
||||
};
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
@@ -70,59 +76,33 @@ static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ub32 insn[] = {
|
||||
static const ub32 insn[] = {
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0xe84b'0024, // ld r2,36(r11)
|
||||
0x7d88'03a6, // mtlr r12
|
||||
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
||||
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
||||
0x7d62'5a14, // add r11,r2,r11
|
||||
0xe98b'0000, // ld r12,0(r11)
|
||||
0xe84b'0008, // ld r2,8(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0010, // ld r11,16(r11)
|
||||
0x4e80'0420, // bctr
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 idx = sym.get_plt_idx(ctx);
|
||||
|
||||
// The PPC64 ELFv1 ABI requires PLT entries to be vary in size depending
|
||||
// on their indices. Unlike other targets, .got.plt is filled not by us
|
||||
// but by the loader, so we don't have a control over where the initial
|
||||
// call to the PLT entry jumps to. So we need to strictly follow the PLT
|
||||
// section layout as the loader expect it to be.
|
||||
if (idx < 0x8000) {
|
||||
constexpr ub32 insn[] = {
|
||||
0x3800'0000, // li r0, PLT_INDEX
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= idx;
|
||||
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
|
||||
} else {
|
||||
constexpr ub32 insn[] = {
|
||||
0x3c00'0000, // lis r0, PLT_INDEX@high
|
||||
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= high(idx);
|
||||
loc[1] |= lo(idx);
|
||||
loc[2] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 8) & 0x00ff'ffff;
|
||||
}
|
||||
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub32 *)(buf + 0) = 0x3800'0000 | sym.get_plt_idx(ctx); // li %r0, PLT_INDEX
|
||||
*(ub32 *)(buf + 4) = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
@@ -131,8 +111,8 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -156,6 +136,11 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
@@ -164,37 +149,58 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
#define S sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (sym.is_ifunc()) {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, S + A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(ub64 *)loc = S + A;
|
||||
} else {
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel, toc_table);
|
||||
}
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
apply_dyn_absrel(ctx, *ctx.TOC, rel, loc, ctx.TOC->value, A, P, dynrel,
|
||||
toc_table);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ub16 *)loc = ha(S + A - TOC);
|
||||
*(ub16 *)loc = ha(S + A - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ub16 *)loc = lo(S + A - TOC);
|
||||
*(ub16 *)loc = S + A - ctx.TOC->value;
|
||||
break;
|
||||
case R_PPC64_TOC16_DS:
|
||||
check(S + A - TOC, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
case R_PPC64_TOC16_DS: {
|
||||
i64 val = S + A - ctx.TOC->value;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= val & 0xfffc;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
*(ub16 *)loc |= (S + A - ctx.TOC->value) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) + A - P;
|
||||
if (sym.has_plt(ctx) || !is_int(val, 26))
|
||||
val = sym.get_thunk_addr(ctx, P) + A - P;
|
||||
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val) {
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
assert(ref.thunk_idx != -1);
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *output_section->thunks[ref.thunk_idx];
|
||||
val = thunk.get_addr(ref.sym_idx) + A - P;
|
||||
}
|
||||
|
||||
check(val, -(1 << 25), 1 << 25);
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
@@ -207,9 +213,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -217,60 +220,50 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC64_REL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - P);
|
||||
break;
|
||||
case R_PPC64_GOT16:
|
||||
*(ub16 *)loc = G - TOC;
|
||||
*(ub16 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
*(ub16 *)loc = ha(G + GOT - TOC);
|
||||
*(ub16 *)loc = ha(G + GOT - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_PLT16_HI:
|
||||
*(ub16 *)loc = hi(G + GOT - TOC);
|
||||
*(ub16 *)loc = hi(G + GOT - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO:
|
||||
*(ub16 *)loc = lo(G + GOT - TOC);
|
||||
*(ub16 *)loc = lo(G + GOT - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ub16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
||||
*(ub16 *)loc |= (G + GOT - ctx.TOC->value) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
*(ub16 *)loc = ha(sym.get_gottp_addr(ctx) - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ub16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
*(ub16 *)loc = ha(sym.get_tlsgd_addr(ctx) - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(ub16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
*(ub16 *)loc = sym.get_tlsgd_addr(ctx) - ctx.TOC->value;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ub16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
*(ub16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(ub16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
*(ub16 *)loc = ctx.got->get_tlsld_addr(ctx) - ctx.TOC->value;
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
|
||||
*(ub16 *)loc = ha(S + A - ctx.tls_begin - E::tls_dtp_offset);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ub16 *)loc = S + A - ctx.tls_begin - E::tls_dtp_offset;
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - ctx.tp_addr) & 0xfffc;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ub16 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - ctx.TOC->value) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TLS:
|
||||
@@ -278,8 +271,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TLSLD:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
Fatal(ctx) << *this << ": apply_reloc_alloc relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,22 +288,30 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto 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 << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
@@ -313,42 +320,60 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
else
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_PPC64_ADDR32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
case R_PPC64_ADDR32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_DTPREL64:
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
*(ub64 *)loc = S + A - ctx.tls_begin - E::tls_dtp_offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
|
||||
}
|
||||
|
||||
#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<const 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_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT | NEEDS_OPD);
|
||||
|
||||
// Any relocation except R_PPC64_REL24 is considered as an
|
||||
// address-taking relocation.
|
||||
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
sym.flags |= NEEDS_OPD;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (sym.is_ifunc())
|
||||
this->file.num_dynrel++;
|
||||
else
|
||||
scan_rel(ctx, sym, rel, toc_table);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
scan_rel(ctx, sym, rel, toc_table);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
@@ -356,7 +381,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_GOT16:
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
@@ -366,14 +390,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
case R_PPC64_TOC:
|
||||
case R_PPC64_REL32:
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
@@ -386,6 +402,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
@@ -394,30 +412,28 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is .plt.got, we save the current r2, read an
|
||||
// address of a function descriptor from .got, restore %r2 and jump
|
||||
// to the function.
|
||||
constexpr ub32 pltgot_thunk[] = {
|
||||
static const ub32 pltgot_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
// Load an address of a function descriptor
|
||||
0x3d82'0000, // addis %r12, %r2, foo@got@toc@ha
|
||||
0xe98c'0000, // ld %r12, foo@got@toc@lo(%r12)
|
||||
|
||||
// Restore the callee's %r2
|
||||
0xe84c'0008, // ld %r2, 8(%r12)
|
||||
|
||||
// Jump to the function
|
||||
0xe98c'0000, // ld %r12, 0(%r12)
|
||||
0x7d89'03a6, // mtctr %r12
|
||||
@@ -425,17 +441,14 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
// If the destination is .plt, read a function descriptor from .got.plt.
|
||||
constexpr ub32 plt_thunk[] = {
|
||||
static const ub32 plt_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
// Materialize an address of a function descriptor
|
||||
0x3d82'0000, // addis %r12, %r2, foo@gotplt@toc@ha
|
||||
0x398c'0000, // addi %r12, %r12, foo@gotplt@toc@lo
|
||||
|
||||
// Restore the callee's %r2
|
||||
0xe84c'0008, // ld %r2, 8(%r12)
|
||||
|
||||
// Jump to the function
|
||||
0xe98c'0000, // ld %r12, 0(%r12)
|
||||
0x7d89'03a6, // mtctr %r12
|
||||
@@ -444,7 +457,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to the function entry address.
|
||||
constexpr ub32 local_thunk[] = {
|
||||
static const ub32 local_thunk[] = {
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -458,61 +471,67 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
|
||||
if (sym.has_got(ctx)) {
|
||||
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
i64 val = sym.get_got_addr(ctx) - TOC;
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
memcpy(loc, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
i64 val = sym.get_got_addr(ctx) - ctx.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else if(sym.has_plt(ctx)) {
|
||||
memcpy(buf, plt_thunk, sizeof(plt_thunk));
|
||||
i64 val = sym.get_gotplt_addr(ctx) - TOC;
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
i64 val = sym.get_gotplt_addr(ctx) - ctx.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else {
|
||||
memcpy(buf, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) - TOC;
|
||||
*(ub32 *)buf |= higha(val);
|
||||
*(ub32 *)(buf + 4) |= lo(val);
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) - ctx.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
|
||||
if (isec && isec->name == ".opd")
|
||||
if (isec && isec->name() == ".opd")
|
||||
return isec.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ElfRel<E> *
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
|
||||
return (u64)r.r_offset;
|
||||
|
||||
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
|
||||
[](const ElfRel<E> &r, i64 offset) {
|
||||
return r.r_offset < offset;
|
||||
});
|
||||
if (it == rels.end() || it->r_offset != offset)
|
||||
|
||||
if (it == rels.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct OpdSymbol {
|
||||
u64 r_offset = 0;
|
||||
Symbol<E> *sym = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
|
||||
if (it == syms.end() || it->r_offset != offset)
|
||||
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, i64 offset) {
|
||||
auto it = std::lower_bound(syms.begin(), syms.end(), offset,
|
||||
[](const OpdSymbol &ent, i64 offset) {
|
||||
return ent.r_offset < offset;
|
||||
});
|
||||
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
return nullptr;
|
||||
return it->sym;
|
||||
}
|
||||
@@ -525,10 +544,10 @@ get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
// However, in reality, .opd isn't a normal input section. It needs many
|
||||
// special treatments as follows:
|
||||
//
|
||||
// 1. A function symbol refers to not a .text but an .opd. Its address
|
||||
// works fine for address-taking relocations such as R_PPC64_ADDR64.
|
||||
// However, R_PPC64_REL24 (which is used for branch instruction) needs
|
||||
// a function's real address instead of the function's .opd address.
|
||||
// 1. A function symbol refers not a .text but an .opd. Its address works
|
||||
// fine for address-taking relocations such as R_PPC64_ADDR64. However,
|
||||
// R_PPC64_REL24 (which is used for branch instruction) needs a
|
||||
// function's real address instead of the function's .opd address.
|
||||
// We need to read .opd contents to find out a function entry point
|
||||
// address to apply R_PPC64_REL24.
|
||||
//
|
||||
@@ -536,9 +555,9 @@ get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
// are taken. Just copying input .opd sections to an output would
|
||||
// produces lots of dead .opd entries.
|
||||
//
|
||||
// 3. In this design, all function symbols refer to an .opd section, and
|
||||
// that doesn't work well with graph traversal optimizations such as
|
||||
// garbage collection or identical comdat folding. For example, garbage
|
||||
// 3. In this design, all function symbols refer an .opd section, and that
|
||||
// doesn't work well with graph traversal optimizations such as garbage
|
||||
// collection or identical comdat folding. For example, garbage
|
||||
// collector would mark an .opd alive which in turn mark all functions
|
||||
// thatare referenced by .opd as alive, effectively keeping all
|
||||
// functions as alive.
|
||||
@@ -551,11 +570,11 @@ get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
//
|
||||
// So, in this function, we undo what the compiler did to .opd. We remove
|
||||
// function symbols from .opd and reattach them to their function entry
|
||||
// points. We also rewrite relocations that directly refer to an input
|
||||
// .opd section so that they refer to function symbols instead. We then
|
||||
// mark input .opd sections as dead.
|
||||
// points. We also rewrite relocations that directly refer an input .opd
|
||||
// section so that they refer function symbols instead. We then mark input
|
||||
// .opd sections as dead.
|
||||
//
|
||||
// After this function, we mark symbols with the NEEDS_PPC_OPD flag if the
|
||||
// After this function, we mark symbols with the NEEDS_OPD flag if the
|
||||
// symbol needs an .opd entry. We then create an output .opd just like we
|
||||
// do for .plt or .got.
|
||||
void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
@@ -578,11 +597,11 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
ElfRel<E> *rel = get_relocation_at(ctx, *opd, sym->value);
|
||||
if (!rel)
|
||||
Fatal(ctx) << *file << ": cannot find a relocation in .opd for "
|
||||
<< *sym << " at offset 0x" << std::hex << (u64)sym->value;
|
||||
<< *sym << " at offset 0x" << std::hex << (u32)sym->value;
|
||||
|
||||
Symbol<E> *sym2 = file->symbols[rel->r_sym];
|
||||
if (sym2->get_type() != STT_SECTION)
|
||||
Fatal(ctx) << *file << ": bad relocation in .opd referring to " << *sym2;
|
||||
Fatal(ctx) << *file << ": bad relocation in .opd referring " << *sym2;
|
||||
|
||||
opd_syms.push_back({sym->value, sym});
|
||||
|
||||
@@ -591,9 +610,11 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// Sort symbols so that get_opd_sym_at() can do binary search.
|
||||
ranges::stable_sort(opd_syms, {}, &OpdSymbol::r_offset);
|
||||
sort(opd_syms, [](const OpdSymbol &a, const OpdSymbol &b) {
|
||||
return a.r_offset < b.r_offset;
|
||||
});
|
||||
|
||||
// Rewrite relocations so that they directly refer to .opd.
|
||||
// Rewrite relocations directly referring .opd.
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (!isec || !isec->is_alive || isec.get() == opd)
|
||||
continue;
|
||||
@@ -603,10 +624,10 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
if (sym.get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
Symbol<E> *real_sym = get_opd_sym_at(opd_syms, r.r_addend);
|
||||
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
|
||||
if (!real_sym)
|
||||
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
|
||||
<< " at offset 0x" << std::hex << (u64)r.r_addend;
|
||||
<< " at offset 0x" << std::hex << (u32)r.r_addend;
|
||||
|
||||
r.r_sym = real_sym->sym_idx;
|
||||
r.r_addend = 0;
|
||||
@@ -616,25 +637,26 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// When a function is exported, the dynamic symbol for the function should
|
||||
// refers to the function's .opd entry. This function marks such symbols
|
||||
// with NEEDS_PPC_OPD.
|
||||
// refers the function's .opd entry. This function marks such symbols with
|
||||
// NEEDS_OPD.
|
||||
void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file && sym->is_exported)
|
||||
if (u32 ty = sym->get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
|
||||
sym->flags |= NEEDS_PPC_OPD;
|
||||
sym->flags |= NEEDS_OPD;
|
||||
});
|
||||
|
||||
// Functions referenced by the ELF header also have to have .opd entries.
|
||||
if (!ctx.arg.entry->is_imported)
|
||||
ctx.arg.entry->flags |= NEEDS_PPC_OPD;
|
||||
auto mark = [&](std::string_view name) {
|
||||
if (!name.empty())
|
||||
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
|
||||
sym.flags |= NEEDS_OPD;
|
||||
};
|
||||
|
||||
if (!ctx.arg.init->is_imported)
|
||||
ctx.arg.init->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
if (!ctx.arg.fini->is_imported)
|
||||
ctx.arg.fini->flags |= NEEDS_PPC_OPD;
|
||||
mark(ctx.arg.entry);
|
||||
mark(ctx.arg.init);
|
||||
mark(ctx.arg.fini);
|
||||
}
|
||||
|
||||
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
@@ -643,31 +665,14 @@ void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
this->shdr.sh_size += ENTRY_SIZE;
|
||||
}
|
||||
|
||||
std::vector<ElfRel<E>> PPC64OpdSection::collect_dynrels(Context<E> &ctx) const {
|
||||
if (!ctx.arg.pic)
|
||||
return {};
|
||||
|
||||
std::vector<ElfRel<E>> rels;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 loc = sym->get_opd_addr(ctx);
|
||||
rels.emplace_back(loc, E::R_RELATIVE, 0, sym->get_addr(ctx, NO_PLT | NO_OPD));
|
||||
rels.emplace_back(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
|
||||
}
|
||||
return rels;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
*buf++ = addr;
|
||||
*buf++ = ctx.extra.TOC->value;
|
||||
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
*buf++ = ctx.TOC->value;
|
||||
*buf++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,477 @@
|
||||
// This file implements the PowerPC ELFv2 ABI which was standardized in
|
||||
// 2014. Modern little-endian PowerPC systems are based on this ABI.
|
||||
// The ABI is often referred to as "ppc64le". This shouldn't be confused
|
||||
// with "ppc64" which refers the original, big-endian PowerPC systems.
|
||||
//
|
||||
// PPC64 is a bit tricky to support because PC-relative load/store
|
||||
// instructions are generally not available. Therefore, it's not easy
|
||||
// for position-independent code to load a value from, for example,
|
||||
// .got, as we can't do that with [PC + the offset to the .got entry].
|
||||
//
|
||||
// We can get the program counter by the following four instructions
|
||||
//
|
||||
// mflr r1 // save the current link register to r1
|
||||
// bl .+4 // branch to the next instruction as if it were a function
|
||||
// mflr r0 // copy the return address to r0
|
||||
// mtlr r1 // restore the original link register value
|
||||
//
|
||||
// , but that's too expensive to do if we do this for each load/store.
|
||||
//
|
||||
// As a workaround, most functions are compiled in such a way that r2 is
|
||||
// assumed to always contain the address of .got + 0x8000. With this, we
|
||||
// can for example load the first entry of .got with a single instruction
|
||||
// `lw r0, -0x8000(r2)`. r2 is called the TOC pointer.
|
||||
//
|
||||
// There's only one .got for each ELF module. Therefore, if a callee is in
|
||||
// the same ELF module, r2 doesn't have to be recomputed. Most function
|
||||
// calls are usually within the same ELF module, so this mechanism is
|
||||
// efficient.
|
||||
//
|
||||
// In PPC64, a function usually have two entry points, global and local.
|
||||
// The global entry point is usually 8 bytes precedes the local entry
|
||||
// point. In between is the following instructions:
|
||||
//
|
||||
// addis r2, r12, .TOC.@ha
|
||||
// addi r2, r2, .TOC.@lo + 4;
|
||||
//
|
||||
// The global entry point assumes that the address of itself is in r12,
|
||||
// and it computes its own TOC pointer from r12. It's easy to do so for
|
||||
// the callee because the offset between its .got + 0x8000 and the
|
||||
// function is known at link-time. The above code sequence then falls
|
||||
// through to the local entry point that assumes r2 is .got + 0x8000.
|
||||
//
|
||||
// So, if a callee's TOC pointer is different from the current one
|
||||
// (e.g. calling a function in another .so), we first load the callee's
|
||||
// address to r12 (e.g. from .got.plt with a r2-relative load) and branch
|
||||
// to that address. Then the callee computes its own TOC pointer using
|
||||
// r12.
|
||||
//
|
||||
// Note on section names: the PPC64 psABI uses a weird naming convention
|
||||
// which calls .got.plt .plt. We ignored that part because it's just
|
||||
// confusing. Since the runtime only cares about segments, we should be
|
||||
// able to name sections whatever we want.
|
||||
//
|
||||
// https://openpowerfoundation.org/specifications/64bitelfabi/
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = PPC64V2;
|
||||
|
||||
// As a special case, we do not create copy relocations nor canonical
|
||||
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
|
||||
// GOT-like section, and no user-generated code directly uses values
|
||||
// in it.
|
||||
static constexpr ScanAction toc_table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
|
||||
};
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
// reach. Thunks read addresses from .got.plt and jump there.
|
||||
// Therefore, once PLT symbols are resolved and final addresses are
|
||||
// written to .got.plt, thunks just skip .plt and directly jump to the
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
// Get PC
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0x7c08'03a6, // mtlr r0
|
||||
// Compute the PLT entry index
|
||||
0xe80b'002c, // ld r0, 44(r11)
|
||||
0x7d8b'6050, // subf r12, r11, r12
|
||||
0x7d60'5a14, // add r11, r0, r11
|
||||
0x380c'ffcc, // addi r0, r12, -52
|
||||
0x7800'f082, // rldicl r0, r0, 62, 2
|
||||
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
|
||||
0xe98b'0000, // ld r12, 0(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0008, // ld r11, 8(r11)
|
||||
0x4e80'0420, // bctr
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul64 *)(buf + 52) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
// bl plt0
|
||||
*(ul32 *)buf = 0x4b00'0000;
|
||||
*(ul32 *)buf |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx)) & 0x00ff'ffff;
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
// directly read GOT entries and jump there.
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
*(ul64 *)loc = val;
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
|
||||
i64 val = sym.esym().ppc_local_entry;
|
||||
if (val == 0 || val == 1)
|
||||
return 0;
|
||||
if (val == 7)
|
||||
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
|
||||
return 1 << val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto 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 sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel, toc_table);
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel, dyn_absrel_table);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ul16 *)loc = S + A - ctx.TOC->value;
|
||||
break;
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - ctx.TOC->value) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = S + A - P + get_local_entry_offset(ctx, sym);
|
||||
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val) {
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
assert(ref.thunk_idx != -1);
|
||||
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
|
||||
}
|
||||
|
||||
check(val, -(1 << 25), 1 << 25);
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
// If a callee is an external function, PLT saves %r2 to the
|
||||
// caller's r2 save slot. We need to restore it after function
|
||||
// return. To do so, there's usually a NOP as a placeholder
|
||||
// after a BL. 0x6000'0000 is a NOP.
|
||||
if (sym.has_plt(ctx) && *(ul32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC64_REL16_LO:
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
*(ul16 *)loc = ha(G + GOT - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_PLT16_HI:
|
||||
*(ul16 *)loc = hi(G + GOT - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO:
|
||||
*(ul16 *)loc = lo(G + GOT - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ul16 *)loc |= (G + GOT - ctx.TOC->value) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(ul16 *)loc = sym.get_tlsgd_addr(ctx) - ctx.TOC->value;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - ctx.TOC->value);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(ul16 *)loc = ctx.got->get_tlsld_addr(ctx) - ctx.TOC->value;
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tls_begin - E::tls_dtp_offset);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ul16 *)loc = S + A - ctx.tls_begin - E::tls_dtp_offset;
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - ctx.TOC->value) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": apply_reloc_alloc relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto 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 << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_PPC64_ADDR32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_DTPREL64:
|
||||
*(ul64 *)loc = S + A - ctx.tls_begin - E::tls_dtp_offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
|
||||
}
|
||||
|
||||
#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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
scan_rel(ctx, sym, rel, toc_table);
|
||||
else
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_REL16_HA:
|
||||
case R_PPC64_REL16_LO:
|
||||
case R_PPC64_PLT16_HI:
|
||||
case R_PPC64_PLT16_LO:
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is PLT, we read an address from .got.plt or .got
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
// Save r2 to the r2 save slot reserved in the caller's stack frame
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
// Jump to a PLT entry
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
// Jump to a local entry point
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
i64 val = got - ctx.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx) + get_local_entry_offset(ctx, sym) -
|
||||
ctx.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,964 @@
|
||||
// RISC-V is a clean RISC ISA. It supports PC-relative load/store for
|
||||
// position-independent code. Its 32-bit and 64-bit ISAs are almost
|
||||
// identical. That is, you can think RV32 as a RV64 without 64-bit
|
||||
// operations. In this file, we support both RV64 and RV32.
|
||||
//
|
||||
// RISC-V is essentially little-endian, but the big-endian version is
|
||||
// available as an extension. GCC supports `-mbig-endian` to generate
|
||||
// big-endian code. Even in big-endian mode, machine instructions are
|
||||
// defined to be encoded in little-endian, though. Only the behavior of
|
||||
// load/store instructions are different between LE RISC-V and BE RISC-V.
|
||||
//
|
||||
// From the linker's point of view, the RISC-V's psABI is unique because
|
||||
// sections in input object files can be shrunk while being copied to the
|
||||
// output file. That is contrary to other psABIs in which sections are an
|
||||
// atomic unit of copying. Let me explain it in more details.
|
||||
//
|
||||
// Since RISC-V instructions are 16-bit or 32-bit long, there's no way to
|
||||
// embed a very large immediate into a branch instruction. In fact, JAL
|
||||
// (jump and link) instruction can jump to only within PC ± 1 MiB because
|
||||
// its immediate is only 21 bits long. If the destination is out of its
|
||||
// reach, we need to use two instructions instead; the first instruction
|
||||
// being AUIPC which sets upper 20 bits to a register and the second being
|
||||
// JALR with a 12-bit immediate and the register. Combined, they specify a
|
||||
// 32 bits displacement.
|
||||
//
|
||||
// Other RISC ISAs have the same limitation, and they solved the problem by
|
||||
// letting the linker create so-called "range extension thunks". It works as
|
||||
// follows: the compiler optimistically emits single jump instructions for
|
||||
// function calls. If the linker finds that a branch target is out of reach,
|
||||
// it emits a small piece of machine code near the branch instruction and
|
||||
// redirect the branch to the linker-synthesized code. The code constructs a
|
||||
// full 32-bit address in a register and jump to the destination. That
|
||||
// linker-synthesized code is called "range extension thunks" or just
|
||||
// "thunks".
|
||||
//
|
||||
// The RISC-V psABI is unique that it works the other way around. That is,
|
||||
// for RISC-V, the compiler always emits two instructions (AUIPC + JAL) for
|
||||
// function calls. If the linker finds the destination is reachable with a
|
||||
// single instruction, it replaces the two instructions with the one and
|
||||
// shrink the section size by one instruction length, instead of filling the
|
||||
// gap with a nop.
|
||||
//
|
||||
// With the presence of this relaxation, sections can no longer be
|
||||
// considered as an atomic unit. If we delete 4 bytes from the middle of a
|
||||
// section, all contents after that point needs to be shifted by 4. Symbol
|
||||
// values and relocation offsets have to be adjusted accordingly if they
|
||||
// refer past the deleted bytes.
|
||||
//
|
||||
// In mold, we use `r_deltas` to memorize how many bytes have be adjusted
|
||||
// for relocations. For symbols, we directly mutate their `value` member.
|
||||
//
|
||||
// RISC-V object files tend to have way more relocations than those for
|
||||
// other targets. This is because all branches, including ones that jump
|
||||
// within the same section, are explicitly expressed with relocations.
|
||||
// Here is why we need them: all control-flow statements such as `if` or
|
||||
// `for` are implemented using branch instructions. For other targets, the
|
||||
// compiler doesn't emit relocations for such branches because they know
|
||||
// at compile-time exactly how many bytes has to be skipped. That's not
|
||||
// true to RISC-V because the linker may delete bytes between a branch and
|
||||
// its destination. Therefore, all branches including in-section ones have
|
||||
// to be explicitly expressed with relocations.
|
||||
//
|
||||
// Note that this mechanism only shrink sections and never enlarge, as
|
||||
// the compiler always emits the longest instruction sequence. This
|
||||
// makes the linker implementation a bit simpler because we don't need
|
||||
// to worry about oscillation.
|
||||
//
|
||||
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static u32 itype(u32 val) {
|
||||
return val << 20;
|
||||
}
|
||||
|
||||
static u32 stype(u32 val) {
|
||||
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
|
||||
}
|
||||
|
||||
static u32 btype(u32 val) {
|
||||
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
|
||||
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
|
||||
}
|
||||
|
||||
static u32 utype(u32 val) {
|
||||
// U-type instructions are used in combination with I-type
|
||||
// instructions. U-type insn sets an immediate to the upper 20-bits
|
||||
// of a register. I-type insn sign-extends a 12-bits immediate and
|
||||
// adds it to a register value to construct a complete value. 0x800
|
||||
// is added here to compensate for the sign-extension.
|
||||
return (val + 0x800) & 0xffff'f000;
|
||||
}
|
||||
|
||||
static u32 jtype(u32 val) {
|
||||
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 cbtype(u32 val) {
|
||||
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static u32 cjtype(u32 val) {
|
||||
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
|
||||
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
|
||||
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_itype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
|
||||
*(ul32 *)loc |= itype(val);
|
||||
}
|
||||
|
||||
static void write_stype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= stype(val);
|
||||
}
|
||||
|
||||
static void write_btype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= btype(val);
|
||||
}
|
||||
|
||||
static void write_utype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
|
||||
*(ul32 *)loc |= utype(val);
|
||||
}
|
||||
|
||||
static void write_jtype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
|
||||
*(ul32 *)loc |= jtype(val);
|
||||
}
|
||||
|
||||
static void write_cbtype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'000'111'00000'11;
|
||||
*(ul16 *)loc |= cbtype(val);
|
||||
}
|
||||
|
||||
static void write_cjtype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'00000000000'11;
|
||||
*(ul16 *)loc |= cjtype(val);
|
||||
}
|
||||
|
||||
// Returns the rd register of an R/I/U/J-type instruction.
|
||||
static u32 get_rd(u32 val) {
|
||||
return bits(val, 11, 7);
|
||||
}
|
||||
|
||||
static void set_rs1(u8 *loc, u32 rs1) {
|
||||
assert(rs1 < 32);
|
||||
*(ul32 *)loc &= 0b111111'11111'00000'111'11111'1111111;
|
||||
*(ul32 *)loc |= rs1 << 15;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn_64[] = {
|
||||
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
0x0003'be03, // ld t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
|
||||
0xfd43'0313, // addi t1, t1, -44 # .plt entry
|
||||
0x0003'8293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
|
||||
0x0013'5313, // srli t1, t1, 1 # .plt entry offset
|
||||
0x0082'b283, // ld t0, 8(t0) # link map
|
||||
0x000e'0067, // jr t3
|
||||
};
|
||||
|
||||
static const ul32 insn_32[] = {
|
||||
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
0x0003'ae03, // lw t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
|
||||
0xfd43'0313, // addi t1, t1, -44 # .plt entry
|
||||
0x0003'8293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
|
||||
0x0023'5313, // srli t1, t1, 2 # .plt entry offset
|
||||
0x0042'a283, // lw t0, 4(t0) # link map
|
||||
0x000e'0067, // jr t3
|
||||
};
|
||||
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, insn_64, sizeof(insn_64));
|
||||
else
|
||||
memcpy(buf, insn_32, sizeof(insn_32));
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 8, gotplt - plt);
|
||||
write_itype(buf + 16, gotplt - plt);
|
||||
}
|
||||
|
||||
static const ul32 plt_entry_64[] = {
|
||||
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e'3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e'0367, // jalr t1, t3
|
||||
0x0000'0013, // nop
|
||||
};
|
||||
|
||||
static const ul32 plt_entry_32[] = {
|
||||
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e'2e03, // lw t3, %pcrel_lo(1b)(t3)
|
||||
0x000e'0367, // jalr t1, t3
|
||||
0x0000'0013, // nop
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
|
||||
else
|
||||
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 4, gotplt - plt);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
|
||||
else
|
||||
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
write_utype(buf, got - plt);
|
||||
write_itype(buf + 4, got - plt);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(U32<E> *)loc += val;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= val;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(U16<E> *)loc -= val;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(U32<E> *)loc -= val;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | (val & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = val;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(U16<E> *)loc = val;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
auto get_r_delta = [&](i64 idx) {
|
||||
return extra.r_deltas.empty() ? 0 : extra.r_deltas[idx];
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_RISCV_RELAX)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
i64 r_offset = rel.r_offset - get_r_delta(i);
|
||||
i64 removed_bytes = get_r_delta(i + 1) - get_r_delta(i);
|
||||
u8 *loc = base + r_offset;
|
||||
|
||||
auto 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 sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
if constexpr (E::is_64)
|
||||
*(U32<E> *)loc = S + A;
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
assert(E::is_64);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_RISCV_BRANCH: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 12), 1 << 12);
|
||||
write_btype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_JAL: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 20), 1 << 20);
|
||||
write_jtype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
|
||||
|
||||
if (removed_bytes == 4) {
|
||||
// auipc + jalr -> jal
|
||||
*(ul32 *)loc = (rd << 7) | 0b1101111;
|
||||
write_jtype(loc, S + A - P);
|
||||
} else if (removed_bytes == 6 && rd == 0) {
|
||||
// auipc + jalr -> c.j
|
||||
*(ul16 *)loc = 0b101'00000000000'01;
|
||||
write_cjtype(loc, S + A - P);
|
||||
} else if (removed_bytes == 6 && rd == 1) {
|
||||
// auipc + jalr -> c.jal
|
||||
assert(!E::is_64);
|
||||
*(ul16 *)loc = 0b001'00000000000'01;
|
||||
write_cjtype(loc, S + A - P);
|
||||
} else {
|
||||
assert(removed_bytes == 0);
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, val);
|
||||
write_itype(loc + 4, val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
*(ul32 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
*(ul32 *)loc = 0;
|
||||
} else {
|
||||
*(ul32 *)loc = S + A - P;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_HI20: {
|
||||
i64 val = S + A;
|
||||
if (removed_bytes == 0) {
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, val);
|
||||
} else {
|
||||
assert(removed_bytes == 4);
|
||||
assert(sign_extend(val, 11) == val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S: {
|
||||
i64 val = S + A;
|
||||
if (rel.r_type == R_RISCV_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
|
||||
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
|
||||
// accessible relative to the zero register. If the upper 20 bits
|
||||
// are all zero, the corresponding LUI might have been removed.
|
||||
if (sign_extend(val, 11) == val)
|
||||
set_rs1(loc, 0);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TPREL_HI20:
|
||||
assert(removed_bytes == 0 || removed_bytes == 4);
|
||||
if (removed_bytes == 0)
|
||||
write_utype(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_RISCV_TPREL_ADD:
|
||||
break;
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
if (rel.r_type == R_RISCV_TPREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
|
||||
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(tp)` if the address is
|
||||
// directly accessible using tp. tp is x4.
|
||||
if (sign_extend(val, 11) == val)
|
||||
set_rs1(loc, 4);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_ADD8:
|
||||
loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(U16<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(U32<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(U64<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(U16<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(U32<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(U64<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_ALIGN: {
|
||||
// A R_RISCV_ALIGN is followed by a NOP sequence. We need to remove
|
||||
// zero or more bytes so that the instruction after R_RISCV_ALIGN is
|
||||
// aligned to a given alignment boundary.
|
||||
//
|
||||
// We need to guarantee that the NOP sequence is valid after byte
|
||||
// removal (e.g. we can't remove the first 2 bytes of a 4-byte NOP).
|
||||
// For the sake of simplicity, we always rewrite the entire NOP sequence.
|
||||
i64 padding_bytes = rel.r_addend - removed_bytes;
|
||||
assert((padding_bytes & 1) == 0);
|
||||
|
||||
i64 i = 0;
|
||||
for (; i <= padding_bytes - 4; i += 4)
|
||||
*(ul32 *)(loc + i) = 0x0000'0013; // nop
|
||||
if (i < padding_bytes)
|
||||
*(ul16 *)(loc + i) = 0x0001; // c.nop
|
||||
break;
|
||||
}
|
||||
case R_RISCV_RVC_BRANCH: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 8), 1 << 8);
|
||||
write_cbtype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_RVC_JUMP: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 11), 1 << 11);
|
||||
write_cjtype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(U16<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
// These relocations are handled in the next loop.
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
|
||||
// Handle PC-relative LO12 relocations. In the above loop, pcrel HI20
|
||||
// relocations overwrote instructions with full 32-bit values to allow
|
||||
// their corresponding pcrel LO12 relocations to read their values.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S: {
|
||||
Symbol<E> &sym = *file.symbols[rels[i].r_sym];
|
||||
assert(sym.get_input_section() == this);
|
||||
|
||||
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
|
||||
u32 val = *(ul32 *)(base + sym.value);
|
||||
|
||||
if (rels[i].r_type == R_RISCV_PCREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original instructions pcrel HI20 relocations overwrote.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_GOT_HI20:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
case R_RISCV_TLS_GD_HI20: {
|
||||
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
|
||||
u32 val = *(ul32 *)loc;
|
||||
memcpy(loc, contents.data() + rels[i].r_offset, 4);
|
||||
write_utype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(U16<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(U32<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(U64<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(U16<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(U32<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(U64<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(U16<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// If a section is not relaxed, we can copy it as a one big chunk.
|
||||
if (extra.r_deltas.empty()) {
|
||||
uncompress_to(ctx, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory-allocated sections may be relaxed, so copy each segment
|
||||
// individually.
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 pos = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
i64 delta = extra.r_deltas[i + 1] - extra.r_deltas[i];
|
||||
if (delta == 0)
|
||||
continue;
|
||||
assert(delta > 0);
|
||||
|
||||
const ElfRel<E> &r = rels[i];
|
||||
memcpy(buf, contents.data() + pos, r.r_offset - pos);
|
||||
buf += r.r_offset - pos;
|
||||
pos = r.r_offset + delta;
|
||||
}
|
||||
|
||||
memcpy(buf, contents.data() + pos, contents.size() - pos);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
if constexpr (E::is_64)
|
||||
scan_rel(ctx, sym, rel, absrel_table);
|
||||
else
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_RISCV_HI20:
|
||||
scan_rel(ctx, sym, rel, absrel_table);
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if constexpr (!E::is_64)
|
||||
Fatal(ctx) << *this << ": R_RISCV_64 cannot be used on RV32";
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_RISCV_GOT_HI20:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
ctx.has_gottp_rel = true;
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
case R_RISCV_ADD8:
|
||||
case R_RISCV_ADD16:
|
||||
case R_RISCV_ADD32:
|
||||
case R_RISCV_ADD64:
|
||||
case R_RISCV_SUB8:
|
||||
case R_RISCV_SUB16:
|
||||
case R_RISCV_SUB32:
|
||||
case R_RISCV_SUB64:
|
||||
case R_RISCV_ALIGN:
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
case R_RISCV_SET8:
|
||||
case R_RISCV_SET16:
|
||||
case R_RISCV_SET32:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
|
||||
(isec->shdr().sh_flags & SHF_EXECINSTR);
|
||||
}
|
||||
|
||||
// Returns the distance between a relocated place and a symbol.
|
||||
template <typename E>
|
||||
static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We handle absolute symbols as if they were infinitely far away
|
||||
// because `shrink_section` 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 INT32_MAX;
|
||||
|
||||
// Likewise, relocations against weak undefined symbols won't be relaxed.
|
||||
if (sym.esym().is_undef_weak())
|
||||
return INT32_MAX;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
return S + A - P;
|
||||
}
|
||||
|
||||
// Scan relocations to shrink sections.
|
||||
template <typename E>
|
||||
static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
isec.extra.r_deltas.resize(rels.size() + 1);
|
||||
|
||||
i64 delta = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &r = rels[i];
|
||||
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
|
||||
isec.extra.r_deltas[i] = delta;
|
||||
|
||||
// Handling R_RISCV_ALIGN is mandatory.
|
||||
//
|
||||
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate some
|
||||
// or all of the instructions so that the instruction that immediately
|
||||
// follows the NOPs is aligned to a specified alignment boundary.
|
||||
if (r.r_type == R_RISCV_ALIGN) {
|
||||
// The total bytes of NOPs is stored to r_addend, so the next
|
||||
// instruction is r_addend away.
|
||||
u64 loc = isec.get_addr() + r.r_offset - delta;
|
||||
u64 next_loc = loc + r.r_addend;
|
||||
u64 alignment = bit_ceil(r.r_addend + 1);
|
||||
assert(alignment <= (1 << isec.p2align));
|
||||
delta += next_loc - align_to(loc, alignment);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handling other relocations is optional.
|
||||
if (!ctx.arg.relax || i == rels.size() - 1 ||
|
||||
rels[i + 1].r_type != R_RISCV_RELAX)
|
||||
continue;
|
||||
|
||||
// Linker-synthesized symbols haven't been assigned their final
|
||||
// values when we are shrinking sections because actual values can
|
||||
// be computed only after we fix the file layout. Therefore, we
|
||||
// assume that relocations against such symbols are always
|
||||
// non-relaxable.
|
||||
if (sym.file == ctx.internal_obj)
|
||||
continue;
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
// These relocations refer an AUIPC + JALR instruction pair to
|
||||
// allow to jump to anywhere in PC ± 2 GiB. If the jump target is
|
||||
// close enough to PC, we can use C.J, C.JAL or JAL instead.
|
||||
i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
if (dist & 1)
|
||||
break;
|
||||
|
||||
std::string_view contents = isec.contents;
|
||||
i64 rd = get_rd(*(ul32 *)(contents.data() + r.r_offset + 4));
|
||||
|
||||
if (rd == 0 && sign_extend(dist, 11) == dist && use_rvc) {
|
||||
// If rd is x0 and the jump target is within ±2 KiB, we can use
|
||||
// C.J, saving 6 bytes.
|
||||
delta += 6;
|
||||
} else if (rd == 1 && sign_extend(dist, 11) == dist && use_rvc && !E::is_64) {
|
||||
// If rd is x1 and the jump target is within ±2 KiB, we can use
|
||||
// C.JAL. This is RV32 only because C.JAL is RV32-only instruction.
|
||||
delta += 6;
|
||||
} else if (sign_extend(dist, 20) == dist) {
|
||||
// If the jump target is within ±1 MiB, we can use JAL.
|
||||
delta += 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_HI20: {
|
||||
// If the upper 20 bits are all zero, we can remove LUI.
|
||||
// The corresponding instructions referred by LO12_I/LO12_S
|
||||
// relocations will use the zero register instead.
|
||||
i64 val = sym.get_addr(ctx);
|
||||
if (sign_extend(val, 11) == val)
|
||||
delta += 4;
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_ADD: {
|
||||
// These relocations are used to materialize the upper 20 bits of
|
||||
// an address relative to the thread pointer as follows:
|
||||
//
|
||||
// lui a5,%tprel_hi(foo) # R_RISCV_TPREL_HI20 (symbol)
|
||||
// add a5,a5,tp,%tprel_add(foo) # R_RISCV_TPREL_ADD (symbol)
|
||||
//
|
||||
// Then thread-local variable `foo` is accessed with a 12-bit offset
|
||||
// like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
|
||||
//
|
||||
// However, if the offset is ±2 KiB, we don't need to materialize
|
||||
// the upper 20 bits in a register. We can instead access the
|
||||
// thread-local variable directly with TP like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(tp)
|
||||
//
|
||||
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
|
||||
i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
|
||||
if (sign_extend(val, 11) == val)
|
||||
delta += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isec.extra.r_deltas[rels.size()] = delta;
|
||||
isec.sh_size -= delta;
|
||||
}
|
||||
|
||||
// Shrink sections by interpreting relocations.
|
||||
//
|
||||
// This operation seems to be optional, because by default longest
|
||||
// instructions are being used. However, calling this function is actually
|
||||
// mandatory because of R_RISCV_ALIGN. R_RISCV_ALIGN is a directive to the
|
||||
// linker to align the location referred to by the relocation to a
|
||||
// specified byte boundary. We at least have to interpret them to satisfy
|
||||
// the alignment constraints.
|
||||
template <typename E>
|
||||
i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "riscv_resize_sections");
|
||||
|
||||
// True if we can use the 2-byte instructions. This is usually true on
|
||||
// Unix because RV64GC is generally considered the baseline hardware.
|
||||
bool use_rvc = get_eflags(ctx) & EF_RISCV_RVC;
|
||||
|
||||
// Find all the relocations that can be relaxed.
|
||||
// This step should only shrink sections.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
shrink_section(ctx, *isec, use_rvc);
|
||||
});
|
||||
|
||||
// Fix symbol values.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
|
||||
InputSection<E> *isec = sym->get_input_section();
|
||||
if (!isec || isec->extra.r_deltas.empty())
|
||||
continue;
|
||||
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
auto it = std::lower_bound(rels.begin(), rels.end(), sym->value,
|
||||
[&](const ElfRel<E> &r, u64 val) {
|
||||
return r.r_offset < val;
|
||||
});
|
||||
|
||||
sym->value -= isec->extra.r_deltas[it - rels.begin()];
|
||||
}
|
||||
});
|
||||
|
||||
// Re-compute section offset again to finalize them.
|
||||
compute_section_sizes(ctx);
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void write_plt_header(Context<E> &, u8 *); \
|
||||
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void \
|
||||
EhFrameSection<E>::apply_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
|
||||
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::copy_contents_riscv(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::scan_relocations(Context<E> &); \
|
||||
template i64 riscv_resize_sections(Context<E> &);
|
||||
|
||||
INSTANTIATE(RV64LE);
|
||||
INSTANTIATE(RV64BE);
|
||||
INSTANTIATE(RV32LE);
|
||||
INSTANTIATE(RV32BE);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -12,7 +12,7 @@
|
||||
// As an instruction set, s390x isn't particularly odd. It has 16 general-
|
||||
// purpose registers. Instructions are 2, 4 or 6 bytes long and always
|
||||
// aligned to 2 bytes boundaries. Despite unfamiliarty, I found that it
|
||||
// just feels like an x86-64 in a parallel universe.
|
||||
// just feels like a 64-bit i386 in a parallel universe.
|
||||
//
|
||||
// Here is the register usage in this ABI:
|
||||
//
|
||||
@@ -25,54 +25,50 @@
|
||||
// a1: upper 32 bits of TP (thread pointer)
|
||||
// a2: lower 32 bits of TP (thread pointer)
|
||||
//
|
||||
// Thread-local storage (TLS) is supported on s390x in the same way as it
|
||||
// is on other targets with one exeption. On other targets, __tls_get_addr
|
||||
// is used to get an address of a thread-local variable. On s390x,
|
||||
// __tls_get_offset is used instead. The difference is __tls_get_offset
|
||||
// returns an address of a thread-local variable as an offset from TP. So
|
||||
// we need to add TP to a return value before use. I don't know why it is
|
||||
// different, but that is the way it is.
|
||||
// TLS is supported on s390x in the same way as it is on other targets
|
||||
// with one exeption. On other targets, __tls_get_addr is used to get an
|
||||
// address of a thread-local variable. On s390x, __tls_get_offset is used
|
||||
// instead. The difference is __tls_get_offset returns an address of a
|
||||
// thread-local variable as an offset from TP. So we need to add TP to a
|
||||
// return value before use. I don't know why it is different, but that is
|
||||
// the way it is.
|
||||
//
|
||||
// https://github.com/IBM/s390x-abi/releases/download/v1.6.1/lzsabi_s390x.pdf
|
||||
|
||||
#if MOLD_S390X
|
||||
// https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
namespace mold::elf {
|
||||
|
||||
using E = S390X;
|
||||
|
||||
static void write_low12(u8 *loc, u64 val) {
|
||||
*(ub16 *)loc &= 0xf000;
|
||||
*(ub16 *)loc |= val & 0x0fff;
|
||||
}
|
||||
|
||||
static void write_mid20(u8 *loc, u64 val) {
|
||||
*(ub32 *)loc &= 0xf000'00ff;
|
||||
*(ub32 *)loc |= (bits(val, 11, 0) << 16) | (bits(val, 19, 12) << 8);
|
||||
}
|
||||
|
||||
static void write_low24(u8 *loc, u64 val) {
|
||||
*(ub32 *)loc &= 0xff00'0000;
|
||||
*(ub32 *)loc |= val & 0x00ff'ffff;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static u8 insn[] = {
|
||||
// Compute the offset into .rela.plt. This is equivalent to
|
||||
// (%r0 - %r1 - 48 - 14) * 3/2 where %r0 is the PLT entry address
|
||||
// plus 14, %r1 is the start address of .plt, and 48 is the size
|
||||
// of this PLT header. We multiply by 3/2 because each PLT entry
|
||||
// is 16 bytes, whereas each .rela.plt entry is 24 bytes.
|
||||
0xb9, 0x09, 0x00, 0x01, // sgr %r0, %r1
|
||||
0xa7, 0x0b, 0xff, 0xc2, // aghi %r0, -62
|
||||
0xeb, 0x10, 0x00, 0x01, 0x00, 0x0c, // srlg %r1, %r0, 1
|
||||
0xb9, 0x08, 0x00, 0x01, // agr %r0, %r1
|
||||
// Store the computed value to 56(%r15) and .got.plt[1] to 48(%15)
|
||||
// where %r15 is the stack pointer.
|
||||
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
|
||||
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
|
||||
// Branch to _dl_runtime_resolve
|
||||
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
|
||||
0x07, 0xf1, // br %r1
|
||||
0x00, 0x00, 0x00, 0x00, // (filler)
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 26) =
|
||||
(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 24) >> 1;
|
||||
*(ub32 *)(buf + 8) = (ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 6) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -80,12 +76,15 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
|
||||
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
|
||||
0x0d, 0x01, // basr %r0, %r1
|
||||
0x00, 0x00, // (filler)
|
||||
0xc0, 0x01, 0, 0, 0, 0, // lgfi %r0, PLT_INDEX
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 14) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -94,21 +93,19 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT_ENTRY_OFFSET
|
||||
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
|
||||
0x07, 0xf1, // br %r1
|
||||
0x00, 0x00, // (filler)
|
||||
0x07, 0x00, // nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_390_PC32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
@@ -124,6 +121,11 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
@@ -132,114 +134,160 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
|
||||
// R_390_*DBL relocs should never refer to a symbol at an odd address
|
||||
check(val, lo, hi);
|
||||
|
||||
// R_390_*DBL relocs should never refer a symbol at an odd address
|
||||
if (val & 1)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
};
|
||||
|
||||
#define S sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_390_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
case R_390_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
case R_390_12:
|
||||
check(S + A, 0, 1 << 12);
|
||||
*(ul16 *)loc |= bits(S + A, 11, 0);
|
||||
}
|
||||
case R_390_12: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 12);
|
||||
write_low12(loc, val);
|
||||
break;
|
||||
case R_390_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = S + A;
|
||||
}
|
||||
case R_390_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
case R_390_20:
|
||||
check(S + A, 0, 1 << 20);
|
||||
write_mid20(loc, S + A);
|
||||
}
|
||||
case R_390_20: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_390_32:
|
||||
case R_390_PLT32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
case R_390_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_PLT64:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_390_PC12DBL:
|
||||
case R_390_PLT12DBL:
|
||||
check_dbl(S + A - P, -(1 << 12), 1 << 12);
|
||||
*(ul16 *)loc |= bits(S + A - P, 12, 1);
|
||||
case R_390_PLT12DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 12), 1 << 12);
|
||||
write_low12(loc, val >> 1);
|
||||
break;
|
||||
case R_390_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - P;
|
||||
}
|
||||
case R_390_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
case R_390_PC32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - P;
|
||||
}
|
||||
case R_390_PC32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_PC64:
|
||||
case R_390_PLT64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_390_PC16DBL:
|
||||
case R_390_PLT16DBL:
|
||||
check_dbl(S + A - P, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc = (S + A - P) >> 1;
|
||||
case R_390_PLT16DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc = val >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_PC24DBL:
|
||||
case R_390_PLT24DBL:
|
||||
check_dbl(S + A - P, -(1 << 24), 1 << 24);
|
||||
*(ub32 *)loc |= bits(S + A - P, 24, 1);
|
||||
case R_390_PLT24DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 24), 1 << 24);
|
||||
write_low24(loc, val >> 1);
|
||||
break;
|
||||
}
|
||||
case R_390_PC32DBL:
|
||||
case R_390_PLT32DBL:
|
||||
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (S + A - P) >> 1;
|
||||
if (ctx.is_static && &sym == ctx.tls_get_offset) {
|
||||
// __tls_get_offset() in libc.a is stub code that calls abort().
|
||||
// So we provide a replacement function.
|
||||
*(ub32 *)loc = (ctx.s390x_tls_get_offset->shdr.sh_addr - P) >> 1;
|
||||
} else {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
}
|
||||
break;
|
||||
case R_390_GOT12:
|
||||
case R_390_GOTPLT12:
|
||||
check(G + A, 0, 1 << 12);
|
||||
*(ul16 *)loc |= bits(G + A, 11, 0);
|
||||
case R_390_GOTPLT12: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 12);
|
||||
write_low12(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT16:
|
||||
case R_390_GOTPLT16:
|
||||
check(G + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = G + A;
|
||||
case R_390_GOTPLT16: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT20:
|
||||
case R_390_GOTPLT20:
|
||||
check(G + A, 0, 1 << 20);
|
||||
write_mid20(loc, G + A);
|
||||
case R_390_GOTPLT20: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT32:
|
||||
case R_390_GOTPLT32:
|
||||
check(G + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = G + A;
|
||||
case R_390_GOTPLT32: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT64:
|
||||
case R_390_GOTPLT64:
|
||||
*(ub64 *)loc = G + A;
|
||||
break;
|
||||
case R_390_GOTOFF16:
|
||||
case R_390_PLTOFF16:
|
||||
check(S + A - GOT, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - GOT;
|
||||
case R_390_PLTOFF16: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF32:
|
||||
case R_390_PLTOFF32:
|
||||
check(S + A - GOT, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - GOT;
|
||||
case R_390_PLTOFF32: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF64:
|
||||
case R_390_PLTOFF64:
|
||||
*(ub64 *)loc = S + A - GOT;
|
||||
@@ -247,27 +295,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_GOTPC:
|
||||
*(ub64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_390_GOTPCDBL:
|
||||
check_dbl(GOT + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (GOT + A - P) >> 1;
|
||||
case R_390_GOTPCDBL: {
|
||||
i64 val = GOT + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
break;
|
||||
case R_390_GOTENT:
|
||||
// If we can relax a GOT-loading LGRL to an address-materializing
|
||||
// LARL, do that. The format of LGRL is 0xc 0x4 <reg> 0x8 followed
|
||||
// by a 32-bit offset. LARL is 0xc 0x0 <reg> 0x0.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx)) {
|
||||
u64 op = *(ub16 *)(loc - 2);
|
||||
u64 val = S + A - P;
|
||||
if ((op & 0xff0f) == 0xc408 && A == 2 && (val & 1) == 0 &&
|
||||
is_int(val, 33)) {
|
||||
*(ub16 *)(loc - 2) = 0xc000 | (op & 0x00f0);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
check_dbl(GOT + G + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (GOT + G + A - P) >> 1;
|
||||
}
|
||||
case R_390_GOTENT: {
|
||||
i64 val = GOT + G + A - P;
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
@@ -283,60 +322,56 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_TLS_GD32:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else if (sym.has_gottp(ctx))
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GD64:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub64 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else if (sym.has_gottp(ctx))
|
||||
*(ub64 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GDCALL:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
// do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// lg %r2, 0(%r2, %r12)
|
||||
static u8 insn[] = { 0xe3, 0x22, 0xc0, 0x00, 0x00, 0x04 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
} else {
|
||||
// nop
|
||||
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
if (!sym.has_tlsgd(ctx)) {
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub32 *)loc = ctx.dtp_addr - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDM64:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub64 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_390_TLS_LDO32:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc = S + A - ctx.tls_begin;
|
||||
else
|
||||
*(ub64 *)loc = ctx.dtp_addr - ctx.tp_addr;
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDO64:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub64 *)loc = S + A - ctx.tls_begin;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDCALL:
|
||||
if (!ctx.got->has_tlsld(ctx)) {
|
||||
// nop
|
||||
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDO32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDO64:
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,28 +381,38 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto 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 << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
case R_390_32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
@@ -378,46 +423,57 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
*(ub64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
|
||||
}
|
||||
|
||||
#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<const 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_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_390_8:
|
||||
case R_390_12:
|
||||
case R_390_16:
|
||||
case R_390_20:
|
||||
case R_390_32:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
scan_rel(ctx, sym, rel, absrel_table);
|
||||
break;
|
||||
case R_390_PC12DBL:
|
||||
case R_390_PC16:
|
||||
case R_390_PC16DBL:
|
||||
case R_390_PC24DBL:
|
||||
case R_390_PC32:
|
||||
case R_390_PC32DBL:
|
||||
case R_390_PC64:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
break;
|
||||
case R_390_GOT12:
|
||||
case R_390_GOT16:
|
||||
@@ -455,41 +511,49 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_390_TLS_GD32:
|
||||
case R_390_TLS_GD64:
|
||||
// We always want to relax calls to __tls_get_offset() in statically-
|
||||
// linked executables because __tls_get_offset() in libc.a just calls
|
||||
// abort().
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
|
||||
// Do nothing
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
if (!relax_tlsgd(ctx, sym))
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
case R_390_TLS_LDM64:
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
if (!relax_tlsld(ctx))
|
||||
ctx.needs_tlsld = true;
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LE32:
|
||||
case R_390_TLS_LE64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_64:
|
||||
case R_390_TLS_LDO32:
|
||||
case R_390_TLS_LDO64:
|
||||
case R_390_TLS_GDCALL:
|
||||
case R_390_TLS_LDCALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
// __tls_get_offset() in libc.a just calls abort(), assuming that the
|
||||
// linker always relaxes TLS calls for statically-linkd executables.
|
||||
// We don't always do that because we believe --relax and --static
|
||||
// should be orthogonal.
|
||||
//
|
||||
// This section provides a replacement for __tls_get_offset() in libc.a.
|
||||
void S390XTlsGetOffsetSection::copy_buf(Context<E> &ctx) {
|
||||
static const u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT
|
||||
0xb9, 0x08, 0x00, 0x21, // agr %r2, %r1
|
||||
0xe3, 0x20, 0x20, 0x08, 0x00, 0x04, // lg %r2, 8(%r2)
|
||||
0xc0, 0x11, 0, 0, 0, 0, // lgfi %r1, TLS_BLOCK_SIZE
|
||||
0xb9, 0x09, 0x00, 0x21, // sgr %r2, %r1
|
||||
0x07, 0xfe, // br %r14
|
||||
};
|
||||
|
||||
#endif
|
||||
assert(this->shdr.sh_size == sizeof(insn));
|
||||
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset;
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
*(ub32 *)(loc + 2) = (ctx.got->shdr.sh_addr - this->shdr.sh_addr) >> 1;
|
||||
*(ub32 *)(loc + 18) = ctx.tp_addr - ctx.tls_begin;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -51,13 +51,15 @@
|
||||
// address. That also means each function needs to execute the above piece
|
||||
// of code to become position-independent.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/sparc.pdf
|
||||
|
||||
#if MOLD_SPARC64
|
||||
// This scheme is very similar to i386. That may not be a coincidence
|
||||
// because the i386 ELF psABI is created by Sun Microsystems too.
|
||||
//
|
||||
// https://docs.oracle.com/cd/E36784_01/html/E36857/chapter6-62988.html
|
||||
// https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-40/index.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
namespace mold::elf {
|
||||
|
||||
using E = SPARC64;
|
||||
|
||||
@@ -78,77 +80,26 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
memset(buf, 0, E::plt_hdr_size);
|
||||
}
|
||||
|
||||
// Returns the byte offset within .plt of the data pointer for a large SPARC
|
||||
// PLT entry. See write_plt_entry below for the block layout this assumes.
|
||||
i64 sparc_plt_ptr_offset(Context<E> &ctx, i64 pltidx) {
|
||||
i64 i = pltidx - sparc_num_small_plt;
|
||||
i64 block = i / 160;
|
||||
i64 nlarge = (i64)ctx.plt->symbols.size() - sparc_num_small_plt;
|
||||
i64 nstub = std::min<i64>(160, nlarge - block * 160);
|
||||
return 0x100000 + block * 5120 + nstub * 24 + (i % 160) * 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
// SPARC uses two PLT entry formats. A "small" entry branches directly to
|
||||
// the resolver stub (.PLT1) with a BPcc instruction whose reach is only
|
||||
// ±1 MiB. Once the PLT grows past that (0x100000 bytes), we switch to a
|
||||
// "large" format: rather than branching to the resolver, a large entry
|
||||
// loads a 64-bit value from a nearby data pointer and jumps to (that value
|
||||
// + its own address).
|
||||
//
|
||||
// Large entries are grouped into blocks of 160: each block is 160 code
|
||||
// stubs followed by 160 8-byte data pointers, one per stub. The stubs must
|
||||
// sit on a fixed grid, 24 bytes apart, because that is how the loader finds
|
||||
// them: on a lazy bind the stub jumps to the resolver (.PLT0) with only its
|
||||
// own address, and the loader derives which symbol to resolve from that
|
||||
// address. Nothing may sit between two stubs, so each stub's pointer lives
|
||||
// in the block's pointer region, which it reaches with a signed 13-bit ldx
|
||||
// offset (see to_plt_offset). This layout is dictated by the loader; we
|
||||
// cannot rearrange or simplify it.
|
||||
i64 idx = sym.get_plt_idx(ctx);
|
||||
static ub32 insn[] = {
|
||||
0x0300'0000, // sethi (. - .PLT0), %g1
|
||||
0x3068'0000, // ba,a %xcc, .PLT1
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
};
|
||||
|
||||
if (idx < sparc_num_small_plt) {
|
||||
static ub32 insn[] = {
|
||||
0x0300'0000, // sethi (. - .PLT0), %g1
|
||||
0x3068'0000, // ba,a %xcc, .PLT1
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
};
|
||||
u64 plt0 = ctx.plt->shdr.sh_addr;
|
||||
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
|
||||
u64 entry = sym.get_plt_addr(ctx);
|
||||
|
||||
u64 plt0 = ctx.plt->shdr.sh_addr;
|
||||
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
|
||||
u64 entry = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
|
||||
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
|
||||
} else {
|
||||
static ub32 insn[] = {
|
||||
0x8a10'000f, // mov %o7, %g5
|
||||
0x4000'0002, // call . + 8
|
||||
0x0100'0000, // nop
|
||||
0xc25b'e000, // ldx [ %o7 + .ptr ], %g1
|
||||
0x83c3'c001, // jmpl %o7 + %g1, %g1
|
||||
0x9e10'0005, // mov %g5, %o7
|
||||
};
|
||||
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
u64 call = sym.get_plt_addr(ctx) + 4;
|
||||
i64 ptroff = sparc_plt_ptr_offset(ctx, idx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 12) |= bits(plt + ptroff - call, 12, 0);
|
||||
|
||||
// The data pointer initially holds (.PLT0 - call) so that the first call
|
||||
// jumps to .PLT0, where the loader's lazy resolver lives. The resolver
|
||||
// later overwrites it with (target - call).
|
||||
*(ub64 *)(ctx.buf + ctx.plt->shdr.sh_offset + ptroff) = plt - call;
|
||||
}
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
|
||||
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -165,12 +116,12 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ub64 *)(buf + 24) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub64 *)(buf + 24) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -192,9 +143,12 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// We iterate over relocations in reverse order so that it is easy
|
||||
// to swap instructions for R_SPARC_TLS_GD_CALL.
|
||||
for (i64 i = (i64)rels.size() - 1; i >= 0; i--) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
continue;
|
||||
@@ -202,85 +156,114 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto rs1 = [&] { return *(ub32 *)loc & (0b11111 << 14); };
|
||||
auto rs2 = [&] { return *(ub32 *)loc & 0b11111; };
|
||||
auto rd = [&] { return *(ub32 *)loc & (0b11111 << 25); };
|
||||
#define S sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_5:
|
||||
check(S + A, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(S + A, 4, 0);
|
||||
case R_SPARC_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_SPARC_6:
|
||||
check(S + A, 0, 1 << 6);
|
||||
*(ub32 *)loc |= bits(S + A, 5, 0);
|
||||
case R_SPARC_5: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(val, 4, 0);
|
||||
break;
|
||||
case R_SPARC_7:
|
||||
check(S + A, 0, 1 << 7);
|
||||
*(ub32 *)loc |= bits(S + A, 6, 0);
|
||||
}
|
||||
case R_SPARC_6: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 6);
|
||||
*(ub32 *)loc |= bits(val, 5, 0);
|
||||
break;
|
||||
case R_SPARC_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
}
|
||||
case R_SPARC_7: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 7);
|
||||
*(ub32 *)loc |= bits(val, 6, 0);
|
||||
break;
|
||||
case R_SPARC_10:
|
||||
check(S + A, 0, 1 << 10);
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0);
|
||||
}
|
||||
case R_SPARC_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*(u8 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_10: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 10);
|
||||
*(ub32 *)loc |= bits(val, 9, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_LO10:
|
||||
case R_SPARC_LOPLT10:
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0);
|
||||
break;
|
||||
case R_SPARC_11:
|
||||
check(S + A, 0, 1 << 11);
|
||||
*(ub32 *)loc |= bits(S + A, 10, 0);
|
||||
case R_SPARC_11: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 11);
|
||||
*(ub32 *)loc |= bits(val, 10, 0);
|
||||
break;
|
||||
case R_SPARC_13:
|
||||
check(S + A, 0, 1 << 13);
|
||||
*(ub32 *)loc |= bits(S + A, 12, 0);
|
||||
}
|
||||
case R_SPARC_13: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 13);
|
||||
*(ub32 *)loc |= bits(val, 12, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_16:
|
||||
case R_SPARC_UA16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = S + A;
|
||||
case R_SPARC_UA16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
case R_SPARC_22:
|
||||
check(S + A, 0, 1 << 22);
|
||||
*(ub32 *)loc |= bits(S + A, 21, 0);
|
||||
}
|
||||
case R_SPARC_22: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 22);
|
||||
*(ub32 *)loc |= bits(val, 21, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_UA32:
|
||||
case R_SPARC_PLT32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
case R_SPARC_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_PLT64:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_REGISTER:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_SPARC_DISP8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
case R_SPARC_DISP8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*(u8 *)loc = val;
|
||||
break;
|
||||
case R_SPARC_DISP16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - P;
|
||||
}
|
||||
case R_SPARC_DISP16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP32:
|
||||
case R_SPARC_PCPLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - P;
|
||||
case R_SPARC_PCPLT32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -290,19 +273,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub16 *)loc |= (bit(val, 16) << 21) | bits(val, 15, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
*(ub32 *)loc |= bits(S + A - P, 20, 2);
|
||||
case R_SPARC_WDISP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 20), 1 << 20);
|
||||
*(ub32 *)loc |= bits(val, 20, 2);
|
||||
break;
|
||||
case R_SPARC_WDISP22:
|
||||
check(S + A - P, -(1 << 23), 1 << 23);
|
||||
*(ub32 *)loc |= bits(S + A - P, 23, 2);
|
||||
}
|
||||
case R_SPARC_WDISP22: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 23), 1 << 23);
|
||||
*(ub32 *)loc |= bits(val, 23, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP30:
|
||||
case R_SPARC_WPLT30:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc |= bits(S + A - P, 31, 2);
|
||||
case R_SPARC_WPLT30: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc |= bits(val, 31, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_HI22:
|
||||
case R_SPARC_HIPLT22:
|
||||
case R_SPARC_LM22:
|
||||
@@ -332,34 +321,39 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// We always have to relax a GOT load to a load immediate if a
|
||||
// symbol is local, because R_SPARC_GOTDATA_OP cannot represent
|
||||
// an addend for a local symbol.
|
||||
if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else {
|
||||
if (sym.is_imported) {
|
||||
*(ub32 *)loc |= bits(G, 31, 10);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
}
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
if (sym.is_absolute()) {
|
||||
case R_SPARC_GOTDATA_OP_LOX10: {
|
||||
if (sym.is_imported) {
|
||||
*(ub32 *)loc |= bits(G, 9, 0);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else {
|
||||
*(ub32 *)loc |= bits(G, 9, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
if (sym.is_imported)
|
||||
break;
|
||||
|
||||
if (sym.is_absolute()) {
|
||||
// ldx [ %rs1 + %rs2 ], %rd → mov %rs2, %rd
|
||||
*(ub32 *)loc = 0x8010'0000 | rs2() | rd();
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
// ldx [ %rs1 + %rs2 ], %rd → add %rs1, %rs2, %rd
|
||||
*(ub32 *)loc = 0x8000'0000 | rs1() | rs2() | rd();
|
||||
// ldx [ %g2 + %g1 ], %g1 → nop
|
||||
*(ub32 *)loc = 0x0100'0000;
|
||||
} else {
|
||||
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
|
||||
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
|
||||
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
|
||||
}
|
||||
break;
|
||||
case R_SPARC_PC10:
|
||||
@@ -402,85 +396,29 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc |= bits(S + A, 11, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 31, 10);
|
||||
} else {
|
||||
*(ub32 *)loc |= bits(~(S + A - ctx.tp_addr), 31, 10);
|
||||
}
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_LO10:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// add %rs1, %rs2, %rd → or %rs1, $imm, %rd
|
||||
*(ub32 *)loc = 0x8010'2000 | rs1() | rd();
|
||||
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 9, 0);
|
||||
} else {
|
||||
// add %rs1, %rs2, %rd → xor %rs1, $imm, %rd
|
||||
*(ub32 *)loc = 0x8018'2000 | rs1() | rd();
|
||||
*(ub32 *)loc |= bits(S + A - ctx.tp_addr, 9, 0) | 0b1'1100'0000'0000;
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_GD_ADD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
// do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// add %rs1, %rs2, %rd → ldx [ %rs1 + %rs2 ], %rd
|
||||
*(ub32 *)loc = 0xc058'0000 | rs1() | rs2() | rd();
|
||||
} else {
|
||||
// add %rs1, %rs2, %rd → add %g7, %rs2, %rd
|
||||
*(ub32 *)loc = 0x8001'c000 | rs2() | rd();
|
||||
}
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
|
||||
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// When we rewrite a branch instruction with a non-branch one,
|
||||
// we need to swap the instruction and the following one so that
|
||||
// the original execution order, which is inverted due to the
|
||||
// branch delay slot, is preserved.
|
||||
//
|
||||
// Since we apply relocations from the end to the beginning,
|
||||
// the instruction at loc + 4 is already complete.
|
||||
memcpy(loc, loc + 4, 4);
|
||||
*(ub32 *)(loc + 4) = 0x9001'c008; // add %g7, %o0, %o0
|
||||
} else {
|
||||
*(ub32 *)loc = 0x0100'0000; // call → nop
|
||||
}
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (ctx.arg.is_static)
|
||||
*(ub32 *)loc |= bits(ctx.sparc_tls_get_addr->shdr.sh_addr + A - P, 31, 2);
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + A - P, 31, 2);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_LO10:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_ADD:
|
||||
if (!ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc = 0x8021'c000 | rs2() | rd(); // sub %g7, %rs2, %rd
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
|
||||
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
|
||||
} else {
|
||||
*(ub32 *)loc = 0x0100'0000; // nop
|
||||
}
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_LDO_HIX22:
|
||||
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 31, 10);
|
||||
*(ub32 *)loc |= bits(S + A - ctx.tls_begin, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_LDO_LOX10:
|
||||
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 9, 0);
|
||||
*(ub32 *)loc |= bits(S + A - ctx.tls_begin, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_IE_HI22:
|
||||
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 31, 10);
|
||||
@@ -497,8 +435,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_SPARC_SIZE32:
|
||||
*(ub32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_SPARC_64:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_TLS_GD_ADD:
|
||||
case R_SPARC_TLS_LDM_ADD:
|
||||
case R_SPARC_TLS_LDO_ADD:
|
||||
case R_SPARC_TLS_IE_LD:
|
||||
case R_SPARC_TLS_IE_LDX:
|
||||
@@ -507,6 +445,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,22 +460,30 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto 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 << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
@@ -542,40 +494,54 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_UA32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
case R_SPARC_UA32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_TLS_DTPOFF32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(ub32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_SPARC_TLS_DTPOFF64:
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
*(ub64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
|
||||
}
|
||||
|
||||
#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<const ElfRel<E>> rels = get_rels(ctx);
|
||||
bool needs_tlsgd = false;
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_SPARC_8:
|
||||
case R_SPARC_5:
|
||||
case R_SPARC_6:
|
||||
@@ -589,6 +555,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_REGISTER:
|
||||
case R_SPARC_UA16:
|
||||
case R_SPARC_UA32:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_PC_HM10:
|
||||
case R_SPARC_OLO10:
|
||||
case R_SPARC_LOX10:
|
||||
@@ -601,7 +568,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_HI22:
|
||||
case R_SPARC_H44:
|
||||
case R_SPARC_HH22:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
scan_rel(ctx, sym, rel, absrel_table);
|
||||
break;
|
||||
case R_SPARC_PLT32:
|
||||
case R_SPARC_WPLT30:
|
||||
@@ -636,45 +603,29 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_WDISP19:
|
||||
case R_SPARC_WDISP22:
|
||||
case R_SPARC_PC_HH22:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
needs_tlsgd = true;
|
||||
}
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
} else {
|
||||
ctx.needs_tlsld = true;
|
||||
}
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_SPARC_TLS_IE_HI22:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
|
||||
ctx.tls_get_addr->flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SPARC_64:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
case R_SPARC_GOTDATA_LOX10:
|
||||
case R_SPARC_TLS_GD_LO10:
|
||||
case R_SPARC_TLS_GD_ADD:
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_LO10:
|
||||
case R_SPARC_TLS_LDM_ADD:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
case R_SPARC_TLS_LDO_HIX22:
|
||||
case R_SPARC_TLS_LDO_LOX10:
|
||||
case R_SPARC_TLS_LDO_ADD:
|
||||
@@ -682,20 +633,35 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_TLS_IE_LD:
|
||||
case R_SPARC_TLS_IE_LDX:
|
||||
case R_SPARC_TLS_IE_LO10:
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
case R_SPARC_SIZE32:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
// TLS_GD_CALL and TLS_LDM_CALL relocations implicitly refer to
|
||||
// __tls_get_addr, which may be dynamically linked from libc.so.
|
||||
Symbol<E> &sym = *ctx.extra.tls_get_addr;
|
||||
if (sym.is_imported && (needs_tlsgd || ctx.needs_tlsld))
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
// __tls_get_addr is not defined by libc.a, so we can't use that function
|
||||
// in statically-linked executables. This section provides a replacement.
|
||||
void SparcTlsGetAddrSection::copy_buf(Context<E> &ctx) {
|
||||
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
#endif
|
||||
static const ub32 insn[] = {
|
||||
0x0300'0000, // sethi %hi(TP_SIZE), %g1
|
||||
0x8210'6000, // or %g1, %lo(TP_SIZE), %g1
|
||||
0x8221'c001, // sub %g7, %g1, %g1
|
||||
0xd05a'2008, // ldx [ %o0 + 8 ], %o0
|
||||
0x81c3'e008, // retl
|
||||
0x9000'4008, // add %g1, %o0, %o0
|
||||
};
|
||||
|
||||
assert(this->shdr.sh_size == sizeof(insn));
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
buf[0] |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
|
||||
buf[1] |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,760 @@
|
||||
// Supporting x86-64 is straightforward. Unlike its predecessor, i386,
|
||||
// x86-64 supports PC-relative addressing for position-independent code.
|
||||
// Being CISC, its instructions are variable in size. Branch instructions
|
||||
// take 4 bytes offsets, so we don't need range extension thunks.
|
||||
//
|
||||
// The psABI specifies %r11 as neither caller- nor callee-saved. It's
|
||||
// intentionally left out so that we can use it as a scratch register in
|
||||
// PLT.
|
||||
//
|
||||
// Thread Pointer (TP) is stored not to a general-purpose register but to
|
||||
// FS segment register. Segment register is a 64-bits register which can
|
||||
// be used as a base address for memory access. Each thread has a unique
|
||||
// FS value, and they access their thread-local variables relative to FS
|
||||
// as %fs:offset_from_tp.
|
||||
//
|
||||
// The value of a segment register itself is not generally readable from
|
||||
// the user space. As a workaround, libc initializes %fs:0 (the first word
|
||||
// referenced by FS) to the value of %fs itself. So we can obtain TP just
|
||||
// by `mov %fs:0, %rax` if we need it.
|
||||
//
|
||||
// For historical reasons, TP points past the end of the TLS block on x86.
|
||||
// This is contrary to other psABIs which usually use the beginning of the
|
||||
// TLS block as TP (with some addend). As a result, offsets from TP to
|
||||
// thread-local variables (TLVs) in the main executable are all negative.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
// This is a security-enhanced version of the regular PLT. The PLT
|
||||
// header and each PLT entry starts with endbr64 for the Intel's
|
||||
// control-flow enforcement security mechanism.
|
||||
//
|
||||
// Note that our IBT-enabled PLT 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. Our IBTPLT consists of just .plt and each entry
|
||||
// is 16 bytes long.
|
||||
//
|
||||
// Our PLT entry clobbers %r11, but that's fine because the resolver
|
||||
// function (_dl_runtime_resolve) clobbers %r11 anyway.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0x53, // push %r11
|
||||
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_X86_64_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
*(ul64 *)loc = val;
|
||||
break;
|
||||
case R_X86_64_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_X86_64_PC64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 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) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto 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 write32 = [&](u64 val) {
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S sym.get_addr(ctx)
|
||||
#define A rel.r_addend
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
|
||||
#define GOT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_X86_64_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
break;
|
||||
case R_X86_64_PC64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
write32s(G + A);
|
||||
break;
|
||||
case R_X86_64_GOT64:
|
||||
*(ul64 *)loc = G + A;
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_PLTOFF64:
|
||||
*(ul64 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
*(ul64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
*(ul64 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOT + A - P);
|
||||
} else {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOT + A - P);
|
||||
} else {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
} else {
|
||||
// Relax GD to LE. If we are creating an exectuable, the offset of
|
||||
// a thread-local variable from TP is a link-time constant. So we
|
||||
// don't need to call __tls_get_addr to obtain the address of a TLV.
|
||||
i64 val = S - ctx.tp_addr;
|
||||
assert(A == -4);
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// or
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
} else {
|
||||
// Relax LD to LE. If we are creating an executable, we don't need
|
||||
// to call __tls_get_addr to obtain the address of the beginning
|
||||
// of the current TLS block. TP points past the end of the TLS
|
||||
// block, and we know the exact size of the TLS block we have
|
||||
// created, so we can just subtract it from TP.
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
assert(A == -4);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
*(ul64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_X86_64_TPOFF32:
|
||||
write32s(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_X86_64_TPOFF64:
|
||||
*(ul64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
if (sym.has_gottp(ctx)) {
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
u32 insn = relax_gottpoff(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else {
|
||||
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto 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 write32 = [&](u64 val) {
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : (i64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(ul64 *)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<const 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_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= (NEEDS_GOT | 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:
|
||||
scan_rel(ctx, sym, rel, absrel_table);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
scan_rel(ctx, sym, rel, dyn_absrel_table);
|
||||
break;
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PC64:
|
||||
scan_rel(ctx, sym, rel, pcrel_table);
|
||||
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 (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (relax_tlsld(ctx))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for 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 (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
|
||||
|
||||
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
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
|
||||
+308
-722
File diff suppressed because it is too large.
Load diff
+554
@@ -0,0 +1,554 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, which often abbreviated as compunit
|
||||
// or cu, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using `gdb-add-index`
|
||||
// post-processing tool that comes with gdb.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name (with the same definition), while there
|
||||
// should be no two functions that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contains attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -fucntion-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4/5 and
|
||||
// .debug_rnglists/.debug_addr in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Split .debug_info into so-called "compilation units". A .debug_info
|
||||
// section usually contains one compunit unless it was created by `ld -r`.
|
||||
// This is for --gdb-index.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
file.debug_info->uncompress(ctx);
|
||||
std::string_view data = file.debug_info->contents;
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(U32<E> *)data.data() == 0xffffffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(U32<E> *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<GdbIndexName> vec;
|
||||
|
||||
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
|
||||
i64 off = 0;
|
||||
for (i64 i = 0; i < file.compunits.size(); i++) {
|
||||
if (offset == off)
|
||||
return file.compunits_idx + i;
|
||||
off += file.compunits[i].size();
|
||||
}
|
||||
Fatal(ctx) << isec << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
auto read = [&](InputSection<E> &isec) {
|
||||
isec.uncompress(ctx);
|
||||
std::string_view contents = isec.contents;
|
||||
|
||||
while (!contents.empty()) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(U32<E> *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(U32<E> *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
|
||||
u8 type = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string_view name = data.data();
|
||||
data = data.substr(name.size() + 1);
|
||||
|
||||
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (file.debug_pubnames)
|
||||
read(*file.debug_pubnames);
|
||||
if (file.debug_pubtypes)
|
||||
read(*file.debug_pubtypes);
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} <
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} ==
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
std::sort(vec.begin(), vec.end(), less);
|
||||
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (u8 *buf = chunk->get_uncompressed_data())
|
||||
return buf;
|
||||
return ctx.buf + chunk->shdr.sh_offset;
|
||||
}
|
||||
|
||||
// Try to find a compilation unit from .debug_info and its
|
||||
// corresponding record from .debug_abbrev and returns them.
|
||||
template <typename E>
|
||||
static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(U16<E> *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(U32<E> *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(U32<E> *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
|
||||
switch (u32 unit_type = cu[6]; unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
cu += 12;
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
cu += 20;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
|
||||
<< std::hex << unit_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
|
||||
<< dwarf_version;
|
||||
}
|
||||
|
||||
u32 abbrev_code = read_uleb(cu);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return {cu, abbrev, dwarf_version};
|
||||
}
|
||||
|
||||
// Estimate the number of address ranges contained in a given file.
|
||||
// It may over-estimate but never under-estimate.
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
// Each CU contains zero or one address area.
|
||||
i64 ret = file.compunits.size();
|
||||
|
||||
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
|
||||
// .debug_ranges contains a vector of [begin, end) address pairs.
|
||||
// The last entry must be a null terminator, so we do -1.
|
||||
if (file.debug_ranges)
|
||||
ret += file.debug_ranges->sh_size / sizeof(Word<E>) / 2 - 1;
|
||||
|
||||
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
|
||||
// contains variable-length entries. The smallest possible range entry
|
||||
// is one byte for the code and two ULEB128 values (each can be as
|
||||
// small as one byte), so 3 bytes.
|
||||
if (file.debug_rnglists)
|
||||
ret += file.debug_rnglists->sh_size / 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields. This class reads them.
|
||||
template <typename E>
|
||||
class DebugInfoReader {
|
||||
public:
|
||||
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
|
||||
: ctx(ctx), file(file), cu(cu) {}
|
||||
|
||||
u64 read(u64 form);
|
||||
|
||||
Context<E> &ctx;
|
||||
ObjectFile<E> &file;
|
||||
u8 *cu;
|
||||
};
|
||||
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *cu++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)cu;
|
||||
cu += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(cu);
|
||||
case DW_FORM_string:
|
||||
cu += strlen((char *)cu) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0) {
|
||||
// base address selection entry
|
||||
base = range[i + 1];
|
||||
} else {
|
||||
vec.push_back(range[i] + base);
|
||||
vec.push_back(range[i + 1] + base);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
Word<E> *addrx) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (;;) {
|
||||
switch (*rnglist++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return vec;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(rnglist)];
|
||||
break;
|
||||
case DW_RLE_startx_endx:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
break;
|
||||
case DW_RLE_startx_length:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)rnglist;
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
u8 *cu;
|
||||
u8 *abbrev;
|
||||
u32 dwarf_version;
|
||||
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
|
||||
|
||||
DebugInfoReader<E> reader{ctx, file, cu};
|
||||
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
std::optional<u64> rnglists_base;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = reader.read(form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(get_buffer(ctx, ctx.debug_addr) + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (dwarf_version <= 4) {
|
||||
Word<E> *range_begin =
|
||||
(Word<E> *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
|
||||
return read_debug_range(ctx, file, range_begin);
|
||||
}
|
||||
|
||||
assert(dwarf_version == 5);
|
||||
|
||||
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
|
||||
if (ranges.form == DW_FORM_sec_offset)
|
||||
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
|
||||
|
||||
if (!rnglists_base)
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {lo, high_pc.value};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {lo, addrx[high_pc.value]};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {lo, lo + high_pc.value};
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
|
||||
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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 should_keep(const InputSection<E> &isec) {
|
||||
u32 type = isec.shdr().sh_type;
|
||||
u32 flags = isec.shdr().sh_flags;
|
||||
std::string_view name = isec.name();
|
||||
|
||||
return (flags & SHF_GNU_RETAIN) ||
|
||||
type == SHT_NOTE ||
|
||||
type == SHT_INIT_ARRAY ||
|
||||
type == SHT_FINI_ARRAY ||
|
||||
type == SHT_PREINIT_ARRAY ||
|
||||
(std::is_same_v<E, ARM32> && type == SHT_ARM_EXIDX) ||
|
||||
name.starts_with(".ctors") ||
|
||||
name.starts_with(".dtors") ||
|
||||
name.starts_with(".init") ||
|
||||
name.starts_with(".fini") ||
|
||||
is_c_identifier(name);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool mark_section(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && fast_mark(isec->is_visited);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
|
||||
// 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 (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (const 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.get_input_section()))
|
||||
continue;
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void collect_root_set(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
|
||||
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->get_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.
|
||||
u32 flags = isec->shdr().sh_flags;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
|
||||
if (should_keep(*isec))
|
||||
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 (const ElfRel<E> &rel : cie.get_rels())
|
||||
enqueue_symbol(file->symbols[rel.r_sym]);
|
||||
});
|
||||
}
|
||||
|
||||
// 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, rootset);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
+92
-112
@@ -65,11 +65,10 @@
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../lib/siphash.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
@@ -79,43 +78,44 @@
|
||||
|
||||
static constexpr int64_t HASH_SIZE = 16;
|
||||
|
||||
using Digest = std::array<uint8_t, HASH_SIZE>;
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
|
||||
namespace std {
|
||||
template <> struct hash<Digest> {
|
||||
template<> struct hash<Digest> {
|
||||
size_t operator()(const Digest &k) const {
|
||||
static_assert(sizeof(size_t) <= HASH_SIZE);
|
||||
size_t val;
|
||||
memcpy(&val, k.data(), sizeof(size_t));
|
||||
return val;
|
||||
return *(int64_t *)&k[0];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace mold {
|
||||
namespace mold::elf {
|
||||
|
||||
static u8 hmac_key[16];
|
||||
class BitVector {
|
||||
public:
|
||||
BitVector(u32 size) : vec((size + 7) / 8) {}
|
||||
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
|
||||
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
|
||||
|
||||
private:
|
||||
std::vector<u8> vec;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static void uniquify_cies(Context<E> &ctx) {
|
||||
Timer t(ctx, "uniquify_cies");
|
||||
std::vector<CieRecord<E> *> cies;
|
||||
|
||||
auto find = [&](CieRecord<E> &cie) -> i64 {
|
||||
for (i64 i = 0; i < cies.size(); i++)
|
||||
if (cie_equals(cie, *cies[i]))
|
||||
return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (CieRecord<E> &cie : file->cies) {
|
||||
if (i64 idx = find(cie); idx != -1) {
|
||||
cie.icf_idx = idx;
|
||||
} else {
|
||||
cie.icf_idx = cies.size();
|
||||
cies.push_back(&cie);
|
||||
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:;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,26 +123,32 @@ static void uniquify_cies(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(isec.name))
|
||||
return false;
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
|
||||
ctx.arg.ignore_data_address_equality;
|
||||
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);
|
||||
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
|
||||
|
||||
if (shdr.sh_flags & SHF_EXECINSTR)
|
||||
return (ctx.arg.icf_all || !isec.address_taken) &&
|
||||
isec.name != ".init" && isec.name != ".fini";
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
}
|
||||
|
||||
// .gcc_except_table contains a compiler-generated table. Pointer
|
||||
// equality for the section is not significant because only the C++
|
||||
// exception handling code will use the table at runtime.
|
||||
if (isec.name == ".gcc_except_table" ||
|
||||
isec.name.starts_with(".gcc_except_table."))
|
||||
return true;
|
||||
static Digest digest_final(SHA256Hash &sha) {
|
||||
u8 buf[SHA256_SIZE];
|
||||
sha.finish(buf);
|
||||
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.name.starts_with(".data.rel.ro");
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
Digest digest;
|
||||
memcpy(digest.data(), buf, HASH_SIZE);
|
||||
return digest;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -157,6 +163,10 @@ static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
|
||||
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()(InputSection<E> *isec) const {
|
||||
@@ -199,7 +209,7 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
static Counter non_eligible("icf_non_eligibles");
|
||||
static Counter leaf("icf_leaf_nodes");
|
||||
|
||||
tbb::concurrent_unordered_map<InputSection<E> *, Atomic<InputSection<E> *>,
|
||||
tbb::concurrent_unordered_map<InputSection<E> *, InputSection<E> *,
|
||||
LeafHasher<E>, LeafEq<E>> map;
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
@@ -216,11 +226,8 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
leaf++;
|
||||
isec->icf_leaf = true;
|
||||
auto [it, inserted] = map.insert({isec.get(), isec.get()});
|
||||
if (!inserted) {
|
||||
InputSection<E> *isec2 = it->second.load();
|
||||
while (isec->get_priority() < isec2->get_priority() &&
|
||||
!it->second.compare_exchange_strong(isec2, isec.get()));
|
||||
}
|
||||
if (!inserted && isec->get_priority() < it->second->get_priority())
|
||||
it->second = isec.get();
|
||||
} else {
|
||||
eligible++;
|
||||
isec->icf_eligible = true;
|
||||
@@ -241,21 +248,21 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
SipHash13_128 hasher(hmac_key);
|
||||
SHA256Hash sha;
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
hasher.update((u8 *)&val, sizeof(val));
|
||||
sha.update((u8 *)&val, sizeof(val));
|
||||
};
|
||||
|
||||
auto hash_string = [&](std::string_view str) {
|
||||
hash(str.size());
|
||||
hasher.update((u8 *)str.data(), str.size());
|
||||
sha.update((u8 *)str.data(), str.size());
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
InputSection<E> *isec = sym.get_input_section();
|
||||
|
||||
if (!sym.file || sym.is_imported) {
|
||||
if (!sym.file) {
|
||||
hash('1');
|
||||
hash((u64)&sym);
|
||||
} else if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
@@ -293,7 +300,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
hash(rel.r_type);
|
||||
hash(rel.r_offset - fde.input_offset);
|
||||
hash(get_addend(isec.file.cies[fde.cie_idx].input_section, rel));
|
||||
hash(isec.file.cies[fde.cie_idx].input_section.get_addend(rel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,13 +308,11 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
hash(rel.r_offset);
|
||||
hash(rel.r_type);
|
||||
hash(get_addend(isec, rel));
|
||||
hash(isec.get_addend(rel));
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
|
||||
Digest digest;
|
||||
hasher.finish(digest.data());
|
||||
return digest;
|
||||
return digest_final(sha);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -396,19 +401,21 @@ static void gather_edges(Context<E> &ctx,
|
||||
InputSection<E> &isec = *sections[i];
|
||||
i64 idx = edge_indices[i];
|
||||
|
||||
for (ElfRel<E> &rel : isec.get_rels(ctx)) {
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->icf_eligible)
|
||||
edges[idx++] = isec->icf_idx;
|
||||
}
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->icf_eligible)
|
||||
edges[idx++] = isec->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, std::span<u8> converged,
|
||||
bool &slot, BitVector &converged,
|
||||
tbb::affinity_partitioner &ap) {
|
||||
static Counter round("icf_round");
|
||||
round++;
|
||||
@@ -417,24 +424,24 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
tbb::enumerable_thread_specific<i64> changed;
|
||||
|
||||
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
|
||||
if (converged[i])
|
||||
if (converged.get(i))
|
||||
return;
|
||||
|
||||
SipHash13_128 hasher(hmac_key);
|
||||
hasher.update(digests[2][i].data(), HASH_SIZE);
|
||||
SHA256Hash sha;
|
||||
sha.update(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))
|
||||
hasher.update(digests[slot][j].data(), HASH_SIZE);
|
||||
sha.update(digests[slot][j].data(), HASH_SIZE);
|
||||
|
||||
hasher.finish(digests[!slot][i].data());
|
||||
digests[!slot][i] = digest_final(sha);
|
||||
|
||||
if (digests[slot][i] == digests[!slot][i]) {
|
||||
// This node has converged. Skip further iterations as it will
|
||||
// yield the same hash.
|
||||
converged[i] = true;
|
||||
converged.set(i);
|
||||
} else {
|
||||
changed.local()++;
|
||||
}
|
||||
@@ -475,36 +482,28 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
tbb::parallel_sort(leaders.begin(), leaders.end(),
|
||||
[](InputSection<E> *a, InputSection<E> *b) {
|
||||
[&](InputSection<E> *a, InputSection<E> *b) {
|
||||
return a->get_priority() < b->get_priority();
|
||||
});
|
||||
|
||||
std::ostream *out = &std::cout;
|
||||
std::ofstream file;
|
||||
|
||||
std::string &path = ctx.arg.print_icf_sections;
|
||||
if (path != "-") {
|
||||
file.open(path);
|
||||
if (file.fail())
|
||||
Fatal(ctx) << "--print-icf-sections: cannot open " << path << ": "
|
||||
<< errno_string();
|
||||
out = &file;
|
||||
}
|
||||
|
||||
i64 saved_bytes = 0;
|
||||
|
||||
for (InputSection<E> *leader : leaders) {
|
||||
auto [begin, end] = map.equal_range(leader);
|
||||
if (begin != end) {
|
||||
*out << "selected section " << *leader << '\n';
|
||||
for (auto it = begin; it != end; it++) {
|
||||
*out << " removing identical section " << *it->second << '\n';
|
||||
saved_bytes += leader->contents.size();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
*out << "ICF saved " << saved_bytes << " bytes\n";
|
||||
SyncOut(ctx) << "ICF saved " << saved_bytes << " bytes";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -513,8 +512,6 @@ void icf_sections(Context<E> &ctx) {
|
||||
if (ctx.objs.empty())
|
||||
return;
|
||||
|
||||
get_random_bytes(hmac_key, sizeof(hmac_key));
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
|
||||
@@ -539,7 +536,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
std::vector<u32> edge_indices;
|
||||
gather_edges<E>(ctx, sections, edges, edge_indices);
|
||||
|
||||
std::vector<u8> converged(digests[0].size());
|
||||
BitVector converged(digests[0].size());
|
||||
bool slot = 0;
|
||||
|
||||
// Execute the propagation rounds until convergence is obtained.
|
||||
@@ -582,23 +579,18 @@ void icf_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group sections by hash values.
|
||||
// Group sections by SHA digest.
|
||||
{
|
||||
Timer t(ctx, "group");
|
||||
|
||||
auto *map =
|
||||
new tbb::concurrent_unordered_map<Digest, Atomic<InputSection<E> *>>;
|
||||
|
||||
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) {
|
||||
InputSection<E> *isec2 = it->second.load();
|
||||
while (isec->get_priority() < isec2->get_priority() &&
|
||||
!it->second.compare_exchange_strong(isec2, isec));
|
||||
}
|
||||
if (!inserted && isec->get_priority() < it->second->get_priority())
|
||||
it->second = isec;
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
@@ -611,21 +603,9 @@ void icf_sections(Context<E> &ctx) {
|
||||
ctx.on_exit.push_back([=] { delete map; });
|
||||
}
|
||||
|
||||
if (!ctx.arg.print_icf_sections.empty())
|
||||
if (ctx.arg.print_icf_sections)
|
||||
print_icf_sections(ctx);
|
||||
|
||||
// Update alignment of leaders.
|
||||
{
|
||||
Timer t(ctx, "update_alignment");
|
||||
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 && isec->leader != isec.get()) {
|
||||
update_maximum(isec->leader->p2align, isec->p2align);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Eliminate duplicate sections.
|
||||
// Symbols pointing to eliminated sections will be redirected on the fly when
|
||||
// exporting to the symtab.
|
||||
@@ -634,7 +614,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
static Counter eliminated("icf_eliminated");
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->icf_removed()) {
|
||||
if (isec && isec->is_alive && isec->is_killed_by_icf()) {
|
||||
isec->kill();
|
||||
eliminated++;
|
||||
}
|
||||
@@ -647,4 +627,4 @@ using E = MOLD_TARGET;
|
||||
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold
|
||||
} // namespace mold::elf
|
||||
+1559
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,313 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
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<const ElfRel<E>> x = get_rels();
|
||||
std::span<const 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;
|
||||
}
|
||||
|
||||
static i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
|
||||
if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
sh_size = chdr.ch_size;
|
||||
p2align = to_p2align(chdr.ch_addralign);
|
||||
} else {
|
||||
sh_size = shdr().sh_size;
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
}
|
||||
|
||||
// Sections may have been compressed. We usually uncompress them
|
||||
// directly into the mmap'ed output file, but we want to uncompress
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
if constexpr (!is_rela<E>)
|
||||
uncompress(ctx);
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress_to(ctx, buf);
|
||||
contents = std::string_view((char *)buf, sh_size);
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
if (contents.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
|
||||
|
||||
switch (hdr.ch_type) {
|
||||
case ELFCOMPRESS_ZLIB: {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": uncompress failed";
|
||||
assert(size == sh_size);
|
||||
break;
|
||||
}
|
||||
case ELFCOMPRESS_ZSTD:
|
||||
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
|
||||
Fatal(ctx) << *this << ": ZSTD_decompress failed";
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ScanAction get_rel_action(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ScanAction table[3][4]) {
|
||||
auto get_output_type = [&] {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto get_sym_type = [&] {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
};
|
||||
|
||||
return table[get_output_type()][get_sym_type()];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel,
|
||||
const ScanAction table[3][4]) {
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << *this << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
|
||||
auto check_textrel = [&] {
|
||||
if (this->shdr().sh_flags & SHF_WRITE)
|
||||
return;
|
||||
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
} else if (ctx.arg.warn_textrel) {
|
||||
Warn(ctx) << *this << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
}
|
||||
ctx.has_textrel = true;
|
||||
};
|
||||
|
||||
switch (get_rel_action(ctx, sym, table)) {
|
||||
case NONE:
|
||||
return;
|
||||
case ERROR:
|
||||
error();
|
||||
return;
|
||||
case COPYREL:
|
||||
if (!ctx.arg.z_copyreloc) {
|
||||
error();
|
||||
} else 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";
|
||||
}
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
return;
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
return;
|
||||
case CPLT:
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
return;
|
||||
case DYNREL:
|
||||
assert(sym.is_imported);
|
||||
check_textrel();
|
||||
this->file.num_dynrel++;
|
||||
return;
|
||||
case BASEREL:
|
||||
check_textrel();
|
||||
if (!this->is_relr_reloc(ctx, rel))
|
||||
this->file.num_dynrel++;
|
||||
return;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> *&dynrel,
|
||||
const ScanAction table[3][4]) {
|
||||
switch (get_rel_action(ctx, sym, table)) {
|
||||
case COPYREL:
|
||||
case CPLT:
|
||||
case NONE:
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case BASEREL:
|
||||
if (is_relr_reloc(ctx, rel)) {
|
||||
*(Word<E> *)loc = S + A;
|
||||
} else {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case DYNREL:
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = A;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
if constexpr (is_riscv<E>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else {
|
||||
uncompress_to(ctx, buf);
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
for (const ElfSym<E> &esym : file.elf_syms) {
|
||||
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
|
||||
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
|
||||
std::string_view name = file.symbol_strtab.data() + esym.st_name;
|
||||
if (ctx.arg.demangle)
|
||||
return demangle(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Record an undefined symbol error which will be displayed all at
|
||||
// once by report_undef_errors().
|
||||
template <typename E>
|
||||
void InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = file.get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
ss << ">>> " << file;
|
||||
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
|
||||
ss << ":(" << func << ")";
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
}
|
||||
|
||||
// Report all undefined symbols, grouped by symbol.
|
||||
template <typename E>
|
||||
void report_undef_errors(Context<E> &ctx) {
|
||||
constexpr i64 max_errors = 3;
|
||||
|
||||
for (auto &pair : ctx.undef_errors) {
|
||||
std::string_view sym_name = pair.first;
|
||||
std::span<std::string> errors = pair.second;
|
||||
|
||||
if (ctx.arg.demangle)
|
||||
sym_name = demangle(sym_name);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "undefined symbol: " << sym_name << "\n";
|
||||
|
||||
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
|
||||
ss << errors[i];
|
||||
|
||||
if (errors.size() > max_errors)
|
||||
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
Error(ctx) << ss.str();
|
||||
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
Warn(ctx) << ss.str();
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template struct CieRecord<E>;
|
||||
template class InputSection<E>;
|
||||
template void report_undef_errors(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,411 @@
|
||||
// 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))
|
||||
.string();
|
||||
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.size() > 3 && tok[1] == "=" && tok[3] == ";") {
|
||||
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
|
||||
get_symbol(ctx, unquote(tok[2])));
|
||||
tok = tok.subspan(4);
|
||||
} else if (tok[0] == ";") {
|
||||
tok = tok.subspan(1);
|
||||
} else {
|
||||
SyntaxError(ctx, tok[0]) << "unknown linker script token";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MachineType 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 MachineType::X86_64;
|
||||
if (tok[2] == "elf32-i386")
|
||||
return MachineType::I386;
|
||||
}
|
||||
return MachineType::NONE;
|
||||
}
|
||||
|
||||
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);
|
||||
ctx.default_version_from_version_script = true;
|
||||
} 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";
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template MachineType get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_version_script(Context<E> &, std::string);
|
||||
template void parse_dynamic_list(Context<E> &, std::string);
|
||||
|
||||
} // namespace mold::elf
|
||||
+141
-195
@@ -29,8 +29,8 @@
|
||||
//
|
||||
// 4. Continue the rest of the linking process as usual.
|
||||
//
|
||||
// When gcc or clang inovkes ld, they pass `-plugin /path/to/linker-plugin.so`
|
||||
// to the linker. The given .so file provides a way to call the compiler
|
||||
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
|
||||
// the linker. The given .so file provides a way to call the compiler
|
||||
// backend.
|
||||
//
|
||||
// The linker plugin API is documented at
|
||||
@@ -47,7 +47,7 @@
|
||||
// functions. `onload` calls callbacks to notify about the pointers to
|
||||
// other functions the linker plugin provides. I don't know why `onload`
|
||||
// can't just return a list of functions or why the linker plugin can't
|
||||
// define not only `onload` but other functions, but that's what it is.
|
||||
// define not only `onload` but other functions, but that's how it works.
|
||||
//
|
||||
// Here is the steps to use the linker plugin:
|
||||
//
|
||||
@@ -95,7 +95,7 @@
|
||||
# define LOG std::ostringstream()
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
namespace mold::elf {
|
||||
|
||||
// Global variables
|
||||
// We store LTO-related information to global variables,
|
||||
@@ -126,7 +126,7 @@ static PluginStatus message(PluginLevel level, const char *fmt, ...) {
|
||||
|
||||
switch (level) {
|
||||
case LDPL_INFO:
|
||||
Out(ctx) << buf;
|
||||
SyncOut(ctx) << buf;
|
||||
break;
|
||||
case LDPL_WARNING:
|
||||
Warn(ctx) << buf;
|
||||
@@ -176,15 +176,14 @@ static PluginStatus add_input_file(const char *path) {
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
static i64 file_priority = 100;
|
||||
|
||||
MappedFile *mf = must_open_file(ctx, path);
|
||||
mf->is_dependency = false;
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
|
||||
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, "");
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, "", false);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
lto_objects<E>.push_back(file);
|
||||
|
||||
file->priority = file_priority++;
|
||||
file->is_reachable = true;
|
||||
file->is_alive = true;
|
||||
file->parse(ctx);
|
||||
file->resolve_symbols(ctx);
|
||||
return LDPS_OK;
|
||||
@@ -201,7 +200,10 @@ static PluginStatus release_input_file(const void *handle) {
|
||||
LOG << "release_input_file\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
file.mf->close_fd();
|
||||
if (file.mf->fd != -1) {
|
||||
close(file.mf->fd);
|
||||
file.mf->fd = -1;
|
||||
}
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
@@ -268,8 +270,8 @@ get_symbols_v1(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
unreachable();
|
||||
}
|
||||
|
||||
// get_symbols teaches the LTO plugin as to how we have resolved symbols.
|
||||
// The plugin uses the symbol resolution info to optimize the program.
|
||||
// get_symbols teaches the LTO plugin as to how we resolved symbols.
|
||||
// The plugin uses the symbol resolution info to optimizes the program.
|
||||
//
|
||||
// For example, if a definition in an IR file is not referenced by
|
||||
// non-IR objects at all, the plugin may choose to completely inline
|
||||
@@ -285,7 +287,7 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
// If file is an archive member which was not chose to be included in
|
||||
// to the final result, we need to make the plugin to ignore all
|
||||
// symbols.
|
||||
if (!file.is_reachable) {
|
||||
if (!file.is_alive) {
|
||||
assert(!is_v2);
|
||||
for (int i = 0; i < nsyms; i++)
|
||||
psyms[i].resolution = LDPR_PREEMPTED_REG;
|
||||
@@ -306,8 +308,7 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
|
||||
if (sym.file->is_dso)
|
||||
return LDPR_RESOLVED_DYN;
|
||||
|
||||
if (((ObjectFile<E> *)sym.file)->is_lto_obj && !sym.is_wrapped)
|
||||
if (((ObjectFile<E> *)sym.file)->is_lto_obj)
|
||||
return esym.is_undef() ? LDPR_RESOLVED_IR : LDPR_PREEMPTED_IR;
|
||||
return esym.is_undef() ? LDPR_RESOLVED_EXEC : LDPR_PREEMPTED_REG;
|
||||
};
|
||||
@@ -343,7 +344,7 @@ static void restart_process(Context<E> &ctx) {
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_reachable)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
@@ -437,77 +438,72 @@ get_api_version(const char *plugin_identifier,
|
||||
return LAPI_V0;
|
||||
}
|
||||
|
||||
// dlopen the linker plugin file
|
||||
template <typename E>
|
||||
static void load_lto_plugin(Context<E> &ctx) {
|
||||
static std::once_flag flag;
|
||||
static void load_plugin(Context<E> &ctx) {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
|
||||
std::call_once(flag, [&] {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
});
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -536,6 +532,8 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
}
|
||||
|
||||
switch (psym.symbol_type) {
|
||||
case LDST_UNKNOWN:
|
||||
break;
|
||||
case LDST_FUNCTION:
|
||||
esym.st_type = STT_FUNC;
|
||||
break;
|
||||
@@ -545,6 +543,8 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
};
|
||||
|
||||
switch (psym.visibility) {
|
||||
case LDPV_DEFAULT:
|
||||
break;
|
||||
case LDPV_PROTECTED:
|
||||
esym.st_visibility = STV_PROTECTED;
|
||||
break;
|
||||
@@ -564,51 +564,34 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
// Returns false if it's GCC.
|
||||
template <typename E>
|
||||
static bool is_llvm(Context<E> &ctx) {
|
||||
return ctx.arg.plugin.find("LLVMgold.") != ctx.arg.plugin.npos;
|
||||
return ctx.arg.plugin.ends_with("LLVMgold.so");
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin supports the get_symbols_v3 API.
|
||||
// Any version of LLVM and GCC 12 or newer support it.
|
||||
template <typename E>
|
||||
static bool supports_v3_api(Context<E> &ctx) {
|
||||
static bool suppots_v3_api(Context<E> &ctx) {
|
||||
return is_gcc_linker_api_v1 || is_llvm(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginInputFile
|
||||
create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
|
||||
PluginInputFile file;
|
||||
MappedFile *mf2 = mf->parent ? mf->parent : mf;
|
||||
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
|
||||
mf2->reopen_fd(file.name);
|
||||
|
||||
file.fd = mf2->fd;
|
||||
|
||||
if (!file.fd)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": unable to handle this LTO object file because "
|
||||
<< "the -plugin option was not provided. Please make sure you "
|
||||
<< "added -flto not only when creating object files but also "
|
||||
<< "when linking the final executable.";
|
||||
|
||||
load_lto_plugin(ctx);
|
||||
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
// V0 API's claim_file is not thread-safe.
|
||||
static std::mutex mu;
|
||||
std::unique_lock lock(mu, std::defer_lock);
|
||||
if (!is_gcc_linker_api_v1)
|
||||
lock.lock();
|
||||
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
|
||||
<< "because no -plugin option was given. Please make sure you "
|
||||
<< "added -flto not only for creating object files but also for "
|
||||
<< "creating the final executable.";
|
||||
|
||||
// dlopen the linker plugin file
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&] { load_plugin(ctx); });
|
||||
|
||||
// Create mold's object instance
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
@@ -618,10 +601,23 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
obj->first_global = 1;
|
||||
obj->is_lto_obj = true;
|
||||
obj->mf = mf;
|
||||
obj->archive_name = mf->parent ? mf->parent->name : "";
|
||||
|
||||
// Create plugin's object instance
|
||||
PluginInputFile file = create_plugin_input_file(ctx, mf);
|
||||
PluginInputFile file = {};
|
||||
|
||||
MappedFile<Context<E>> *mf2 = mf->parent ? mf->parent : mf;
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
if (mf2->fd == -1)
|
||||
mf2->fd = open(file.name, O_RDONLY);
|
||||
file.fd = mf2->fd;
|
||||
if (file.fd == -1)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
|
||||
if (mf->parent)
|
||||
obj->archive_name = mf->parent->name;
|
||||
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
file.handle = (void *)obj;
|
||||
|
||||
LOG << "read_lto_symbols: "<< mf->name << "\n";
|
||||
@@ -629,111 +625,60 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
// claim_file_hook() calls add_symbols() which initializes `plugin_symbols`
|
||||
int claimed = false;
|
||||
claim_file_hook(&file, &claimed);
|
||||
if (!claimed)
|
||||
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin;"
|
||||
<< " please make sure you are using the same compiler of the"
|
||||
<< " same version for all object files";
|
||||
|
||||
if (mf->parent)
|
||||
mf->parent->close_fd();
|
||||
else
|
||||
mf->close_fd();
|
||||
|
||||
if (!claimed) {
|
||||
if (!mf->parent && !mf->thin_parent)
|
||||
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin;"
|
||||
<< " please make sure you are using the same compiler of the"
|
||||
<< " same version for all object files";
|
||||
return nullptr;
|
||||
// It looks like GCC doesn't need fd after claim_file_hook() while
|
||||
// LLVM needs it and takes the ownership of fd. To prevent "too many
|
||||
// open files" issue, we close fd only for GCC. This is ugly, though.
|
||||
if (!is_llvm(ctx)) {
|
||||
close(mf2->fd);
|
||||
mf2->fd = -1;
|
||||
}
|
||||
|
||||
// Create a symbol strtab
|
||||
i64 strtab_size = 1;
|
||||
for (PluginSymbol &psym : plugin_symbols)
|
||||
strtab_size += strlen(psym.name) + 1;
|
||||
std::string strtab(strtab_size, '\0');
|
||||
// Initialize object symbols
|
||||
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
|
||||
|
||||
// Initialize esyms
|
||||
obj->lto_elf_syms.resize(plugin_symbols.size() + 1);
|
||||
obj->lto_comdat_groups.resize(plugin_symbols.size() + 1);
|
||||
i64 strtab_offset = 1;
|
||||
|
||||
for (i64 i = 0; i < plugin_symbols.size(); i++) {
|
||||
PluginSymbol &psym = plugin_symbols[i];
|
||||
obj->lto_elf_syms[i + 1] = to_elf_sym<E>(psym);
|
||||
obj->lto_elf_syms[i + 1].st_name = strtab_offset;
|
||||
|
||||
i64 len = strlen(psym.name);
|
||||
memcpy(strtab.data() + strtab_offset, psym.name, len);
|
||||
strtab_offset += len + 1;
|
||||
|
||||
// comdat_key is non-null if the symbol is defined in a comdat member
|
||||
// section. We handle such symbols differently than comdat symbols in
|
||||
// a regular file because, unlike regular object files, IR files don't
|
||||
// have input sections.
|
||||
if (psym.comdat_key) {
|
||||
std::string_view key = save_string(ctx, psym.comdat_key);
|
||||
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, key);
|
||||
}
|
||||
for (PluginSymbol &psym : plugin_symbols) {
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
|
||||
}
|
||||
|
||||
obj->symbol_strtab = save_string(ctx, strtab);
|
||||
obj->elf_syms = obj->lto_elf_syms;
|
||||
obj->populate_symbol_names();
|
||||
obj->initialize_symbols(ctx);
|
||||
obj->elf_syms = *esyms;
|
||||
obj->symvers.resize(esyms->size());
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Entry point
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
|
||||
Timer t(ctx, "run_lto_plugin");
|
||||
load_lto_plugin(ctx);
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_lto");
|
||||
|
||||
if (!ctx.arg.lto_pass2 && !supports_v3_api(ctx))
|
||||
if (!ctx.arg.lto_pass2 && !suppots_v3_api(ctx))
|
||||
restart_process(ctx);
|
||||
|
||||
assert(phase == 1);
|
||||
phase = 2;
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (!file->is_lto_obj) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file && !sym->file->is_dso &&
|
||||
((ObjectFile<E> *)sym->file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->referenced_by_regular_obj = true;
|
||||
}
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file->is_lto_obj)
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
|
||||
if (sym.file && !sym.file->is_dso &&
|
||||
((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.referenced_by_regular_obj = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Symbols specified by the --wrap option needs to be visible from
|
||||
// regular object files.
|
||||
for (std::string_view name : ctx.arg.wrap) {
|
||||
get_symbol(ctx, name)->referenced_by_regular_obj = true;
|
||||
|
||||
std::string_view x = save_string(ctx, "__wrap_" + std::string(name));
|
||||
std::string_view y = save_string(ctx, "__real_" + std::string(name));
|
||||
|
||||
get_symbol(ctx, x)->referenced_by_regular_obj = true;
|
||||
get_symbol(ctx, y)->referenced_by_regular_obj = true;
|
||||
}
|
||||
|
||||
// Keep some symbols
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
sym->referenced_by_regular_obj = true;
|
||||
|
||||
// Object files containing .gnu.offload_lto_.* sections need to be
|
||||
// given to the LTO backend. Such sections contains code and data for
|
||||
// peripherails (typically GPUs).
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_reachable && !file->is_lto_obj && file->is_gcc_offload_obj) {
|
||||
PluginInputFile pfile = create_plugin_input_file(ctx, file->mf);
|
||||
int claimed = false;
|
||||
claim_file_hook(&pfile, &claimed);
|
||||
assert(!claimed);
|
||||
}
|
||||
}
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
if (PluginStatus st = all_symbols_read_hook(); st != LDPS_OK)
|
||||
@@ -745,14 +690,15 @@ std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {
|
||||
Timer t(ctx, "lto_cleanup");
|
||||
|
||||
if (cleanup_hook)
|
||||
cleanup_hook();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
|
||||
template std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &);
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
} // namespace mold
|
||||
} // namespace mold::elf
|
||||
@@ -1,15 +1,15 @@
|
||||
#include "mold.h"
|
||||
#include "lto.h"
|
||||
|
||||
namespace mold {
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ void lto_cleanup(Context<E> &ctx) {}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
|
||||
template std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &);
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
} // namespace mold
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifdef _WIN32
|
||||
# include "lto-win32.cc"
|
||||
#else
|
||||
# include "lto-unix.cc"
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../lib/integers.h"
|
||||
#include "../inttypes.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -55,7 +55,9 @@ enum PluginApiVersion {
|
||||
|
||||
struct PluginTagValue {
|
||||
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
|
||||
PluginTagValue(PluginTag tag, auto *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
|
||||
template <typename T>
|
||||
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
|
||||
PluginTag tag;
|
||||
union {
|
||||
@@ -73,11 +75,7 @@ enum PluginOutputFileType {
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
#if __MINGW32__
|
||||
HANDLE fd;
|
||||
#else
|
||||
int fd;
|
||||
#endif
|
||||
i32 fd;
|
||||
u64 offset;
|
||||
u64 filesize;
|
||||
void *handle;
|
||||
@@ -155,7 +153,7 @@ enum PluginLevel {
|
||||
};
|
||||
|
||||
enum PluginLinkerAPIVersion {
|
||||
LAPI_V0,
|
||||
LAPI_V0 = 0,
|
||||
LAPI_V1,
|
||||
};
|
||||
|
||||
+813
@@ -0,0 +1,813 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <direct.h>
|
||||
# define _chdir chdir
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386).
|
||||
template <typename E>
|
||||
static MachineType get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
auto get_elf_type = [&](u8 *buf) {
|
||||
bool is_le = (((EL32Ehdr *)buf)->e_ident[EI_DATA] == ELFDATA2LSB);
|
||||
bool is_64;
|
||||
u32 e_machine;
|
||||
|
||||
if (is_le) {
|
||||
EL32Ehdr &ehdr = *(EL32Ehdr *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
} else {
|
||||
EB32Ehdr &ehdr = *(EB32Ehdr *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
}
|
||||
|
||||
switch (e_machine) {
|
||||
case EM_386:
|
||||
return MachineType::I386;
|
||||
case EM_X86_64:
|
||||
return MachineType::X86_64;
|
||||
case EM_ARM:
|
||||
return MachineType::ARM32;
|
||||
case EM_AARCH64:
|
||||
return MachineType::ARM64;
|
||||
case EM_RISCV:
|
||||
if (is_le)
|
||||
return is_64 ? MachineType::RV64LE : MachineType::RV32LE;
|
||||
return is_64 ? MachineType::RV64BE : MachineType::RV32BE;
|
||||
case EM_PPC64:
|
||||
return is_le ? MachineType::PPC64V2 : MachineType::PPC64V1;
|
||||
case EM_S390X:
|
||||
return MachineType::S390X;
|
||||
case EM_SPARC64:
|
||||
return MachineType::SPARC64;
|
||||
case EM_68K:
|
||||
return MachineType::M68K;
|
||||
default:
|
||||
return MachineType::NONE;
|
||||
}
|
||||
};
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return MachineType::NONE;
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return MachineType::NONE;
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mf);
|
||||
default:
|
||||
return MachineType::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
MachineType mt = get_machine_type(ctx, mf);
|
||||
if (mt != ctx.arg.emulation)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: "
|
||||
<< ctx.arg.emulation << " is expected but got " << mt;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
|
||||
check_file_compatibility(ctx, mf);
|
||||
|
||||
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 ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_lto_objs");
|
||||
count++;
|
||||
|
||||
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
|
||||
return nullptr;
|
||||
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
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) {
|
||||
check_file_compatibility(ctx, mf);
|
||||
|
||||
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)) {
|
||||
switch (get_file_type(child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
|
||||
ctx.objs.push_back(file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, mf, ""))
|
||||
ctx.objs.push_back(file);
|
||||
return;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MachineType
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
if (MachineType ty = get_machine_type(ctx, mf); ty != MachineType::NONE)
|
||||
return ty;
|
||||
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;
|
||||
|
||||
MachineType ty = get_machine_type(ctx, mf);
|
||||
if (ty == MachineType::NONE || ty == E::machine_type)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << (int)ty
|
||||
<< " " << (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> 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 = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (arg == "--as-needed") {
|
||||
ctx.as_needed = true;
|
||||
} else if (arg == "--no-as-needed") {
|
||||
ctx.as_needed = false;
|
||||
} else if (arg == "--whole-archive") {
|
||||
ctx.whole_archive = true;
|
||||
} else if (arg == "--no-whole-archive") {
|
||||
ctx.whole_archive = false;
|
||||
} else if (arg == "--Bstatic") {
|
||||
ctx.is_static = true;
|
||||
} else if (arg == "--Bdynamic") {
|
||||
ctx.is_static = false;
|
||||
} else if (arg == "--start-lib") {
|
||||
ctx.in_lib = true;
|
||||
} else if (arg == "--end-lib") {
|
||||
ctx.in_lib = false;
|
||||
} else if (remove_prefix(arg, "--version-script=")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, VER_NDX_GLOBAL, false});
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
} else if (arg == "--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 (remove_prefix(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(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
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 (needs_thunk<E>) {
|
||||
static Counter thunk_bytes("thunk_bytes");
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
|
||||
thunk_bytes += thunk->size();
|
||||
}
|
||||
|
||||
Counter::print();
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &sec : ctx.merged_sections)
|
||||
sec->print_stats(ctx);
|
||||
}
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
template <typename E>
|
||||
static int redo_main(int argc, char **argv, MachineType ty) {
|
||||
switch (ty) {
|
||||
case MachineType::I386:
|
||||
return elf_main<I386>(argc, argv);
|
||||
case MachineType::ARM64:
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
case MachineType::ARM32:
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
case MachineType::RV64LE:
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
case MachineType::RV64BE:
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
case MachineType::RV32LE:
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
case MachineType::RV32BE:
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
case MachineType::PPC64V1:
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
case MachineType::PPC64V2:
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
case MachineType::S390X:
|
||||
return elf_main<S390X>(argc, argv);
|
||||
case MachineType::SPARC64:
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
case MachineType::M68K:
|
||||
return elf_main<M68K>(argc, argv);
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// Process -run option first. process_run_subcommand() does not return.
|
||||
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv)) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
Fatal(ctx) << "-run is supported only on Unix";
|
||||
#endif
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
}
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation == MachineType::NONE)
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if constexpr (std::is_same_v<E, X86_64>)
|
||||
if (ctx.arg.emulation != MachineType::X86_64)
|
||||
return redo_main<E>(argc, argv, ctx.arg.emulation);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
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();
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fork a subprocess unless --no-fork is given.
|
||||
std::function<void()> on_complete;
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
#endif
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
// 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;
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->traced = true;
|
||||
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
// 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 a dummy file containing linker-synthesized symbols.
|
||||
create_internal_file(ctx);
|
||||
|
||||
// resolve_symbols is 4 things in 1 phase:
|
||||
//
|
||||
// - Determine the set of object files to extract from archives.
|
||||
// - Remove redundant COMDAT sections (e.g. duplicate inline functions).
|
||||
// - Finally, the actual symbol resolution.
|
||||
// - LTO, which requires preliminary symbol resolution before running
|
||||
// and a follow-up re-resolution after the LTO objects are emitted.
|
||||
//
|
||||
// These passes have complex interactions, and unfortunately has to be
|
||||
// put together in a single phase.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
register_section_pieces(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_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(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);
|
||||
|
||||
// Create linker-synthesized sections such as .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Make sure that there's no duplicate symbol
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
if constexpr (std::is_same_v<E, PPC64V1>)
|
||||
ppc64v1_rewrite_opd(ctx);
|
||||
|
||||
// Bin input sections into output sections.
|
||||
bin_sections(ctx);
|
||||
|
||||
// Get a list of output sections.
|
||||
append(ctx.chunks, collect_output_sections(ctx));
|
||||
|
||||
// Add synthetic symbols such as __ehdr_start or __end.
|
||||
add_synthetic_symbols(ctx);
|
||||
|
||||
// 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);
|
||||
|
||||
// Handle `-z execstack-if-needed`.
|
||||
if (ctx.arg.z_execstack_if_needed)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->needs_executable_stack)
|
||||
ctx.arg.z_execstack = true;
|
||||
|
||||
// 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 symbols 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.
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(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);
|
||||
|
||||
// Likewise, .ctors and .dtors have to be sorted. They are rare
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// 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);
|
||||
|
||||
if constexpr (std::is_same_v<E, PPC64V1>)
|
||||
ppc64v1_scan_symbols(ctx);
|
||||
|
||||
// Scan relocations to find symbols that need entries in .got, .plt,
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_relocations(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_output_sections(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);
|
||||
|
||||
// Print reports about undefined symbols, if needed.
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
report_undef_errors(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
if (ctx.verdef)
|
||||
ctx.verdef->construct(ctx);
|
||||
|
||||
// Fill .gnu.version_r section contents.
|
||||
ctx.verneed->construct(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
create_output_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 construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// Update 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() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (ctx.chunks[i]->kind() != 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 RISC-V, branches are encode using multiple instructions so
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
// are close enough. Do this optimization.
|
||||
if constexpr (is_riscv<E>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
|
||||
// At this point, memory layout is fixed.
|
||||
|
||||
// Set actual addresses to linker-synthesized symbols.
|
||||
fix_synthetic_symbols(ctx);
|
||||
|
||||
// Beyond this, you can assume that symbol addresses including their
|
||||
// GOT or PLT addresses have a correct final value.
|
||||
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
|
||||
filesize = compress_debug_sections(ctx);
|
||||
|
||||
// At this point, both memory and file layouts are fixed.
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file =
|
||||
OutputFile<Context<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 and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(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);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
// entire output file. Now that we wrote everything except build-id,
|
||||
// we can compute it.
|
||||
if (ctx.buildid)
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
|
||||
t_copy.stop();
|
||||
ctx.checkpoint();
|
||||
|
||||
// Close the output file. This is the end of the linker's main job.
|
||||
ctx.output_file->close(ctx);
|
||||
|
||||
// Handle --dependency-file
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(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();
|
||||
ctx.checkpoint();
|
||||
return 0;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
|
||||
extern template int elf_main<I386>(int, char **);
|
||||
extern template int elf_main<ARM32>(int, char **);
|
||||
extern template int elf_main<ARM64>(int, char **);
|
||||
extern template int elf_main<RV32BE>(int, char **);
|
||||
extern template int elf_main<RV32LE>(int, char **);
|
||||
extern template int elf_main<RV64LE>(int, char **);
|
||||
extern template int elf_main<RV64BE>(int, char **);
|
||||
extern template int elf_main<PPC64V1>(int, char **);
|
||||
extern template int elf_main<PPC64V2>(int, char **);
|
||||
extern template int elf_main<S390X>(int, char **);
|
||||
extern template int elf_main<SPARC64>(int, char **);
|
||||
extern template int elf_main<M68K>(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -4,27 +4,38 @@
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <sstream>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mold {
|
||||
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->get_type() != STT_SECTION) {
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
typename Map<E>::accessor acc;
|
||||
map.insert(acc, {isec, {}});
|
||||
acc->second.push_back(sym);
|
||||
}
|
||||
if (sym->file != file || sym->get_type() == STT_SECTION)
|
||||
continue;
|
||||
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
assert(file == &isec->file);
|
||||
typename Map<E>::accessor acc;
|
||||
map.insert(acc, {isec, {}});
|
||||
acc->second.push_back(sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -33,25 +44,22 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
return map;
|
||||
|
||||
tbb::parallel_for(map.range(), [](const typename Map<E>::range_type &range) {
|
||||
for (auto &[k, v] : range)
|
||||
ranges::stable_sort(v, {}, &Symbol<E>::value);
|
||||
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) {
|
||||
Timer t(ctx, "print_map");
|
||||
|
||||
std::ostream *out = &std::cout;
|
||||
std::ofstream file;
|
||||
std::unique_ptr<std::ofstream> file;
|
||||
|
||||
if (!ctx.arg.Map.empty() && ctx.arg.Map != "-") {
|
||||
file.open(ctx.arg.Map);
|
||||
if (file.fail())
|
||||
Fatal(ctx) << "--print-map: cannot open " << ctx.arg.Map << ": "
|
||||
<< errno_string();
|
||||
out = &file;
|
||||
if (!ctx.arg.Map.empty()) {
|
||||
file = open_output_file(ctx);
|
||||
out = file.get();
|
||||
}
|
||||
|
||||
// Construct a section-to-symbol map.
|
||||
@@ -60,32 +68,29 @@ void print_map(Context<E> &ctx) {
|
||||
// Print a mapfile.
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
for (Chunk<E> *osec : ctx.chunks) {
|
||||
*out << std::showbase
|
||||
<< std::setw(18) << std::hex << (u64)chunk->shdr.sh_addr << std::dec
|
||||
<< std::setw(11) << (u64)chunk->shdr.sh_size
|
||||
<< std::setw(6) << (u64)chunk->shdr.sh_addralign
|
||||
<< " " << chunk->name << "\n";
|
||||
<< std::setw(18) << std::hex << (u64)osec->shdr.sh_addr << std::dec
|
||||
<< std::setw(11) << (u64)osec->shdr.sh_size
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
|
||||
OutputSection<E> *osec = chunk->to_osec();
|
||||
if (!osec)
|
||||
if (osec->kind() != OUTPUT_SECTION)
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = osec->members;
|
||||
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;
|
||||
|
||||
u64 addr = 0;
|
||||
if (osec->shdr.sh_flags & SHF_ALLOC)
|
||||
addr = osec->shdr.sh_addr + mem->offset;
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
u64 addr = osec->shdr.sh_addr + mem->offset;
|
||||
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << addr << std::dec
|
||||
<< std::setw(11) << (u64)mem->sh_size
|
||||
<< std::setw(6) << (1 << mem->p2align)
|
||||
<< std::setw(6) << (1 << (u64)mem->p2align)
|
||||
<< " " << *mem << "\n";
|
||||
|
||||
typename Map<E>::const_accessor acc;
|
||||
@@ -108,4 +113,4 @@ using E = MOLD_TARGET;
|
||||
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
} // namespace mold
|
||||
} // namespace mold::elf
|
||||
@@ -1,5 +1,8 @@
|
||||
#define _GNU_SOURCE 1
|
||||
|
||||
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
#include <dlfcn.h>
|
||||
#include <spawn.h>
|
||||
#include <stdarg.h>
|
||||
@@ -9,10 +12,6 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if __has_include(<alloca.h>)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static char *get_mold_path() {
|
||||
@@ -62,8 +61,7 @@ static bool is_ld(const char *path) {
|
||||
ptr--;
|
||||
|
||||
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd") ||
|
||||
!strcmp(ptr, "ld.mold");
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
@@ -133,14 +131,3 @@ int posix_spawn(pid_t *pid, const char *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);
|
||||
}
|
||||
+2804
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+2193
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,595 @@
|
||||
// This file implements the -r or ---relocatable option. If the option
|
||||
// is given to a linker, the linker creates not an executable nor a
|
||||
// shared library file but instead create another object file by
|
||||
// combining input object files.
|
||||
//
|
||||
// The behavior of a linker with -r is pretty different from the one
|
||||
// without -r, and adding code for -r to the regular code path could
|
||||
// severely complicate it. Therefore, we implement -r as an
|
||||
// independent feature from others. That's why this file share only a
|
||||
// small amount of code with other files.
|
||||
//
|
||||
// Since the -r option is a minor feature, we don't aim for speed in
|
||||
// this file. This is a "better than nothing" implementation of the -r
|
||||
// option.
|
||||
//
|
||||
// Here is the strategy as to how to combine multiple object files
|
||||
// into one:
|
||||
//
|
||||
// - Regular sections containing opaque data (e.g. ".text" or
|
||||
// ".data") are just copied as-is without merging.
|
||||
//
|
||||
// - .symtab, .strtab and .shstrtab are merged.
|
||||
//
|
||||
// - COMDAT groups are uniquified.
|
||||
//
|
||||
// - Relocation sections are copied one by one, but we need to fix
|
||||
// symbol indices.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E> class RObjectFile;
|
||||
|
||||
template <typename E>
|
||||
class RChunk {
|
||||
public:
|
||||
RChunk() {
|
||||
out_shdr.sh_addralign = 1;
|
||||
}
|
||||
|
||||
virtual ~RChunk() = default;
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx) = 0;
|
||||
|
||||
std::string_view name;
|
||||
i64 shndx = 0;
|
||||
ElfShdr<E> in_shdr = {};
|
||||
ElfShdr<E> out_shdr = {};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RInputSection : public RChunk<E> {
|
||||
public:
|
||||
RInputSection(Context<E> &ctx, RObjectFile<E> &file, const ElfShdr<E> &shdr);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
RObjectFile<E> &file;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RSymtabSection : public RChunk<E> {
|
||||
public:
|
||||
RSymtabSection() {
|
||||
this->name = ".symtab";
|
||||
this->out_shdr.sh_type = SHT_SYMTAB;
|
||||
this->out_shdr.sh_entsize = sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_addralign = sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void add_global_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> sym_map;
|
||||
std::vector<ElfSym<E>> syms{1};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RStrtabSection : public RChunk<E> {
|
||||
public:
|
||||
RStrtabSection(std::string_view name) {
|
||||
this->name = name;
|
||||
this->out_shdr.sh_type = SHT_STRTAB;
|
||||
this->out_shdr.sh_size = 1;
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> strings;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputEhdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputEhdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfEhdr<E>);
|
||||
}
|
||||
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputShdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputShdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RObjectFile {
|
||||
public:
|
||||
RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf, bool is_alive);
|
||||
|
||||
void remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups);
|
||||
|
||||
template <typename T>
|
||||
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
|
||||
MappedFile<Context<E>> &mf;
|
||||
std::span<ElfShdr<E>> elf_sections;
|
||||
std::vector<std::unique_ptr<RInputSection<E>>> sections;
|
||||
std::span<const ElfSym<E>> syms;
|
||||
std::vector<i64> symidx;
|
||||
std::unordered_set<std::string_view> defined_syms;
|
||||
std::unordered_set<std::string_view> undef_syms;
|
||||
i64 symtab_shndx = 0;
|
||||
i64 first_global = 0;
|
||||
bool is_alive;
|
||||
const char *strtab = nullptr;
|
||||
const char *shstrtab = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_local_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind == STB_LOCAL);
|
||||
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common()) {
|
||||
if (!file.sections[sym.st_shndx])
|
||||
return;
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
}
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_global_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind != STB_LOCAL);
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
auto [it, inserted] = sym_map.insert({name, syms.size()});
|
||||
|
||||
if (inserted) {
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common())
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
file.symidx[idx] = it->second;
|
||||
|
||||
ElfSym<E> &existing = syms[it->second];
|
||||
if (existing.is_undef() && !sym.is_undef()) {
|
||||
if (!sym.is_abs() && !sym.is_common())
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
sym.st_name = existing.st_name;
|
||||
existing = sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
this->out_shdr.sh_size = syms.size() * sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_link = ctx.r_strtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::write_to(Context<E> &ctx) {
|
||||
ElfSym<E> *buf = (ElfSym<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < syms.size(); i++)
|
||||
buf[i] = syms[i];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RInputSection<E>::RInputSection(Context<E> &ctx, RObjectFile<E> &file,
|
||||
const ElfShdr<E> &shdr)
|
||||
: file(file) {
|
||||
this->name = file.shstrtab + shdr.sh_name;
|
||||
this->in_shdr = shdr;
|
||||
this->out_shdr = shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::update_shdr(Context<E> &ctx) {
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.symidx[this->in_shdr.sh_info];
|
||||
break;
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.sections[this->in_shdr.sh_info]->shndx;
|
||||
break;
|
||||
default:
|
||||
if (this->in_shdr.sh_link) {
|
||||
std::unique_ptr<RInputSection<E>> &sec =
|
||||
file.sections[this->in_shdr.sh_info];
|
||||
|
||||
if (sec)
|
||||
this->out_shdr.sh_link = sec->shndx;
|
||||
else if (this->in_shdr.sh_link == file.symtab_shndx)
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::write_to(Context<E> &ctx) {
|
||||
if (this->in_shdr.sh_type == SHT_NOBITS)
|
||||
return;
|
||||
|
||||
std::span<u8> contents = file.template get_data<u8>(ctx, this->in_shdr);
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset, contents.data(), contents.size());
|
||||
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP: {
|
||||
U32<E> *mem = (U32<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < this->out_shdr.sh_size / sizeof(u32); i++)
|
||||
mem[i] = file.sections[mem[i]]->shndx;
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
case SHT_RELA: {
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
i64 size = this->out_shdr.sh_size / sizeof(ElfRel<E>);
|
||||
|
||||
for (i64 i = 0; i < size; i++) {
|
||||
const ElfSym<E> &sym = file.syms[rel[i].r_sym];
|
||||
if (sym.is_undef() || sym.is_abs() || sym.is_common() ||
|
||||
file.sections[sym.st_shndx])
|
||||
rel[i].r_sym = file.symidx[rel[i].r_sym];
|
||||
else
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
}
|
||||
|
||||
i64 i = 0;
|
||||
i64 j = 0;
|
||||
for (; j < size; j++)
|
||||
if (rel[j].r_type)
|
||||
rel[i++] = rel[j];
|
||||
for (; i < size; i++)
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 RStrtabSection<E>::add_string(std::string_view str) {
|
||||
auto [it, inserted] = strings.insert({str, this->out_shdr.sh_size});
|
||||
if (inserted)
|
||||
this->out_shdr.sh_size += str.size() + 1;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RStrtabSection<E>::write_to(Context<E> &ctx) {
|
||||
for (auto [str, offset] : strings)
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset + offset, str.data(), str.size());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputEhdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfEhdr<E> &hdr = *(ElfEhdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
memcpy(&hdr.e_ident, "\177ELF", 4);
|
||||
hdr.e_ident[EI_CLASS] = E::is_64 ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_DATA] = E::is_le ? ELFDATA2LSB : ELFDATA2MSB;
|
||||
hdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
hdr.e_type = ET_REL;
|
||||
hdr.e_machine = E::e_machine;
|
||||
hdr.e_version = EV_CURRENT;
|
||||
hdr.e_shoff = ctx.r_shdr->out_shdr.sh_offset;
|
||||
hdr.e_ehsize = sizeof(ElfEhdr<E>);
|
||||
hdr.e_shentsize = sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
hdr.e_shnum = ctx.r_shdr->out_shdr.sh_size / sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
this->out_shdr.sh_size += sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfShdr<E> *hdr = (ElfShdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
hdr[chunk->shndx] = chunk->out_shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RObjectFile<E>::RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf,
|
||||
bool is_alive)
|
||||
: mf(mf), is_alive(is_alive) {
|
||||
// Read ELF header and section header
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mf.data;
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mf.data + ehdr.e_shoff);
|
||||
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
|
||||
elf_sections = {sh_begin, sh_begin + num_sections};
|
||||
sections.resize(num_sections);
|
||||
|
||||
// Read .shstrtab
|
||||
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
shstrtab = (char *)(mf.data + elf_sections[shstrtab_idx].sh_offset);
|
||||
|
||||
// Read .symtab
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
if (shdr.sh_type == SHT_SYMTAB) {
|
||||
syms = get_data<const ElfSym<E>>(ctx, shdr);
|
||||
strtab = (char *)(mf.data + elf_sections[shdr.sh_link].sh_offset);
|
||||
symtab_shndx = i;
|
||||
first_global = shdr.sh_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
symidx.resize(syms.size());
|
||||
|
||||
// Read sections
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
switch (shdr.sh_type) {
|
||||
case SHT_NULL:
|
||||
case SHT_SYMTAB:
|
||||
case SHT_STRTAB:
|
||||
break;
|
||||
default:
|
||||
sections[i].reset(new RInputSection(ctx, *this, shdr));
|
||||
}
|
||||
}
|
||||
|
||||
// Read global symbols
|
||||
for (i64 i = first_global; i < syms.size(); i++) {
|
||||
std::string_view name = strtab + syms[i].st_name;
|
||||
if (syms[i].is_undef())
|
||||
undef_syms.insert(name);
|
||||
else
|
||||
defined_syms.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
template <typename E>
|
||||
void RObjectFile<E>::remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups) {
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
if (!sections[i])
|
||||
continue;
|
||||
ElfShdr<E> &shdr = sections[i]->in_shdr;
|
||||
if (shdr.sh_type != SHT_GROUP)
|
||||
continue;
|
||||
|
||||
// Get a comdat group signature and insert it into a set.
|
||||
const ElfSym<E> &sym = syms[shdr.sh_info];
|
||||
std::string_view signature = strtab + sym.st_name;
|
||||
if (groups.insert(signature).second)
|
||||
continue;
|
||||
|
||||
// If it is a duplicate, remove it and its members.
|
||||
for (i64 j : this->template get_data<U32<E>>(ctx, shdr).subspan(1))
|
||||
sections[j] = nullptr;
|
||||
sections[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
template <typename T>
|
||||
std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
T *begin = (T *)(mf.data + shdr.sh_offset);
|
||||
T *end = (T *)(mf.data + shdr.sh_offset + shdr.sh_size);
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::unique_ptr<RObjectFile<E>>>
|
||||
open_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files;
|
||||
bool whole_archive = false;
|
||||
|
||||
while (!args.empty()) {
|
||||
const std::string &arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (arg == "--whole-archive") {
|
||||
whole_archive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--no-whole-archive") {
|
||||
whole_archive = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.starts_with("--version-script=") ||
|
||||
arg.starts_with("--dynamic-list=") ||
|
||||
arg.starts_with("--export-dynamic-symbol=") ||
|
||||
arg.starts_with("--export-dynamic-symbol-list="))
|
||||
continue;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
|
||||
if (arg == "-l") {
|
||||
mf = find_library(ctx, arg.substr(2));
|
||||
} else {
|
||||
if (arg.starts_with('-'))
|
||||
continue;
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, arg);
|
||||
}
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *mf, true));
|
||||
break;
|
||||
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)
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *child, whole_archive));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
i64 offset = 0;
|
||||
for (RChunk<E> *chunk : ctx.r_chunks) {
|
||||
offset = align_to(offset, chunk->out_shdr.sh_addralign);
|
||||
chunk->out_shdr.sh_offset = offset;
|
||||
offset += chunk->out_shdr.sh_size;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool contains(std::unordered_set<std::string_view> &a,
|
||||
std::unordered_set<std::string_view> &b) {
|
||||
for (std::string_view x : b)
|
||||
if (a.contains(x))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string> file_args) {
|
||||
// Read object files
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
|
||||
|
||||
// Identify needed objects
|
||||
std::unordered_set<std::string_view> undef_syms;
|
||||
auto add_syms = [&](RObjectFile<E> &file) {
|
||||
undef_syms.insert(file.undef_syms.begin(), file.undef_syms.end());
|
||||
for (std::string_view name : file.defined_syms)
|
||||
undef_syms.erase(name);
|
||||
file.is_alive = true;
|
||||
};
|
||||
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
if (file->is_alive)
|
||||
add_syms(*file);
|
||||
|
||||
for (;;) {
|
||||
bool added = false;
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files) {
|
||||
if (!file->is_alive && contains(undef_syms, file->defined_syms)) {
|
||||
add_syms(*file);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
if (!added)
|
||||
break;
|
||||
}
|
||||
|
||||
std::erase_if(files, [](std::unique_ptr<RObjectFile<E>> &file) {
|
||||
return !file->is_alive;
|
||||
});
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
std::unordered_set<std::string_view> comdat_groups;
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
file->remove_comdats(ctx, comdat_groups);
|
||||
|
||||
// Create headers and linker-synthesized sections
|
||||
ROutputEhdr<E> ehdr;
|
||||
ROutputShdr<E> shdr;
|
||||
RSymtabSection<E> symtab;
|
||||
RStrtabSection<E> shstrtab(".shstrtab");
|
||||
RStrtabSection<E> strtab(".strtab");
|
||||
|
||||
ctx.r_chunks.push_back(&ehdr);
|
||||
ctx.r_chunks.push_back(&shstrtab);
|
||||
ctx.r_chunks.push_back(&strtab);
|
||||
|
||||
ctx.r_ehdr = &ehdr;
|
||||
ctx.r_shdr = &shdr;
|
||||
ctx.r_shstrtab = &shstrtab;
|
||||
ctx.r_strtab = &strtab;
|
||||
ctx.r_symtab = &symtab;
|
||||
|
||||
// Add input sections to output sections
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (std::unique_ptr<RInputSection<E>> &sec : file->sections)
|
||||
if (sec)
|
||||
ctx.r_chunks.push_back(sec.get());
|
||||
|
||||
ctx.r_chunks.push_back(&symtab);
|
||||
ctx.r_chunks.push_back(&shdr);
|
||||
|
||||
// Assign output section indices
|
||||
i64 shndx = 1;
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk != &ehdr && chunk != &shdr)
|
||||
chunk->shndx = shndx++;
|
||||
|
||||
// Add section names to .shstrtab
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
chunk->out_shdr.sh_name = shstrtab.add_string(chunk->name);
|
||||
|
||||
// Copy symbols from input objects to an output object
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = 1; i < file->first_global; i++)
|
||||
symtab.add_local_symbol(ctx, *file, i);
|
||||
|
||||
symtab.out_shdr.sh_info = symtab.syms.size();
|
||||
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = file->first_global; i < file->syms.size(); i++)
|
||||
symtab.add_global_symbol(ctx, *file, i);
|
||||
|
||||
// Finalize section header
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Open an output file
|
||||
i64 filesize = assign_offsets(ctx);
|
||||
std::unique_ptr<OutputFile<Context<E>>> out =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
memset(out->buf, 0, filesize);
|
||||
ctx.buf = out->buf;
|
||||
|
||||
// Write to the output file
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->write_to(ctx);
|
||||
out->close(ctx);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void combine_objects(Context<E> &, std::span<std::string>);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,23 +1,23 @@
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static int pipe_write_fd = -1;
|
||||
namespace mold::elf {
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
// Exiting from a program with large memory usage is slow --
|
||||
// it may take a few hundred milliseconds. To hide the latency,
|
||||
// we fork a child and let it do the actual linking work.
|
||||
template <typename E>
|
||||
void fork_child() {
|
||||
std::function<void()> fork_child() {
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1) {
|
||||
perror("pipe");
|
||||
@@ -50,19 +50,14 @@ void fork_child() {
|
||||
|
||||
// Child
|
||||
close(pipefd[0]);
|
||||
pipe_write_fd = pipefd[1];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void notify_parent() {
|
||||
if (pipe_write_fd == -1)
|
||||
return;
|
||||
|
||||
char buf[] = {1};
|
||||
[[maybe_unused]] int n = write(pipe_write_fd, buf, 1);
|
||||
assert(n == 1);
|
||||
pipe_write_fd = -1;
|
||||
return [=] {
|
||||
char buf[] = {1};
|
||||
[[maybe_unused]] int n = write(pipefd[1], buf, 1);
|
||||
assert(n == 1);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename E>
|
||||
static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
@@ -89,9 +84,6 @@ static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
template <typename E>
|
||||
[[noreturn]]
|
||||
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
#ifdef __APPLE__
|
||||
Fatal(ctx) << "-run is not supported on macOS";
|
||||
#else
|
||||
assert(argv[1] == "-run"s || argv[1] == "--run"s);
|
||||
|
||||
if (!argv[2])
|
||||
@@ -102,11 +94,11 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
std::string dso_path = find_dso(ctx, self);
|
||||
|
||||
// Set environment variables
|
||||
setenv("LD_PRELOAD", dso_path.c_str(), 1);
|
||||
setenv("MOLD_PATH", self.c_str(), 1);
|
||||
putenv(strdup(("LD_PRELOAD=" + dso_path).c_str()));
|
||||
putenv(strdup(("MOLD_PATH=" + self).c_str()));
|
||||
|
||||
// If ld, ld.lld or ld.gold is specified, run mold instead
|
||||
if (std::string cmd = path_filename(argv[2]);
|
||||
// If ld, ld.lld or ld.gold is specified, run mold itself
|
||||
if (std::string cmd = filepath(argv[2]).filename();
|
||||
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
|
||||
std::vector<char *> args;
|
||||
args.push_back(argv[0]);
|
||||
@@ -119,13 +111,12 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
// Execute a given command
|
||||
execvp(argv[2], argv + 2);
|
||||
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
|
||||
#endif
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void fork_child<E>();
|
||||
template void notify_parent<E>();
|
||||
template void process_run_subcommand(Context<E> &, int, char **);
|
||||
|
||||
} // namespace mold
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
// RISC instructions are usually up to 4 bytes long, so the immediates of
|
||||
// their branch instructions are naturally smaller than 32 bits. This is
|
||||
// contrary to x86-64 on which branch instructions take 4 bytes immediates
|
||||
// and can jump to anywhere within PC ± 2 GiB.
|
||||
//
|
||||
// In fact, ARM32's branch instructions can jump only within ±16 MiB and
|
||||
// ARM64's ±128 MiB, for example. 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".
|
||||
//
|
||||
// The function in this file creates thunks.
|
||||
//
|
||||
// Note that although thunks play an important role in an executable, they
|
||||
// don't take up too much space in it. For example, among the clang-16's
|
||||
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
|
||||
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
|
||||
// need more thunks on ARM32 whose branch range is shorter than ARM64.
|
||||
// That said, the total size of thunks still isn't that much. Therefore,
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Branch reach in bytes.
|
||||
//
|
||||
// ARM64's branch has 26 bits immediate, and it's scaled by 4 because all
|
||||
// instructions are 4 bytes aligned, so it's effectively 28 bits long.
|
||||
// That means the range is [-2^27, 2^27).
|
||||
//
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
// aligned to 2, so it's effectively 25 bits. ARM32's non-Thumb branches
|
||||
// have twice longer range than its Thumb counterparts, but we
|
||||
// conservatively use the Thumb's limitation.
|
||||
//
|
||||
// PPC64's branch has 24 bits immediate, and the instructions are aligned
|
||||
// to 4, therefore 26.
|
||||
//
|
||||
// Here is the summary of branch instructions reaches:
|
||||
//
|
||||
// ARM64: PC ± 128 MiB
|
||||
// ARM32: PC ± 16 MiB
|
||||
// PPC64: PC ± 32 MiB
|
||||
template <typename E>
|
||||
static constexpr i64 max_distance =
|
||||
1LL << (std::is_same_v<E, ARM64> ? 27 : std::is_same_v<E, ARM32> ? 24 : 25);
|
||||
|
||||
// We create thunks for each 12.8/1.6/3.2 MiB code block for
|
||||
// ARM64/ARM32/PPC64, respectively.
|
||||
template <typename E>
|
||||
static constexpr i64 batch_size = max_distance<E> / 10;
|
||||
|
||||
// We assume that a single thunk group is smaller than 100 KiB.
|
||||
static constexpr i64 max_thunk_size = 102400;
|
||||
|
||||
// Returns true if a given relocation is of type used for function calls.
|
||||
template <typename E>
|
||||
static bool needs_thunk_rel(const ElfRel<E> &r) {
|
||||
u32 ty = r.r_type;
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM64>) {
|
||||
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
|
||||
} else if constexpr (std::is_same_v<E, ARM32>) {
|
||||
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
|
||||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL;
|
||||
} else {
|
||||
static_assert(is_ppc<E>);
|
||||
return ty == R_PPC64_REL24;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
// Even if the target is the same section, we branch to its PLT
|
||||
// if it has one. So a symbol with a PLT is also considered an
|
||||
// out-of-section reference.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
// If the target section is in the same output section but
|
||||
// hasn't got any address yet, that's unreacahble.
|
||||
if (isec2->offset == -1)
|
||||
return false;
|
||||
|
||||
// Thumb and ARM B instructions cannot be converted to BX, so we
|
||||
// always have to make them jump to a thunk to switch processor mode
|
||||
// even if their destinations are within their ranges.
|
||||
if constexpr (std::is_same_v<E, ARM32>) {
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
|
||||
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, NO_OPD);
|
||||
i64 A = isec.get_addend(rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -max_distance<E> <= val && val < max_distance<E>;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
sym->flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Scan relocations to collect symbols that need thunks.
|
||||
template <typename E>
|
||||
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
RangeExtensionThunk<E> &thunk) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, isec, sym, rel))
|
||||
continue;
|
||||
|
||||
// This relocation needs a thunk. If the symbol is already in a
|
||||
// previous thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i].thunk_idx = sym.extra.thunk_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to the current thunk if it's not
|
||||
// added already.
|
||||
range_extn[i].thunk_idx = thunk.thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> m = osec.members;
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
m[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)m.size(), [&](i64 i) {
|
||||
m[i]->offset = -1;
|
||||
});
|
||||
|
||||
// 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 < m.size()) {
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk after D.
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance<E>) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by BATCH_SIZE. We want
|
||||
// to make sure that there's at least one section between B and C
|
||||
// to ensure progress.
|
||||
c = b + 1;
|
||||
while (c < m.size() &&
|
||||
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size<E>)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset + max_distance<E> < c_offset)
|
||||
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;
|
||||
offset = align_to(offset, thunk.alignment);
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(&m[b], &m[c], [&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, thunk);
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
assert(thunk.size() < max_thunk_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 std::tuple{a->file->priority, a->sym_idx} <
|
||||
std::tuple{b->file->priority, b->sym_idx};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(&m[b], &m[c], [&](InputSection<E> *isec) {
|
||||
std::span<Symbol<E> *> syms = isec->file.symbols;
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next batch.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
#if defined(MOLD_ARM64) || defined(MOLD_ARM32) || \
|
||||
defined(MOLD_PPC64V1) || defined(MOLD_PPC64V2)
|
||||
using E = MOLD_TARGET;
|
||||
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::string ret = std::filesystem::canonical(path, ec).string();
|
||||
return ec ? std::string(path) : ret;
|
||||
}
|
||||
|
||||
// Removes redundant '/..' or '/.' from a given path.
|
||||
// The transformation is done purely by lexical processing.
|
||||
// This function does not access file system.
|
||||
std::string path_clean(std::string_view path) {
|
||||
return filepath(path).lexically_normal().string();
|
||||
}
|
||||
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
if (path.is_absolute())
|
||||
return path.lexically_normal();
|
||||
return (std::filesystem::current_path() / path).lexically_normal();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "elf/elf.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum class FileType {
|
||||
UNKNOWN,
|
||||
EMPTY,
|
||||
ELF_OBJ,
|
||||
ELF_DSO,
|
||||
MACH_OBJ,
|
||||
MACH_EXE,
|
||||
MACH_DYLIB,
|
||||
MACH_BUNDLE,
|
||||
MACH_UNIVERSAL,
|
||||
AR,
|
||||
THIN_AR,
|
||||
TAPI,
|
||||
TEXT,
|
||||
GCC_LTO_OBJ,
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
bool is_text_file(MappedFile<C> *mf) {
|
||||
u8 *data = mf->data;
|
||||
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
using namespace mold::elf;
|
||||
|
||||
const char *data = mf->get_contents().data();
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
|
||||
|
||||
for (ElfShdr<E> &sec : shdrs) {
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
continue;
|
||||
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
auto skip = [](u8 type) {
|
||||
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
|
||||
};
|
||||
|
||||
// GCC LTO object contains only sections symbols followed by a common
|
||||
// symbol whose name is `__gnu_lto_v1` or `__gnu_lto_slim`.
|
||||
i64 i = 1;
|
||||
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
|
||||
i++;
|
||||
|
||||
if (i < elf_syms.size() && elf_syms[i].st_shndx == SHN_COMMON) {
|
||||
std::string_view name =
|
||||
data + shdrs[sec.sh_link].sh_offset + elf_syms[i].st_name;
|
||||
if (name.starts_with("__gnu_lto_"))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf) {
|
||||
std::string_view data = mf->get_contents();
|
||||
|
||||
if (data.empty())
|
||||
return FileType::EMPTY;
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
u8 byte_order = ((elf::EL32Ehdr *)data.data())->e_ident[elf::EI_DATA];
|
||||
|
||||
if (byte_order == elf::ELFDATA2LSB) {
|
||||
elf::EL32Ehdr &ehdr = *(elf::EL32Ehdr *)data.data();
|
||||
|
||||
if (ehdr.e_type == elf::ET_REL) {
|
||||
if (ehdr.e_ident[elf::EI_CLASS] == elf::ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<elf::I386>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<elf::X86_64>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
|
||||
if (ehdr.e_type == elf::ET_DYN)
|
||||
return FileType::ELF_DSO;
|
||||
} else {
|
||||
elf::EB32Ehdr &ehdr = *(elf::EB32Ehdr *)data.data();
|
||||
|
||||
if (ehdr.e_type == elf::ET_REL) {
|
||||
if (ehdr.e_ident[elf::EI_CLASS] == elf::ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<elf::M68K>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<elf::SPARC64>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
|
||||
if (ehdr.e_type == elf::ET_DYN)
|
||||
return FileType::ELF_DSO;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("\xcf\xfa\xed\xfe")) {
|
||||
switch (*(ul32 *)(data.data() + 12)) {
|
||||
case 1: // MH_OBJECT
|
||||
return FileType::MACH_OBJ;
|
||||
case 2: // MH_EXECUTE
|
||||
return FileType::MACH_EXE;
|
||||
case 6: // MH_DYLIB
|
||||
return FileType::MACH_DYLIB;
|
||||
case 8: // MH_BUNDLE
|
||||
return FileType::MACH_BUNDLE;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("!<arch>\n"))
|
||||
return FileType::AR;
|
||||
if (data.starts_with("!<thin>\n"))
|
||||
return FileType::THIN_AR;
|
||||
if (data.starts_with("--- !tapi-tbd"))
|
||||
return FileType::TAPI;
|
||||
if (data.starts_with("\xca\xfe\xba\xbe"))
|
||||
return FileType::MACH_UNIVERSAL;
|
||||
if (is_text_file(mf))
|
||||
return FileType::TEXT;
|
||||
if (data.starts_with("\xde\xc0\x17\x0b"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
if (data.starts_with("BC\xc0\xde"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
inline std::string filetype_to_string(FileType type) {
|
||||
switch (type) {
|
||||
case FileType::UNKNOWN: return "UNKNOWN";
|
||||
case FileType::EMPTY: return "EMPTY";
|
||||
case FileType::ELF_OBJ: return "ELF_OBJ";
|
||||
case FileType::ELF_DSO: return "ELF_DSO";
|
||||
case FileType::MACH_EXE: return "MACH_EXE";
|
||||
case FileType::MACH_OBJ: return "MACH_OBJ";
|
||||
case FileType::MACH_DYLIB: return "MACH_DYLIB";
|
||||
case FileType::MACH_BUNDLE: return "MACH_BUNDLE";
|
||||
case FileType::MACH_UNIVERSAL: return "MACH_UNIVERSAL";
|
||||
case FileType::AR: return "AR";
|
||||
case FileType::THIN_AR: return "THIN_AR";
|
||||
case FileType::TAPI: return "TAPI";
|
||||
case FileType::TEXT: return "TEXT";
|
||||
case FileType::GCC_LTO_OBJ: return "GCC_LTO_OBJ";
|
||||
case FileType::LLVM_BITCODE: return "LLVM_BITCODE";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, FileType type) {
|
||||
out << filetype_to_string(type);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,149 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<Glob> Glob::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::bitset<256> &bitset = vec.back().bitset;
|
||||
|
||||
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++)
|
||||
bitset[i] = true;
|
||||
} else {
|
||||
bitset[(u8)pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
bitset.flip();
|
||||
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 {Glob{std::move(vec)}};
|
||||
}
|
||||
|
||||
bool Glob::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
}
|
||||
|
||||
bool Glob::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.bitset[str[0]])
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return str.empty();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -4,7 +4,7 @@
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "lib.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace mold {
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += std::ldexp(1.0, -val);
|
||||
z += pow(2, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
+31
-41
@@ -1,54 +1,44 @@
|
||||
#!/bin/sh
|
||||
# This script installs binary packages needed to build mold.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
#!/bin/bash
|
||||
set -e
|
||||
. /etc/os-release
|
||||
source /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++ clang gdb
|
||||
# The first line for each distro installs a build dependency.
|
||||
# The second line installs extra packages for unittests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
[ "$1" = update ] && apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
|
||||
apt-get install -y file
|
||||
;;
|
||||
fedora | fedora-* | amzn | rhel | centos)
|
||||
dnf install -y gcc-c++ cmake glibc-static libstdc++-static diffutils util-linux tar
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
[ "$1" = update ] && apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
|
||||
apt-get install -y file
|
||||
;;
|
||||
rocky | ol)
|
||||
dnf install -y gcc-c++ cmake diffutils util-linux
|
||||
fedora-*)
|
||||
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
|
||||
dnf install -y glibc-static file libstdc++-static diffutils util-linux
|
||||
;;
|
||||
opensuse-*)
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux gawk
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
|
||||
opensuse-tumbleweed-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
arch | archarm | artix | endeavouros | manjaro | cachyos)
|
||||
pacman -Sy --needed --noconfirm base-devel cmake util-linux
|
||||
gentoo-*)
|
||||
[ "$1" = update ] && emerge-webrsync
|
||||
emerge dev-util/cmake sys-libs/zlib
|
||||
;;
|
||||
void)
|
||||
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
|
||||
;;
|
||||
alpine)
|
||||
apk update
|
||||
apk add bash make linux-headers cmake gcc g++
|
||||
;;
|
||||
clear-linux-os)
|
||||
swupd update
|
||||
swupd bundle-add c-basic diffutils
|
||||
;;
|
||||
almalinux)
|
||||
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel cmake diffutils
|
||||
;;
|
||||
altlinux)
|
||||
apt-get update
|
||||
apt-get install -y gcc-c++ make cmake ctest diffutils util-linux
|
||||
;;
|
||||
freebsd)
|
||||
pkg update
|
||||
pkg install -y cmake bash binutils gcc
|
||||
arch-*)
|
||||
[ "$1" = update ] && pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
|
||||
# to enable cross-target tests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian)
|
||||
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
// This file defines integral types for file input/output. We need to use
|
||||
// these types instead of the plain integers (such as uint32_t or int32_t)
|
||||
// when reading from/writing to an mmap'ed file area for the following
|
||||
// reasons:
|
||||
//
|
||||
// 1. mold is always a cross linker and should not depend on what host it
|
||||
// is running on. Users should be able to run mold on a big-endian
|
||||
// SPARC machine to create a little-endian RV64 binary, for example.
|
||||
//
|
||||
// 2. Even though data members in all ELF data strucutres are naturally
|
||||
// aligned, they are not guaranteed to be aligned on memory. Because
|
||||
// archive file (.a file) aligns each member only to a 2 byte boundary,
|
||||
// anything larger than 2 bytes may be unaligned in an mmap'ed memory.
|
||||
// Unaligned access is an undefined behavior in C/C++, so we shouldn't
|
||||
// cast an arbitrary pointer to a uint32_t, for example, to read a
|
||||
// 32-bits value.
|
||||
//
|
||||
// The data types defined in this file don't depend on host byte order and
|
||||
// don't do unaligned access.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __LITTLE_ENDIAN__ 1
|
||||
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __BIG_ENDIAN__ 1
|
||||
# else
|
||||
# error "unknown host byte order"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
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;
|
||||
|
||||
template <typename T>
|
||||
static inline T bswap(T val) {
|
||||
switch (sizeof(T)) {
|
||||
case 2: return __builtin_bswap16(val);
|
||||
case 4: return __builtin_bswap32(val);
|
||||
case 8: return __builtin_bswap64(val);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int SIZE = sizeof(T)>
|
||||
class LittleEndian {
|
||||
public:
|
||||
LittleEndian() = default;
|
||||
LittleEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
x = bswap(x);
|
||||
return x;
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
return (val[2] << 16) | (val[1] << 8) | val[0];
|
||||
}
|
||||
}
|
||||
|
||||
LittleEndian &operator=(T x) {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
x = bswap(x);
|
||||
memcpy(val, &x, sizeof(T));
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
val[2] = x >> 16;
|
||||
val[1] = x >> 8;
|
||||
val[0] = x;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LittleEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
LittleEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
LittleEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
LittleEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
LittleEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
LittleEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 val[SIZE];
|
||||
};
|
||||
|
||||
using il16 = LittleEndian<i16>;
|
||||
using il32 = LittleEndian<i32>;
|
||||
using il64 = LittleEndian<i64>;
|
||||
using ul16 = LittleEndian<u16>;
|
||||
using ul24 = LittleEndian<u32, 3>;
|
||||
using ul32 = LittleEndian<u32>;
|
||||
using ul64 = LittleEndian<u64>;
|
||||
|
||||
template <typename T, int SIZE = sizeof(T)>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = default;
|
||||
BigEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
if constexpr (std::endian::native == std::endian::little)
|
||||
x = bswap(x);
|
||||
return x;
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
return (val[0] << 16) | (val[1] << 8) | val[2];
|
||||
}
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
if constexpr (std::endian::native == std::endian::little)
|
||||
x = bswap(x);
|
||||
memcpy(val, &x, sizeof(T));
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
val[0] = x >> 16;
|
||||
val[1] = x >> 8;
|
||||
val[2] = x;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 val[SIZE];
|
||||
};
|
||||
|
||||
using ib16 = BigEndian<i16>;
|
||||
using ib32 = BigEndian<i32>;
|
||||
using ib64 = BigEndian<i64>;
|
||||
using ub16 = BigEndian<u16>;
|
||||
using ub24 = BigEndian<u32, 3>;
|
||||
using ub32 = BigEndian<u32>;
|
||||
using ub64 = BigEndian<u64>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,132 +0,0 @@
|
||||
// This file implements the Aho-Corasick algorithm to search multiple
|
||||
// strings within an input string simultaneously. It is essentially a
|
||||
// trie with additional links. For details, see
|
||||
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
|
||||
//
|
||||
// We use it for simple glob patterns in version scripts or dynamic
|
||||
// list files. Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// Aho-Corasick can do only substring search, so it cannot handle
|
||||
// complex glob patterns such as `*foo*bar*`. We handle such patterns
|
||||
// with the Glob class.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace mold {
|
||||
|
||||
bool AhoCorasick::can_handle(std::string_view pat) {
|
||||
if (pat.starts_with('*'))
|
||||
pat.remove_prefix(1);
|
||||
if (pat.ends_with('*'))
|
||||
pat.remove_suffix(1);
|
||||
return pat.find_first_of("*?[") == pat.npos;
|
||||
}
|
||||
|
||||
i64 AhoCorasick::find(std::string_view str) {
|
||||
if (nodes.empty())
|
||||
return -1;
|
||||
|
||||
i64 idx = 0;
|
||||
i64 val = -1;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (i64 j = idx; j != -1; j = nodes[j].suffix_link) {
|
||||
i64 child = nodes[j].children[c];
|
||||
if (child != -1) {
|
||||
idx = child;
|
||||
val = std::max(val, nodes[child].value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
idx = 0;
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str)
|
||||
walk(c);
|
||||
walk('\0');
|
||||
return val;
|
||||
}
|
||||
|
||||
bool AhoCorasick::add(std::string_view pat, i64 val) {
|
||||
assert(can_handle(pat));
|
||||
if (nodes.empty())
|
||||
nodes.resize(1);
|
||||
i64 idx = 0;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
if (nodes[idx].children[c] == -1) {
|
||||
nodes[idx].children[c] = nodes.size();
|
||||
nodes.resize(nodes.size() + 1);
|
||||
}
|
||||
idx = nodes[idx].children[c];
|
||||
};
|
||||
|
||||
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
|
||||
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
|
||||
// substring matching, so we use \0 as a beginning/end-of-string
|
||||
// markers.
|
||||
if (!pat.starts_with('*'))
|
||||
walk('\0');
|
||||
for (u8 c : pat)
|
||||
if (c != '*')
|
||||
walk(c);
|
||||
if (!pat.ends_with('*'))
|
||||
walk('\0');
|
||||
|
||||
nodes[idx].value = std::max(nodes[idx].value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AhoCorasick::compile() {
|
||||
if (nodes.empty())
|
||||
return;
|
||||
fix_suffix_links(0);
|
||||
fix_values();
|
||||
}
|
||||
|
||||
void AhoCorasick::fix_suffix_links(i64 idx) {
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
i64 child = nodes[idx].children[i];
|
||||
if (child == -1)
|
||||
continue;
|
||||
|
||||
i64 j = nodes[idx].suffix_link;
|
||||
for (; j != -1; j = nodes[j].suffix_link) {
|
||||
if (nodes[j].children[i] != -1) {
|
||||
nodes[child].suffix_link = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == -1)
|
||||
nodes[child].suffix_link = 0;
|
||||
fix_suffix_links(child);
|
||||
}
|
||||
}
|
||||
|
||||
void AhoCorasick::fix_values() {
|
||||
std::queue<i64> queue;
|
||||
queue.push(0);
|
||||
|
||||
do {
|
||||
i64 idx = queue.front();
|
||||
queue.pop();
|
||||
|
||||
for (i64 child : nodes[idx].children) {
|
||||
if (child != -1) {
|
||||
i64 suffix = nodes[child].suffix_link;
|
||||
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,50 +0,0 @@
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
struct Atomic : std::atomic<T> {
|
||||
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
|
||||
|
||||
using std::atomic<T>::atomic;
|
||||
|
||||
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
|
||||
|
||||
Atomic<T> &operator=(const Atomic<T> &other) {
|
||||
store(other.load());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val, std::memory_order order = relaxed) {
|
||||
std::atomic<T>::store(val, order);
|
||||
}
|
||||
|
||||
T load(std::memory_order order = relaxed) const {
|
||||
return std::atomic<T>::load(order);
|
||||
}
|
||||
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
|
||||
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
|
||||
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
|
||||
|
||||
bool test_and_set() {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return load() || exchange(true);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "integers.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace mold {
|
||||
|
||||
class BitvectorProxy {
|
||||
public:
|
||||
BitvectorProxy(u64 &word, size_t pos)
|
||||
: word(word), mask(1ULL << pos) {}
|
||||
|
||||
BitvectorProxy &operator=(bool val) {
|
||||
if (val)
|
||||
word |= mask;
|
||||
else
|
||||
word &= ~mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitvectorProxy &operator=(const BitvectorProxy &other) {
|
||||
return *this = (bool)other;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return word & mask;
|
||||
}
|
||||
|
||||
private:
|
||||
u64 &word;
|
||||
u64 mask;
|
||||
};
|
||||
|
||||
class Bitvector {
|
||||
public:
|
||||
Bitvector() = default;
|
||||
Bitvector(i64 n) : size(n), words((n + 63) / 64) {}
|
||||
|
||||
void resize(i64 n) {
|
||||
words.clear();
|
||||
words.resize((n + 63) / 64);
|
||||
size = n;
|
||||
}
|
||||
|
||||
Bitvector &operator|=(const Bitvector &x) {
|
||||
assert(size == x.size);
|
||||
for (i64 i = 0; i < words.size(); i++)
|
||||
words[i] |= x.words[i];
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitvector &operator&=(const Bitvector &x) {
|
||||
assert(size == x.size);
|
||||
for (i64 i = 0; i < words.size(); i++)
|
||||
words[i] &= x.words[i];
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitvector &operator<<=(size_t n) {
|
||||
assert(n == 1);
|
||||
for (i64 i = words.size() - 1; i > 0; i--)
|
||||
words[i] = (words[i] << 1) | (words[i - 1] >> 63);
|
||||
words[0] <<= 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitvectorProxy operator[](size_t pos) {
|
||||
assert(pos < size);
|
||||
return {words[pos / 64], pos % 64};
|
||||
}
|
||||
|
||||
i64 size = 0;
|
||||
std::vector<u64> words;
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,29 +0,0 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
#define MOLD_FIRST_TARGET @MOLD_FIRST_TARGET@
|
||||
|
||||
#cmakedefine01 HAVE_MADVISE
|
||||
#cmakedefine01 HAVE_UNAME
|
||||
#cmakedefine01 MOLD_USE_MIMALLOC
|
||||
#cmakedefine01 MOLD_USE_SYSTEM_MIMALLOC
|
||||
|
||||
#cmakedefine01 HAVE_TARGET_X86_64
|
||||
#cmakedefine01 HAVE_TARGET_I386
|
||||
#cmakedefine01 HAVE_TARGET_ARM64LE
|
||||
#cmakedefine01 HAVE_TARGET_ARM64BE
|
||||
#cmakedefine01 HAVE_TARGET_ARM32LE
|
||||
#cmakedefine01 HAVE_TARGET_ARM32BE
|
||||
#cmakedefine01 HAVE_TARGET_RV32LE
|
||||
#cmakedefine01 HAVE_TARGET_RV32BE
|
||||
#cmakedefine01 HAVE_TARGET_RV64LE
|
||||
#cmakedefine01 HAVE_TARGET_RV64BE
|
||||
#cmakedefine01 HAVE_TARGET_PPC32
|
||||
#cmakedefine01 HAVE_TARGET_PPC64V1
|
||||
#cmakedefine01 HAVE_TARGET_PPC64V2
|
||||
#cmakedefine01 HAVE_TARGET_S390X
|
||||
#cmakedefine01 HAVE_TARGET_SPARC64
|
||||
#cmakedefine01 HAVE_TARGET_M68K
|
||||
#cmakedefine01 HAVE_TARGET_SH4LE
|
||||
#cmakedefine01 HAVE_TARGET_SH4BE
|
||||
#cmakedefine01 HAVE_TARGET_LOONGARCH64
|
||||
#cmakedefine01 HAVE_TARGET_LOONGARCH32
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
// This function "forges" a CRC. That is, given the current and a desired
|
||||
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
|
||||
// the original data to yield the desired CRC. Trailing garbage is ignored
|
||||
// by many bianry file formats, so you can create a file with a desired
|
||||
// CRC using crc32_solve(). We need it for --separate-debug-file.
|
||||
std::vector<u8> crc32_solve(u32 current, u32 desired) {
|
||||
constexpr u32 poly = 0xedb88320;
|
||||
u32 x = ~desired;
|
||||
|
||||
// Each iteration computes x = (x * x^-1) mod poly.
|
||||
for (i64 i = 0; i < 32; i++) {
|
||||
x = std::rotl(x, 1);
|
||||
x ^= (x & 1) * (poly << 1);
|
||||
}
|
||||
|
||||
x ^= ~current;
|
||||
|
||||
std::vector<u8> out(4);
|
||||
out[0] = x;
|
||||
out[1] = x >> 8;
|
||||
out[2] = x >> 16;
|
||||
out[3] = x >> 24;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Compute a CRC for given data in parallel
|
||||
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
|
||||
struct Shard {
|
||||
u8 *buf;
|
||||
i64 len;
|
||||
u32 crc;
|
||||
};
|
||||
|
||||
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
|
||||
std::vector<Shard> shards;
|
||||
|
||||
while (len > 0) {
|
||||
i64 sz = std::min(len, shard_size);
|
||||
shards.push_back({buf, sz, 0});
|
||||
buf += sz;
|
||||
len -= sz;
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(shards, [](Shard &shard) {
|
||||
shard.crc = crc32(0, shard.buf, shard.len);
|
||||
});
|
||||
|
||||
for (Shard &shard : shards)
|
||||
crc = crc32_combine(crc, shard.crc, shard.len);
|
||||
return crc;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "../third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
|
||||
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
|
||||
#ifndef _WIN32
|
||||
if (name.starts_with("_Z")) {
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
free(buf);
|
||||
buf = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (buf)
|
||||
return buf;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#if __APPLE__ || _WIN32
|
||||
fprintf(stderr, "mold: get_self_path is not supported");
|
||||
exit(1);
|
||||
#elif __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-256
@@ -1,256 +0,0 @@
|
||||
// This file implements a glob matcher that can run multiple glob patterns
|
||||
// against an input string. mold uses the glob matcher for symbol name
|
||||
// patterns in a version script or a dynamic list file. Since we may need to
|
||||
// match hundreds of glob patterns against millions of symbol names, the
|
||||
// speed of the matcher is very important.
|
||||
//
|
||||
// The pattern match implemented in this file is NFA-based, although the
|
||||
// cost of the function is O(n*m), where n is the number of NFA states and m
|
||||
// is the length of the input string. We do not use recursion or
|
||||
// backtracking, unlike a generic NFA-based regular expression matcher. This
|
||||
// is doable because glob patterns are very limited subsets of regexes.
|
||||
//
|
||||
// Here is the explanation of the algorithm. Observe that the only "tricky"
|
||||
// meta-character in a glob pattern is "*", which matches zero or more
|
||||
// characters. Other characters and meta-characters always match a single
|
||||
// input character. So the key of the algorithm is to handle "*" efficiently.
|
||||
//
|
||||
// We can represent a glob pattern "a*b*" with three NFA states: q_start, q1
|
||||
// and q_accept, with the following transition functions:
|
||||
//
|
||||
// δ(q_start, "a") = q1
|
||||
// δ(q1, <any character>) = q1
|
||||
// δ(q1, "b") = q_accept
|
||||
// δ(q_accept, <any character>) = q_accept
|
||||
//
|
||||
// We can construct such an NFA in a straightforward manner. We maintain NFA
|
||||
// states as a list, with the initial contents being the start state. Each
|
||||
// character except for "*" creates a new NFA state, adds a transition from
|
||||
// the last state in the list to the new one, and appends the new state at
|
||||
// the end of the list. "*" sets the "is_star" flag on the last NFA state.
|
||||
// The flag indicates that the state machine can remain in the state for any
|
||||
// input character.
|
||||
//
|
||||
// An NFA constructed this way doesn't have any complicated loops,
|
||||
// ε-transitions, or anything like that. Each state has only one incoming
|
||||
// edge. The only loops in the state transition are the self-loops on states
|
||||
// followed by a "*". Aside from that, the state machine progresses linearly
|
||||
// from the start state to the accept state.
|
||||
//
|
||||
// Each state of an NFA can be represented by a single bit. If a bit is 1,
|
||||
// the non-deterministic state machine is in that state. Otherwise, it's
|
||||
// not. Observe that a state with the "is_star" flag will continued to be 1
|
||||
// once it becomes 1, since the state machine can loop over the state on any
|
||||
// input character.
|
||||
//
|
||||
// With that observation, we can represent an NFA with a bit vector of N
|
||||
// bits, where N is the number of NFA states. For each input character, bit
|
||||
// M becomes 1 if
|
||||
//
|
||||
// - bit M-1 is 1 and there's a transition from state_{M-1} to state_M
|
||||
// with the given character, or
|
||||
// - bit M is 1 and state_M's "is_star" flag is 1.
|
||||
//
|
||||
// Initially, the 0th bit is 1 for the start state. At each step, the bits
|
||||
// propagate from least significant to most significant positions, at most
|
||||
// one bit at a time. If the most significant bit is 1 after the entire
|
||||
// input has been processed, the string matches.
|
||||
//
|
||||
// This propagation can be implemented with bitwise OR, bitwise AND, and a
|
||||
// one-bit bit shift on the bit vector. All these operations are very cheap.
|
||||
//
|
||||
// We can combine multiple glob matchers into a single matcher by simply
|
||||
// concatenating the bit vectors of their state machines.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
|
||||
std::vector<MultiGlob::State> vec(1);
|
||||
|
||||
while (!pat.empty()) {
|
||||
u8 c = pat[0];
|
||||
pat = pat.substr(1);
|
||||
std::bitset<256> chars;
|
||||
|
||||
switch (c) {
|
||||
case '*':
|
||||
vec.back().is_star = true;
|
||||
continue;
|
||||
case '?':
|
||||
chars.set();
|
||||
break;
|
||||
case '\\':
|
||||
if (pat.empty())
|
||||
return {};
|
||||
chars[pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
break;
|
||||
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
|
||||
//
|
||||
// Both `!` and `^` are accepted as negation markers. `!` is the
|
||||
// POSIX/shell convention used by other linkers. `^` was mold's
|
||||
// original syntax and is kept for backward compatibility.
|
||||
bool negate = false;
|
||||
bool closed = false;
|
||||
|
||||
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
|
||||
negate = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
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++)
|
||||
chars[i] = true;
|
||||
} else {
|
||||
chars[pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
chars.flip();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
chars[c] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
vec.push_back({chars, false});
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Instead of returning just a match/no match boolean value, our glob
|
||||
// matcher returns an integer value associated with each given pattern.
|
||||
// If multiple patterns match at the same time, the largest associated
|
||||
// value will be returned by find().
|
||||
bool MultiGlob::add(std::string_view pat, i64 val) {
|
||||
std::vector<State> vec = parse_glob(pat);
|
||||
if (vec.empty())
|
||||
return false;
|
||||
patterns.push_back({std::move(vec), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiGlob::compile() {
|
||||
if (patterns.empty())
|
||||
return;
|
||||
|
||||
ranges::stable_sort(patterns, ranges::greater(), &GlobPattern::value);
|
||||
|
||||
std::vector<State> states;
|
||||
for (GlobPattern &p : patterns)
|
||||
append(states, p.states);
|
||||
i64 sz = states.size();
|
||||
|
||||
start_states.resize(sz);
|
||||
for (i64 pos = 0; GlobPattern &p : patterns) {
|
||||
start_states[pos] = true;
|
||||
pos += p.states.size();
|
||||
}
|
||||
|
||||
star_mask.resize(sz);
|
||||
for (i64 i = 0; i < sz; i++)
|
||||
if (states[i].is_star)
|
||||
star_mask[i] = true;
|
||||
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
char_mask[i].resize(sz);
|
||||
for (i64 j = 0; j < sz; j++)
|
||||
if (states[j].incoming_edge[i])
|
||||
char_mask[i][j] = true;
|
||||
}
|
||||
}
|
||||
|
||||
i64 MultiGlob::find(std::string_view str) {
|
||||
if (patterns.empty())
|
||||
return -1;
|
||||
|
||||
Bitvector bits = start_states;
|
||||
Bitvector tmp;
|
||||
|
||||
for (u8 c : str) {
|
||||
// This is equivalent to
|
||||
//
|
||||
// bits = (bits & star_mask) | ((bits << 1) & char_mask[c])
|
||||
//
|
||||
// but we update the existing objects in place to avoid allocating
|
||||
// temporary objects.
|
||||
tmp = bits;
|
||||
tmp &= star_mask;
|
||||
bits <<= 1;
|
||||
bits &= char_mask[c];
|
||||
bits |= tmp;
|
||||
}
|
||||
|
||||
for (i64 pos = 0; GlobPattern &p : patterns) {
|
||||
pos += p.states.size();
|
||||
if (bits[pos - 1])
|
||||
return p.value;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Glob::add(std::string_view pat, i64 val) {
|
||||
assert(val >= 0);
|
||||
assert(!is_compiled);
|
||||
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if (aho_corasick.can_handle(pat))
|
||||
return aho_corasick.add(pat, val);
|
||||
return multi_glob.add(pat, val);
|
||||
}
|
||||
|
||||
i64 Glob::find(std::string_view str) {
|
||||
std::call_once(once, [&] {
|
||||
multi_glob.compile();
|
||||
aho_corasick.compile();
|
||||
is_compiled = true;
|
||||
});
|
||||
|
||||
return std::max(multi_glob.find(str), aho_corasick.find(str));
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
// This file defines integral types for file input/output. We need to use
|
||||
// these types instead of the plain integers (such as uint32_t or int32_t)
|
||||
// when reading from/writing to an mmap'ed file area for the following
|
||||
// reasons:
|
||||
//
|
||||
// 1. mold is always a cross linker and should not depend on what host it
|
||||
// is running on. For example, users should be able to run mold on a
|
||||
// little-endian x86 machine to create a big-endian s390x binary.
|
||||
//
|
||||
// 2. Even though data members in all ELF data strucutres are naturally
|
||||
// aligned, they are not guaranteed to be aligned on memory because of
|
||||
// archive files. Archive files (.a files) align each file only to a
|
||||
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
|
||||
// in an mmap'ed memory. Misaligned access is an undefined behavior in
|
||||
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
|
||||
// example, to read a 32 bit value.
|
||||
//
|
||||
// The data types defined in this file are independent of the host byte
|
||||
// order and are designed to avoid unaligned access.
|
||||
//
|
||||
// Note that in C/C++, memcpy is a portable and efficient way to access
|
||||
// unaligned data, as it is typically treated as an intrinsic. Compilers
|
||||
// can easily optimize memcpy calls in this file into a single load or
|
||||
// store instruction.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __LITTLE_ENDIAN__ 1
|
||||
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __BIG_ENDIAN__ 1
|
||||
# else
|
||||
# error "unknown host byte order"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T, bool is_le, int size = sizeof(T)>
|
||||
class Integer {
|
||||
public:
|
||||
constexpr Integer() = default;
|
||||
|
||||
constexpr Integer(T v) {
|
||||
if (std::is_constant_evaluated() || size == 3) {
|
||||
for (int i = 0; i < size; i++)
|
||||
buf[is_le ? i : (size - i - 1)] = v >> (i * 8);
|
||||
} else {
|
||||
if (!is_native)
|
||||
v = bswap(v);
|
||||
memcpy(buf, &v, size);
|
||||
}
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
if (size == 3) {
|
||||
if (is_le)
|
||||
return buf[2] << 16 | buf[1] << 8 | buf[0];
|
||||
return buf[0] << 16 | buf[1] << 8 | buf[2];
|
||||
}
|
||||
|
||||
T v;
|
||||
memcpy(&v, buf, size);
|
||||
return is_native ? v : bswap(v);
|
||||
}
|
||||
|
||||
Integer &operator=(T v) { new (this) Integer(v); return *this; }
|
||||
Integer &operator++() { return *this = *this + 1; }
|
||||
Integer operator++(int) { auto x = *this; ++*this; return x; }
|
||||
Integer &operator--() { return *this = *this - 1; }
|
||||
Integer operator--(int) { auto x = *this; --*this; return x; }
|
||||
Integer &operator+=(T v) { return *this = *this + v; }
|
||||
Integer &operator-=(T v) { return *this = *this - v; }
|
||||
Integer &operator&=(T v) { return *this = *this & v; }
|
||||
Integer &operator|=(T v) { return *this = *this | v; }
|
||||
|
||||
private:
|
||||
static constexpr bool is_native =
|
||||
(std::endian::native == (is_le ? std::endian::little : std::endian::big));
|
||||
|
||||
static T bswap(T v) {
|
||||
switch (size) {
|
||||
case 2: return __builtin_bswap16(v);
|
||||
case 4: return __builtin_bswap32(v);
|
||||
case 8: return __builtin_bswap64(v);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t buf[size];
|
||||
};
|
||||
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using il16 = Integer<i16, true>;
|
||||
using il32 = Integer<i32, true>;
|
||||
using il64 = Integer<i64, true>;
|
||||
|
||||
using ul16 = Integer<u16, true>;
|
||||
using ul24 = Integer<u32, true, 3>;
|
||||
using ul32 = Integer<u32, true>;
|
||||
using ul64 = Integer<u64, true>;
|
||||
|
||||
using ib16 = Integer<i16, false>;
|
||||
using ib32 = Integer<i32, false>;
|
||||
using ib64 = Integer<i64, false>;
|
||||
|
||||
using ub16 = Integer<u16, false>;
|
||||
using ub24 = Integer<u32, false, 3>;
|
||||
using ub32 = Integer<u32, false>;
|
||||
using ub64 = Integer<u64, false>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,709 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "atomics.h"
|
||||
#include "integers.h"
|
||||
#include "bitvector.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <io.h>
|
||||
#else
|
||||
# include <sys/mman.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define XXH_INLINE_ALL 1
|
||||
#include "../third-party/xxhash/xxhash.h"
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define unreachable() __builtin_unreachable()
|
||||
#else
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
inline uint64_t hash_string(std::string_view str) {
|
||||
return XXH3_64bits(str.data(), str.size());
|
||||
}
|
||||
|
||||
class HashCmp {
|
||||
public:
|
||||
static size_t hash(const std::string_view &k) {
|
||||
return hash_string(k);
|
||||
}
|
||||
|
||||
static bool equal(const std::string_view &k1, const std::string_view &k2) {
|
||||
return k1 == k2;
|
||||
}
|
||||
};
|
||||
|
||||
namespace mold {
|
||||
|
||||
namespace ranges = std::ranges;
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
inline u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.emplace_back(record);
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
// Some C++ libraries haven't implemented std::has_single_bit yet.
|
||||
inline bool has_single_bit(u64 val) {
|
||||
return std::popcount(val) == 1;
|
||||
}
|
||||
|
||||
// Some C++ libraries haven't implemented std::bit_ceil yet.
|
||||
inline u64 bit_ceil(u64 val) {
|
||||
if (has_single_bit(val))
|
||||
return val;
|
||||
return 1LL << (64 - std::countl_zero(val));
|
||||
}
|
||||
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
if (align == 0)
|
||||
return val;
|
||||
assert(has_single_bit(align));
|
||||
return (val + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 align_down(u64 val, u64 align) {
|
||||
assert(has_single_bit(align));
|
||||
return val & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 bit(u64 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
};
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
// Cast val to a signed N bit integer.
|
||||
// For example, sign_extend(x, 32) == (i32)x for any integer x.
|
||||
inline i64 sign_extend(u64 val, i64 n) {
|
||||
return (i64)(val << (64 - n)) >> (64 - n);
|
||||
}
|
||||
|
||||
inline bool is_int(u64 val, i64 n) {
|
||||
return sign_extend(val, n) == val;
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(new_val, old_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(old_val, new_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void append(std::vector<T> &x, const auto &y) {
|
||||
x.insert(x.end(), y.begin(), y.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
|
||||
i64 size = 0;
|
||||
for (std::vector<T> &v : vec)
|
||||
size += v.size();
|
||||
|
||||
std::vector<T> ret;
|
||||
ret.reserve(size);
|
||||
for (std::vector<T> &v : vec)
|
||||
append(ret, v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void remove_duplicates(std::vector<T> &vec) {
|
||||
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
|
||||
}
|
||||
|
||||
inline i64 write_string(void *buf, std::string_view str) {
|
||||
memcpy(buf, str.data(), str.size());
|
||||
*((u8 *)buf + str.size()) = '\0';
|
||||
return str.size() + 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void write_vector(void *buf, const std::vector<T> &vec) {
|
||||
if (!vec.empty())
|
||||
memcpy(buf, vec.data(), vec.size() * sizeof(T));
|
||||
}
|
||||
|
||||
inline void encode_uleb(std::vector<u8> &vec, u64 val) {
|
||||
do {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
vec.push_back(val ? (byte | 0x80) : byte);
|
||||
} while (val);
|
||||
}
|
||||
|
||||
inline void encode_sleb(std::vector<u8> &vec, i64 val) {
|
||||
for (;;) {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
|
||||
bool neg = (byte & 0x40);
|
||||
if ((val == 0 && !neg) || (val == -1 && neg)) {
|
||||
vec.push_back(byte);
|
||||
break;
|
||||
}
|
||||
vec.push_back(byte | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
inline i64 write_uleb(u8 *buf, u64 val) {
|
||||
i64 i = 0;
|
||||
do {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
buf[i++] = val ? (byte | 0x80) : byte;
|
||||
} while (val);
|
||||
return i;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 **buf) {
|
||||
u64 val = 0;
|
||||
u8 shift = 0;
|
||||
u8 byte;
|
||||
do {
|
||||
byte = *(*buf)++;
|
||||
val |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 *buf) {
|
||||
u8 *tmp = buf;
|
||||
return read_uleb(&tmp);
|
||||
}
|
||||
|
||||
inline i64 read_sleb(u8 **buf) {
|
||||
u64 val = 0;
|
||||
u8 shift = 0;
|
||||
u8 byte;
|
||||
do {
|
||||
byte = *(*buf)++;
|
||||
val |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
return sign_extend(val, shift);
|
||||
}
|
||||
|
||||
inline i64 read_sleb(u8 *buf) {
|
||||
u8 *tmp = buf;
|
||||
return read_sleb(&tmp);
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view *str) {
|
||||
u8 *start = (u8 *)str->data();
|
||||
u8 *ptr = start;
|
||||
u64 val = read_uleb(&ptr);
|
||||
*str = str->substr(ptr - start);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view str) {
|
||||
std::string_view tmp = str;
|
||||
return read_uleb(&tmp);
|
||||
}
|
||||
|
||||
inline i64 uleb_size(u64 val) {
|
||||
for (int i = 1; i < 9; i++)
|
||||
if (val < (1LL << (7 * i)))
|
||||
return i;
|
||||
return 9;
|
||||
}
|
||||
|
||||
inline void overwrite_uleb(u8 *loc, u64 val) {
|
||||
while (*loc & 0b1000'0000) {
|
||||
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
|
||||
val >>= 7;
|
||||
}
|
||||
*loc = val & 0b0111'1111;
|
||||
}
|
||||
|
||||
static inline void pause() {
|
||||
#if defined(__x86_64__)
|
||||
asm volatile("pause");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_8A__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Concurrent Map
|
||||
//
|
||||
|
||||
// This is an implementation of a fast concurrent hash map. Unlike
|
||||
// ordinary hash tables, this impl just aborts if it becomes full.
|
||||
// So you need to give a correct estimation of the final size before
|
||||
// using it. We use this hash map to uniquify pieces of data in
|
||||
// mergeable sections.
|
||||
//
|
||||
// We've implemented this ourselves because the performance of
|
||||
// conrurent hash map is critical for our linker.
|
||||
template <typename T>
|
||||
class ConcurrentMap {
|
||||
public:
|
||||
ConcurrentMap() = default;
|
||||
|
||||
ConcurrentMap(i64 nbuckets) {
|
||||
resize(nbuckets);
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
if (entries) {
|
||||
#ifdef _WIN32
|
||||
_aligned_free(entries);
|
||||
#else
|
||||
munmap(entries, sizeof(Entry) * nbuckets);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// In order to avoid unnecessary cache-line false sharing, we want
|
||||
// to make this object to be aligned to a reasonably large
|
||||
// power-of-two address.
|
||||
struct alignas(32) Entry {
|
||||
Atomic<const char *> key;
|
||||
u32 keylen;
|
||||
T value;
|
||||
};
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
assert(!entries);
|
||||
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
i64 bufsize = sizeof(Entry) * this->nbuckets;
|
||||
|
||||
// Allocate a zero-initialized buffer. mmap() is faster than
|
||||
// malloc()+memset(), and MAP_POPULATE pre-faults pages here to
|
||||
// avoid CoW faults and TLB shootdowns under concurrent inserts.
|
||||
#ifdef _WIN32
|
||||
entries = (Entry *)_aligned_malloc(bufsize, alignof(Entry));
|
||||
memset((void *)entries, 0, bufsize);
|
||||
#elif MAP_POPULATE
|
||||
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0);
|
||||
#else
|
||||
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
assert(has_single_bit(nbuckets));
|
||||
|
||||
u64 begin = hash & (nbuckets - 1);
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
|
||||
for (i64 i = 0; i < MAX_RETRY; i++) {
|
||||
u64 idx = (begin & ~mask) | ((begin + i) & mask);
|
||||
Entry &ent = entries[idx];
|
||||
|
||||
// It seems avoiding compare-and-swap is faster overall at least
|
||||
// on my Zen4 machine, so do it.
|
||||
if (const char *ptr = ent.key.load(std::memory_order_acquire);
|
||||
ptr != nullptr && ptr != (char *)-1) {
|
||||
if (key == std::string_view(ptr, ent.keylen))
|
||||
return {&ent.value, false};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, use CAS to atomically claim the ownership of the slot.
|
||||
const char *ptr = nullptr;
|
||||
bool claimed = ent.key.compare_exchange_strong(ptr, (char *)-1,
|
||||
std::memory_order_acquire);
|
||||
|
||||
// If we successfully claimed the ownership of the slot,
|
||||
// copy values to it.
|
||||
if (claimed) {
|
||||
new (&ent.value) T(val);
|
||||
ent.keylen = key.size();
|
||||
ent.key.store(key.data(), std::memory_order_release);
|
||||
return {&ent.value, true};
|
||||
}
|
||||
|
||||
// If someone is copying values to the slot, do busy wait.
|
||||
while (ptr == (char *)-1) {
|
||||
pause();
|
||||
ptr = ent.key.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// If the same key is already present, this is the slot we are
|
||||
// looking for.
|
||||
if (key == std::string_view(ptr, ent.keylen))
|
||||
return {&ent.value, false};
|
||||
}
|
||||
|
||||
std::cerr << "ConcurrentMap is full\n";
|
||||
abort();
|
||||
}
|
||||
|
||||
i64 get_idx(T *value) const {
|
||||
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
|
||||
return (Entry *)addr - entries;
|
||||
}
|
||||
|
||||
// Return a list of map entries sorted in a deterministic order.
|
||||
std::vector<Entry *> get_sorted_entries(i64 shard_idx) {
|
||||
if (nbuckets == 0)
|
||||
return {};
|
||||
|
||||
i64 shard_size = nbuckets / NUM_SHARDS;
|
||||
i64 begin = shard_idx * shard_size;
|
||||
i64 end = begin + shard_size;
|
||||
|
||||
i64 sz = 0;
|
||||
for (i64 i = begin; i < end; i++)
|
||||
if (entries[i].key)
|
||||
sz++;
|
||||
|
||||
std::vector<Entry *> vec;
|
||||
vec.reserve(sz);
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while (begin < end && entries[end - 1].key)
|
||||
vec.push_back(entries + --end);
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
i64 last = 0;
|
||||
for (i64 i = begin; i < end;) {
|
||||
while (i < end && entries[i].key)
|
||||
vec.push_back(entries + i++);
|
||||
|
||||
std::sort(vec.begin() + last, vec.end(), [](Entry *a, Entry *b) {
|
||||
if (a->keylen != b->keylen)
|
||||
return a->keylen < b->keylen;
|
||||
return memcmp(a->key, b->key, a->keylen) < 0;
|
||||
});
|
||||
|
||||
last = vec.size();
|
||||
|
||||
while (i < end && !entries[i].key)
|
||||
i++;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<Entry *> get_sorted_entries_all() {
|
||||
std::vector<std::vector<Entry *>> vec(NUM_SHARDS);
|
||||
tbb::parallel_for((i64)0, NUM_SHARDS, [&](i64 i) {
|
||||
vec[i] = get_sorted_entries(i);
|
||||
});
|
||||
return flatten(vec);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 4096;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 256;
|
||||
|
||||
Entry *entries = nullptr;
|
||||
u64 nbuckets = 0;
|
||||
};
|
||||
|
||||
//
|
||||
// random.cc
|
||||
//
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size);
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
|
||||
class HyperLogLog {
|
||||
public:
|
||||
void insert(u64 hash) {
|
||||
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
|
||||
}
|
||||
|
||||
i64 get_cardinality() const;
|
||||
|
||||
void merge(const HyperLogLog &other) {
|
||||
for (i64 i = 0; i < NBUCKETS; i++)
|
||||
update_maximum(buckets[i], other.buckets[i]);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr i64 NBUCKETS = 2048;
|
||||
static constexpr double ALPHA = 0.79402;
|
||||
|
||||
Atomic<u8> buckets[NBUCKETS];
|
||||
};
|
||||
|
||||
//
|
||||
// aho-corasick.cc
|
||||
//
|
||||
|
||||
class AhoCorasick {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return nodes.empty(); }
|
||||
void compile();
|
||||
i64 find(std::string_view str);
|
||||
|
||||
static bool can_handle(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
TrieNode() { children.fill(-1); }
|
||||
i64 value = -1;
|
||||
i32 suffix_link = -1;
|
||||
std::array<i32, 256> children;
|
||||
};
|
||||
|
||||
void fix_suffix_links(i64 idx);
|
||||
void fix_values();
|
||||
|
||||
std::vector<TrieNode> nodes;
|
||||
};
|
||||
|
||||
//
|
||||
// glob.cc
|
||||
//
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return patterns.empty(); }
|
||||
void compile();
|
||||
i64 find(std::string_view str);
|
||||
|
||||
struct State {
|
||||
std::bitset<256> incoming_edge;
|
||||
bool is_star = false;
|
||||
};
|
||||
|
||||
struct GlobPattern {
|
||||
std::vector<State> states;
|
||||
i64 value = -1;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<GlobPattern> patterns;
|
||||
Bitvector start_states;
|
||||
Bitvector star_mask;
|
||||
Bitvector char_mask[256];
|
||||
};
|
||||
|
||||
class Glob {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return multi_glob.empty() && aho_corasick.empty(); }
|
||||
i64 find(std::string_view str);
|
||||
|
||||
private:
|
||||
std::once_flag once;
|
||||
bool is_compiled = false;
|
||||
|
||||
MultiGlob multi_glob;
|
||||
AhoCorasick aho_corasick;
|
||||
};
|
||||
|
||||
//
|
||||
// filepath.cc
|
||||
//
|
||||
|
||||
inline std::filesystem::path path_dirname(std::string_view path) {
|
||||
return std::filesystem::path(path).parent_path();
|
||||
}
|
||||
|
||||
inline std::string path_filename(std::string_view path) {
|
||||
return std::filesystem::path(path).filename().string();
|
||||
}
|
||||
|
||||
inline std::string path_clean(std::string_view path) {
|
||||
return std::filesystem::path(path).lexically_normal().string();
|
||||
}
|
||||
|
||||
std::string get_self_path();
|
||||
|
||||
//
|
||||
// demangle.cc
|
||||
//
|
||||
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name);
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name);
|
||||
|
||||
//
|
||||
// crc32.cc
|
||||
//
|
||||
|
||||
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
|
||||
std::vector<u8> crc32_solve(u32 current, u32 desired);
|
||||
|
||||
//
|
||||
// compress.cc
|
||||
//
|
||||
|
||||
class Compressor {
|
||||
public:
|
||||
virtual void write_to(u8 *buf) = 0;
|
||||
virtual ~Compressor();
|
||||
i64 compressed_size = 0;
|
||||
|
||||
protected:
|
||||
std::vector<std::span<u8>> shards;
|
||||
};
|
||||
|
||||
class ZlibCompressor : public Compressor {
|
||||
public:
|
||||
ZlibCompressor(u8 *buf, i64 size, i64 level);
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
u32 checksum = 0;
|
||||
};
|
||||
|
||||
class ZstdCompressor : public Compressor {
|
||||
public:
|
||||
ZstdCompressor(u8 *buf, i64 size, i64 level);
|
||||
void write_to(u8 *buf) override;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
|
||||
// TarFile is a class to create a tar file.
|
||||
//
|
||||
// If you pass `--repro` to mold, mold collects all input files and
|
||||
// put them into `<output-file-path>.repro.tar`, so that it is easy to
|
||||
// run the same command with the same command line arguments.
|
||||
class TarWriter {
|
||||
public:
|
||||
static std::unique_ptr<TarWriter>
|
||||
open(std::string output_path, std::string basedir);
|
||||
|
||||
~TarWriter();
|
||||
void append(std::string path, std::string_view data);
|
||||
|
||||
private:
|
||||
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
|
||||
|
||||
FILE *out = nullptr;
|
||||
std::string basedir;
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace mold {
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size) {
|
||||
std::random_device rand;
|
||||
i64 i = 0;
|
||||
|
||||
for (; i < size - 4; i += 4) {
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, 4);
|
||||
}
|
||||
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, size - i);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
// This is a header-only C++20 implementation of SipHash based on the
|
||||
// reference implementation. To use, just copy this header file into
|
||||
// your project and #include it.
|
||||
//
|
||||
// https://github.com/rui314/siphash/blob/main/siphash.h
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
template <int C_ROUNDS, int D_ROUNDS, int OUTLEN>
|
||||
class SipHashTmpl {
|
||||
public:
|
||||
static_assert(OUTLEN == 64 || OUTLEN == 128);
|
||||
|
||||
SipHashTmpl(void *key) {
|
||||
uint64_t k0 = read64(key);
|
||||
uint64_t k1 = read64((char *)key + 8);
|
||||
|
||||
v0 = 0x736f6d6570736575 ^ k0;
|
||||
v1 = 0x646f72616e646f6d ^ k1;
|
||||
v2 = 0x6c7967656e657261 ^ k0;
|
||||
v3 = 0x7465646279746573 ^ k1;
|
||||
|
||||
if (OUTLEN == 128)
|
||||
v1 ^= 0xee;
|
||||
}
|
||||
|
||||
void update(void *msgp, int64_t msglen) {
|
||||
char *msg = (char *)msgp;
|
||||
sum += msglen;
|
||||
|
||||
if (buflen) {
|
||||
if (buflen + msglen < 8) {
|
||||
memcpy(buf + buflen, msg, msglen);
|
||||
buflen += msglen;
|
||||
return;
|
||||
}
|
||||
|
||||
int j = 8 - buflen;
|
||||
memcpy(buf + buflen, msg, j);
|
||||
compress(read64(buf));
|
||||
|
||||
msg += j;
|
||||
msglen -= j;
|
||||
buflen = 0;
|
||||
}
|
||||
|
||||
while (msglen >= 8) {
|
||||
compress(read64(msg));
|
||||
msg += 8;
|
||||
msglen -= 8;
|
||||
}
|
||||
|
||||
memcpy(buf, msg, msglen);
|
||||
buflen = msglen;
|
||||
}
|
||||
|
||||
void finish(void *out) {
|
||||
memset(buf + buflen, 0, 8 - buflen);
|
||||
compress(((uint64_t)sum << 56) | read64(buf));
|
||||
|
||||
v2 ^= (OUTLEN == 128) ? 0xee : 0xff;
|
||||
finalize();
|
||||
write64(out, v0 ^ v1 ^ v2 ^ v3);
|
||||
|
||||
if (OUTLEN == 128) {
|
||||
v1 ^= 0xdd;
|
||||
finalize();
|
||||
write64((char *)out + 8, v0 ^ v1 ^ v2 ^ v3);
|
||||
}
|
||||
}
|
||||
|
||||
static void hash(void *out, void *key, void *in, int inlen) {
|
||||
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN> h(key);
|
||||
h.update(in, inlen);
|
||||
h.finish(out);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t v0, v1, v2, v3;
|
||||
uint8_t buf[8];
|
||||
uint8_t buflen = 0;
|
||||
uint8_t sum = 0;
|
||||
|
||||
uint64_t read64(void *loc) {
|
||||
uint64_t val;
|
||||
memcpy(&val, loc, 8);
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void write64(void *loc, uint64_t val) {
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
memcpy(loc, &val, 8);
|
||||
}
|
||||
|
||||
uint64_t bswap(uint64_t val) {
|
||||
return ((val << 56) & 0xff00000000000000) |
|
||||
((val << 40) & 0x00ff000000000000) |
|
||||
((val << 24) & 0x0000ff0000000000) |
|
||||
((val << 8) & 0x000000ff00000000) |
|
||||
((val >> 8) & 0x00000000ff000000) |
|
||||
((val >> 24) & 0x0000000000ff0000) |
|
||||
((val >> 40) & 0x000000000000ff00) |
|
||||
((val >> 56) & 0x00000000000000ff);
|
||||
}
|
||||
|
||||
void round() {
|
||||
v0 += v1;
|
||||
v1 = std::rotl(v1, 13);
|
||||
v1 ^= v0;
|
||||
v0 = std::rotl(v0, 32);
|
||||
v2 += v3;
|
||||
v3 = std::rotl(v3, 16);
|
||||
v3 ^= v2;
|
||||
v0 += v3;
|
||||
v3 = std::rotl(v3, 21);
|
||||
v3 ^= v0;
|
||||
v2 += v1;
|
||||
v1 = std::rotl(v1, 17);
|
||||
v1 ^= v2;
|
||||
v2 = std::rotl(v2, 32);
|
||||
}
|
||||
|
||||
void compress(uint64_t m) {
|
||||
v3 ^= m;
|
||||
for (int i = 0; i < C_ROUNDS; i++)
|
||||
round();
|
||||
v0 ^= m;
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
for (int i = 0; i < D_ROUNDS; i++)
|
||||
round();
|
||||
}
|
||||
};
|
||||
|
||||
using SipHash = SipHashTmpl<2, 4, 64>;
|
||||
using SipHash128 = SipHashTmpl<2, 4, 128>;
|
||||
using SipHash13 = SipHashTmpl<1, 3, 64>;
|
||||
using SipHash13_128 = SipHashTmpl<1, 3, 128>;
|
||||
@@ -0,0 +1,709 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
using E = ARM64;
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
static u64 page_offset(u64 hi, u64 lo) {
|
||||
u64 val = page(hi) - page(lo);
|
||||
return (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *buf = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
static const ul32 insn[] = {
|
||||
0x90000010, // adrp x16, $ptr@PAGE
|
||||
0xf9400210, // ldr x16, [x16, $ptr@PAGEOFF]
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::stub_size);
|
||||
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + word_size * i;
|
||||
u64 this_addr = this->hdr.addr + E::stub_size * i;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[0] |= page_offset(la_addr, this_addr);
|
||||
buf[1] |= bits(la_addr, 11, 3) << 10;
|
||||
buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *start = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
ul32 *buf = start;
|
||||
|
||||
static const ul32 insn0[] = {
|
||||
0x90000011, // adrp x17, $__dyld_private@PAGE
|
||||
0x91000231, // add x17, x17, $__dyld_private@PAGEOFF
|
||||
0xa9bf47f0, // stp x16, x17, [sp, #-16]!
|
||||
0x90000010, // adrp x16, $dyld_stub_binder@PAGE
|
||||
0xf9400210, // ldr x16, [x16, $dyld_stub_binder@PAGEOFF]
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
|
||||
u64 dyld_private = get_symbol(ctx, "__dyld_private")->get_addr(ctx);
|
||||
buf[0] |= page_offset(dyld_private, this->hdr.addr);
|
||||
buf[1] |= bits(dyld_private, 11, 0) << 10;
|
||||
|
||||
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx);
|
||||
buf[3] |= page_offset(stub_binder, this->hdr.addr - 12);
|
||||
buf[4] |= bits(stub_binder, 11, 0) << 10;
|
||||
|
||||
buf += 6;
|
||||
|
||||
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
|
||||
static const ul32 insn[] = {
|
||||
0x18000050, // ldr w16, addr
|
||||
0x14000000, // b stubHelperHeader
|
||||
0x00000000, // addr: .long <idx>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[1] |= bits((start - buf - 1) * 4, 27, 2);
|
||||
buf[2] = ctx.stubs.bind_offsets[i];
|
||||
buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr) {
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
i64 addend = 0;
|
||||
|
||||
switch (rels[i].type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
switch (rels[i].p2size) {
|
||||
case 2:
|
||||
addend = *(il32 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
|
||||
break;
|
||||
case 3:
|
||||
addend = *(il64 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_ADDEND:
|
||||
addend = rels[i++].idx;
|
||||
break;
|
||||
}
|
||||
|
||||
MachRel &r = rels[i];
|
||||
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
|
||||
|
||||
Relocation<E> &rel = vec.back();
|
||||
|
||||
if (i > 0 && rels[i - 1].type == ARM64_RELOC_SUBTRACTOR)
|
||||
rel.is_subtracted = true;
|
||||
|
||||
if (!rel.is_subtracted && rels[i].type != ARM64_RELOC_SUBTRACTOR)
|
||||
rel.is_pcrel = r.is_pcrel;
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend) : addend;
|
||||
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
std::span<Relocation<E>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
u8 *loc = buf + r.offset;
|
||||
i64 val = r.addend;
|
||||
u64 pc = get_addr(ctx) + r.offset;
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute a relocated value.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_SUBTRACTOR: {
|
||||
Relocation<E> s = rels[++i];
|
||||
assert(s.type == ARM64_RELOC_UNSIGNED);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val += val2 - val1;
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
val += r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
val += r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES)
|
||||
val -= ctx.tls_begin;
|
||||
|
||||
// Write a computed value to the output buffer.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
if (r.is_pcrel)
|
||||
val -= pc;
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(ul32 *)loc = val;
|
||||
else if (r.p2size == 3)
|
||||
*(ul64 *)loc = val;
|
||||
else
|
||||
unreachable();
|
||||
break;
|
||||
case ARM64_RELOC_BRANCH26: {
|
||||
assert(r.is_pcrel);
|
||||
val -= pc;
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
val = isec.osec.thunks[r.thunk_idx]->get_addr(r.thunk_sym_idx) - pc;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
assert(r.is_pcrel);
|
||||
*(ul32 *)loc |= page_offset(val, pc);
|
||||
break;
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: {
|
||||
assert(!r.is_pcrel);
|
||||
u32 insn = *(ul32 *)loc;
|
||||
i64 scale = 0;
|
||||
if ((insn & 0x3b000000) == 0x39000000) {
|
||||
scale = bits(insn, 31, 30);
|
||||
if (scale == 0 && (insn & 0x04800000) == 0x04800000)
|
||||
scale = 4;
|
||||
}
|
||||
*(ul32 *)loc |= bits(val, 11, scale) << 10;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_RANGE_EXTN_THUNK;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
Subsection<E> &subsec, Relocation<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.stub_idx != -1)
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
if (!sym.subsec || &sym.subsec->isec.osec != &subsec.isec.osec)
|
||||
return false;
|
||||
|
||||
if (sym.subsec->output_offset == -1)
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 addr = sym.get_addr(ctx);
|
||||
i64 pc = subsec.get_addr(ctx) + rel.offset;
|
||||
i64 val = addr + rel.addend - pc;
|
||||
return -(1 << 27) <= val && val < (1 << 27);
|
||||
}
|
||||
|
||||
// We create a thunk no further than 100 MiB from any section.
|
||||
static 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;
|
||||
|
||||
// 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".
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<Subsection<E> *> members = osec.members;
|
||||
if (members.empty())
|
||||
return;
|
||||
|
||||
members[0]->output_offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->output_offset = -1;
|
||||
});
|
||||
|
||||
// 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]->output_offset < MAX_DISTANCE) {
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->output_offset = offset;
|
||||
offset += members[d]->input_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by GROUP_SIZE.
|
||||
while (c < members.size() &&
|
||||
members[c]->output_offset - members[b]->output_offset < GROUP_SIZE)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->output_offset + members[c - 1]->input_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.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
std::span<Relocation<E>> rels = subsec->get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
if (!r.sym->file || r.type != ARM64_RELOC_BRANCH26)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, *r.sym, *subsec, r))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (r.sym->thunk_idx != -1) {
|
||||
r.thunk_idx = r.sym->thunk_idx;
|
||||
r.thunk_sym_idx = r.sym->thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
r.thunk_idx = thunk.thunk_idx;
|
||||
r.thunk_sym_idx = -1;
|
||||
|
||||
if (!(r.sym->flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
|
||||
NEEDS_RANGE_EXTN_THUNK)) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(r.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 std::tuple{a->file->priority, a->value} <
|
||||
std::tuple{b->file->priority, b->value};
|
||||
});
|
||||
|
||||
// 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.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
for (Relocation<E> &r : subsec->get_rels())
|
||||
if (r.thunk_idx == thunk.thunk_idx)
|
||||
r.thunk_sym_idx = r.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.hdr.size = offset;
|
||||
}
|
||||
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.hdr.offset + offset;
|
||||
|
||||
static const ul32 data[] = {
|
||||
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 addr = symbols[i]->get_addr(ctx);
|
||||
u64 pc = output_section.hdr.addr + offset + i * ENTRY_SIZE;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
*(ul32 *)loc |= page_offset(addr, pc);
|
||||
*(ul32 *)(loc + 4) |= bits(addr, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
#define ASSERT_RANGE(val, start, size) \
|
||||
assert((start) <= (val) && (val) < ((start) + (size)))
|
||||
|
||||
// Rewrite
|
||||
// ldr Xb/Wb, [Xa, #imm]
|
||||
// with
|
||||
// ldr Xb/Wb, [#base_addr + #imm]
|
||||
// if we can.
|
||||
static bool relax_ldr(ul32 *loc, u64 loc_addr, u64 base_addr) {
|
||||
switch (*loc & 0xffc0'0000) {
|
||||
case 0xf940'0000: { // ldr Xb, [Xa, #imm]
|
||||
u64 imm = bits(*loc, 21, 10) * 8;
|
||||
i64 disp = base_addr + imm - loc_addr;
|
||||
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
|
||||
// ldr Xc, [#base_addr + #imm]
|
||||
*loc = 0x5800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case 0xb940'0000: { // ldr Wb, [Xa, #imm]
|
||||
u64 imm = bits(*loc, 21, 10) * 4;
|
||||
i64 disp = base_addr + imm - loc_addr;
|
||||
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
|
||||
// ldr Wc, [#base_addr + #imm]
|
||||
*loc = 0x1800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_adrp(ul32 *loc) {
|
||||
return (*loc & 0x9f00'0000) == 0x9000'0000;
|
||||
}
|
||||
|
||||
static u64 get_adrp_imm(ul32 *loc) {
|
||||
return (bits(*loc, 23, 5) << 14) + (bits(*loc, 30, 29) << 12);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void relax_adrp_ldr_got_ldr(Context<E> &ctx,
|
||||
ul32 *loc1, ul32 *loc2, ul32 *loc3,
|
||||
u64 addr1, u64 addr2, u64 addr3,
|
||||
ObjectFile<E> *file) {
|
||||
// We expect the following instructions for a GOT-indirect load:
|
||||
//
|
||||
// adrp Xa, _foo@GOTPAGE
|
||||
// ldr Xb, [Xa, _foo@GOTPAGEOFF]
|
||||
// ldr/ldrb/ldrsb/ldrsh/... Xc/Wc, [Xb, #imm]
|
||||
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0xf940'0000)
|
||||
return;
|
||||
|
||||
u64 got_page = page(addr1) + get_adrp_imm(loc1);
|
||||
u64 pageoff = bits(*loc2, 21, 10) << 3;
|
||||
|
||||
if (got_page + pageoff < ctx.got.hdr.addr ||
|
||||
ctx.got.hdr.addr + ctx.got.hdr.size <= got_page + pageoff)
|
||||
Fatal(ctx) << *file << ": LDR_GOT_LDR out of range";
|
||||
|
||||
ASSERT_RANGE(got_page + pageoff, ctx.got.hdr.addr, ctx.got.hdr.size);
|
||||
|
||||
u64 got_value = *(ul64 *)(ctx.buf + ctx.got.hdr.offset + got_page + pageoff -
|
||||
ctx.got.hdr.addr);
|
||||
|
||||
// If the GOT slot is already filled with a value and we can
|
||||
// inline the value, do it.
|
||||
if (got_value && relax_ldr(loc3, addr3, got_value)) {
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
*loc2 = 0xd503'201f; // nop
|
||||
return;
|
||||
}
|
||||
|
||||
// If the GOT slot is close enough to PC, we can eliminate ADRP.
|
||||
if (relax_ldr(loc2, addr2, got_page))
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_adrp(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// adrp Xa, _bar@PAGE
|
||||
// ->
|
||||
// ldr Xa, _foo@PAGE
|
||||
// nop
|
||||
|
||||
if (is_adrp(loc1) && is_adrp(loc2) &&
|
||||
page(addr1) + get_adrp_imm(loc1) == page(addr2) + get_adrp_imm(loc2) &&
|
||||
bits(*loc1, 4, 0) == bits(*loc2, 4, 0))
|
||||
*loc2 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_ldr(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// ldr Xb/Wb, [Xa, _foo@PAGEOFF]
|
||||
// ->
|
||||
// nop
|
||||
// ldr Xb/Wb, [_foo]
|
||||
if (is_adrp(loc1) && relax_ldr(loc2, addr2, page(addr1) + get_adrp_imm(loc1)))
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_add(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// add Xb, Xa, _foo@PAGEOFF
|
||||
// ->
|
||||
// nop
|
||||
// adr Xb, _foo
|
||||
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0x9100'0000)
|
||||
return;
|
||||
|
||||
i64 disp = page(addr1) + get_adrp_imm(loc1) + bits(*loc2, 21, 10) - addr2;
|
||||
|
||||
if (disp == sign_extend(disp, 20)) {
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
*loc2 = 0x1000'0000 | (bits(disp, 1, 0) << 29) |
|
||||
(bits(disp, 20, 2) << 5) | bits(*loc2, 4, 0); // adr Xb, _foo
|
||||
}
|
||||
}
|
||||
|
||||
static Subsection<E> *find_subsection_by_addr(ObjectFile<E> *file, u32 addr) {
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
if (subsec->input_addr <= addr &&
|
||||
addr < subsec->input_addr + subsec->input_size)
|
||||
return subsec;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// On ARM, we generally need two or more instructions to materialize
|
||||
// an address of an object in a register or jump to a function.
|
||||
// However, if an object or a function is close enough to PC, a single
|
||||
// instruction suffices.
|
||||
//
|
||||
// This function replaces such redundant instruction sequences with
|
||||
// shorter instruction sequences. We pad holes with NOPs, so the total
|
||||
// number of instructions won't change by this relaxation, but replacing
|
||||
// instructions with NOPs generally makes program faster since the CPU
|
||||
// has a special logic to skip NOPs as quickly as possible.
|
||||
//
|
||||
// Locations of relaxable instructions are in the
|
||||
// LC_LINKER_OPTIMIZATION_HINT segment. That segment contains a
|
||||
// sequence of ULEB-encoded integers.
|
||||
//
|
||||
// This pass is optional; an output file works correctly without
|
||||
// applying these optimizations.
|
||||
void apply_linker_optimization_hints(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_linker_optimization_hints");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::string_view hints = file->get_linker_optimization_hints(ctx);
|
||||
|
||||
while (!hints.empty()) {
|
||||
i64 type = read_uleb(hints);
|
||||
if (type == 0)
|
||||
return;
|
||||
|
||||
i64 nargs = read_uleb(hints);
|
||||
|
||||
switch (type) {
|
||||
case LOH_ARM64_ADRP_LDR_GOT_LDR: {
|
||||
assert(nargs == 3);
|
||||
i64 input_addr1 = read_uleb(hints);
|
||||
i64 input_addr2 = read_uleb(hints);
|
||||
i64 input_addr3 = read_uleb(hints);
|
||||
|
||||
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
|
||||
if (!subsec || !subsec->is_alive)
|
||||
return;
|
||||
|
||||
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr3, subsec->input_addr, subsec->input_size);
|
||||
|
||||
i64 offset1 = subsec->input_addr - input_addr1;
|
||||
i64 offset2 = subsec->input_addr - input_addr2;
|
||||
i64 offset3 = subsec->input_addr - input_addr3;
|
||||
|
||||
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
|
||||
ul32 *loc1 = (ul32 *)(loc + offset1);
|
||||
ul32 *loc2 = (ul32 *)(loc + offset2);
|
||||
ul32 *loc3 = (ul32 *)(loc + offset3);
|
||||
|
||||
u64 addr1 = subsec->get_addr(ctx) + offset1;
|
||||
u64 addr2 = subsec->get_addr(ctx) + offset2;
|
||||
u64 addr3 = subsec->get_addr(ctx) + offset3;
|
||||
|
||||
relax_adrp_ldr_got_ldr(ctx, loc1, loc2, loc3, addr1, addr2, addr3,
|
||||
file);
|
||||
break;
|
||||
}
|
||||
case LOH_ARM64_ADRP_ADRP:
|
||||
case LOH_ARM64_ADRP_LDR:
|
||||
case LOH_ARM64_ADRP_ADD: {
|
||||
assert(nargs == 2);
|
||||
i64 input_addr1 = read_uleb(hints);
|
||||
i64 input_addr2 = read_uleb(hints);
|
||||
|
||||
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
|
||||
if (!subsec || !subsec->is_alive)
|
||||
return;
|
||||
|
||||
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
|
||||
|
||||
i64 offset1 = subsec->input_addr - input_addr1;
|
||||
i64 offset2 = subsec->input_addr - input_addr2;
|
||||
|
||||
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
|
||||
ul32 *loc1 = (ul32 *)(loc + offset1);
|
||||
ul32 *loc2 = (ul32 *)(loc + offset2);
|
||||
|
||||
u64 addr1 = subsec->get_addr(ctx) + offset1;
|
||||
u64 addr2 = subsec->get_addr(ctx) + offset2;
|
||||
|
||||
switch (type) {
|
||||
case LOH_ARM64_ADRP_ADRP:
|
||||
relax_adrp_adrp(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
case LOH_ARM64_ADRP_LDR:
|
||||
relax_adrp_ldr(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
case LOH_ARM64_ADRP_ADD:
|
||||
relax_adrp_add(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Skip unsupported optimizations hints.
|
||||
for (i64 i = 0; i < nargs; i++)
|
||||
read_uleb(hints);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,216 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
template <>
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->hdr.offset;
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
// `ff 25 xx xx xx xx` is a RIP-relative indirect jump instruction,
|
||||
// i.e., `jmp *IMM(%rip)`. It loads an address from la_symbol_ptr
|
||||
// and jump there.
|
||||
static_assert(E::stub_size == 6);
|
||||
buf[i * 6] = 0xff;
|
||||
buf[i * 6 + 1] = 0x25;
|
||||
*(ul32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * word_size - (this->hdr.addr + i * 6 + 6);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *start = ctx.buf + this->hdr.offset;
|
||||
u8 *buf = start;
|
||||
|
||||
static const u8 insn0[] = {
|
||||
0x4c, 0x8d, 0x1d, 0, 0, 0, 0, // lea $__dyld_private(%rip), %r11
|
||||
0x41, 0x53, // push %r11
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *$dyld_stub_binder@GOT(%rip)
|
||||
0x90, // nop
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
|
||||
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
*(ul32 *)(buf + 3) =
|
||||
get_symbol(ctx, "__dyld_private")->get_addr(ctx) - this->hdr.addr - 7;
|
||||
*(ul32 *)(buf + 11) =
|
||||
get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx) - this->hdr.addr - 15;
|
||||
|
||||
buf += 16;
|
||||
|
||||
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
|
||||
u8 insn[] = {
|
||||
0x68, 0, 0, 0, 0, // push $bind_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp $__stub_helper
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(ul32 *)(buf + 6) = start - buf - 10;
|
||||
buf += 10;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 get_reloc_addend(u32 type) {
|
||||
switch (type) {
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
return 1;
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
return 2;
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(il32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
if (r.p2size == 3)
|
||||
return *(il64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = rels[i];
|
||||
i64 addend = read_addend((u8 *)file.mf->data + hdr.offset, r);
|
||||
|
||||
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
|
||||
Relocation<E> &rel = vec.back();
|
||||
|
||||
if (i > 0 && rels[i - 1].type == X86_64_RELOC_SUBTRACTOR)
|
||||
rel.is_subtracted = true;
|
||||
|
||||
if (!rel.is_subtracted && rels[i].type != X86_64_RELOC_SUBTRACTOR)
|
||||
rel.is_pcrel = r.is_pcrel;
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend + 4) : addend;
|
||||
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
case X86_64_RELOC_SUBTRACTOR:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
std::span<Relocation<E>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
u64 val = r.addend;
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
case X86_64_RELOC_SIGNED:
|
||||
case X86_64_RELOC_BRANCH:
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_SUBTRACTOR: {
|
||||
Relocation<E> s = rels[++i];
|
||||
assert(s.type == X86_64_RELOC_UNSIGNED);
|
||||
assert(r.p2size == s.p2size);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val += val2 - val1;
|
||||
break;
|
||||
}
|
||||
case X86_64_RELOC_GOT:
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
val += r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
val += r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES) {
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
val -= ctx.tls_begin;
|
||||
} else if (r.is_pcrel) {
|
||||
val -= get_addr(ctx) + r.offset + 4 + get_reloc_addend(r.type);
|
||||
}
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(ul32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(ul64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,572 @@
|
||||
#include "mold.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
Options:
|
||||
-F<PATH> Add DIR to framework search path
|
||||
-L<PATH> Add DIR to library search path
|
||||
-ObjC Load all static archive members that implement
|
||||
an Objective-C class or category
|
||||
-U <SYMBOL> Allow a symbol to be undefined
|
||||
-Z Do not search the standard directories when
|
||||
searching for libraries and frameworks
|
||||
-add_ast_path <FILE> Add a N_AST symbol with the given filename
|
||||
-add_empty_section <SEGNAME> <SECTNAME>
|
||||
Add an empty section
|
||||
-adhoc_codesign Add ad-hoc code signature to the output file
|
||||
-no_adhoc_codesign
|
||||
-all_load Include all objects from static archives
|
||||
-noall_load
|
||||
-application_extension Verify that all dylibs are extension-safe
|
||||
-no_application_extension
|
||||
-arch <ARCH_NAME> Specify target architecture
|
||||
-bundle Produce a mach-o bundle
|
||||
-bundle_loader <EXECUTABLE> Resolve undefined symbols using the given executable
|
||||
-compatibility_version <VERSION>
|
||||
Specifies the compatibility version number of the library
|
||||
-current_version <VERSION> Specifies the current version number of the library.
|
||||
-dead_strip Remove unreachable functions and data
|
||||
-dead_strip_dylibs Remove unreachable dylibs from dependencies
|
||||
-debug_variant Ignored
|
||||
-demangle Demangle C++ symbols in log messages (default)
|
||||
-dependency_info <FILE> Ignored
|
||||
-dylib Produce a dynamic library
|
||||
-dylib_compatibility_version <VERSION>
|
||||
Alias for -compatibility_version
|
||||
-dylib_current_version <VERSION>
|
||||
Alias for -current_version
|
||||
-dylib_install_name Alias for -install_name
|
||||
-dynamic Link against dylibs (default)
|
||||
-e <SYMBOL> Specify the entry point of a main executable
|
||||
-execute Produce an executable (default)
|
||||
-export_dynamic Preserves all global symbols in main executables during LTO
|
||||
-exported_symbol <SYMBOL> Export a given symbol
|
||||
-exported_symbols_list <FILE>
|
||||
Read a list of exported symbols from a given file
|
||||
-filelist <FILE>[,<DIR>] Specify the list of input file names
|
||||
-final_output <NAME>
|
||||
-force_load <FILE> Include all objects from a given static archive
|
||||
-framework <NAME>,[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-headerpad <SIZE> Allocate the size of padding after load commands
|
||||
-headerpad_max_install_names
|
||||
Allocate MAXPATHLEN byte padding after load commands
|
||||
-help Report usage information
|
||||
-hidden-l<LIB>
|
||||
-ignore_optimization_hints Do not rewrite instructions as optimization (default)
|
||||
-enable_optimization_hints
|
||||
-install_name <NAME>
|
||||
-l<LIB> Search for a given library
|
||||
-lto_library <FILE> Load a LTO linker plugin library
|
||||
-macos_version_min <VERSION>
|
||||
-map <FILE> Write map file to a given file
|
||||
-mark_dead_strippable_dylib Mark the output as dead-strippable
|
||||
-needed-l<LIB> Search for a given library
|
||||
-needed-framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-no_deduplicate Ignored
|
||||
-no_function_starts Do not generate an LC_FUNCTION_STARTS load command
|
||||
-no_uuid Do not generate an LC_UUID load command
|
||||
-o <FILE> Set output filename
|
||||
-objc_abi_version <VERSION> Ignored
|
||||
-object_path_lto <FILE> Write a LTO temporary file to a given path
|
||||
-order_file <FILE> Layout functions and data according to specification in a given file
|
||||
-pagezero_size <SIZE> Specify the size of the __PAGEZERO segment
|
||||
-platform_version <PLATFORM> <MIN_VERSION> <SDK_VERSION>
|
||||
Set platform, platform version and SDK version
|
||||
-random_uuid Generate a random LC_UUID load command
|
||||
-reexport-l<LIB> Search for a given library
|
||||
-rpath <PATH> Add PATH to the runpath search path list
|
||||
-search_dylibs_first
|
||||
-search_paths_first
|
||||
-sectalign <SEGNAME> <SECTNAME> <VALUE>
|
||||
Set a section's alignment to a given value
|
||||
-sectcreate <SEGNAME> <SECTNAME> <FILE>
|
||||
-stack_size <SIZE>
|
||||
-stats Show statistics info
|
||||
-syslibroot <DIR> Prepend DIR to library search paths
|
||||
-t Print out each file the linker loads
|
||||
-thread_count <NUMBER> Use given number of threads
|
||||
-u <SYMBOL> Force load a given symbol from archive if necessary
|
||||
-unexported_symbol <SYMBOL> Export all but a given symbol
|
||||
-unexported_symbols_list <FILE>
|
||||
Read a list of unexported symbols from a given file
|
||||
-v Report version information
|
||||
-weak_framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-weak-l<LIB> Search for a given library)";
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
|
||||
static std::regex re(R"(\d+)", std::regex_constants::ECMAScript);
|
||||
if (std::regex_match(arg.begin(), arg.end(), re))
|
||||
return stoi(std::string(arg));
|
||||
|
||||
if (arg == "macos")
|
||||
return PLATFORM_MACOS;
|
||||
if (arg == "ios")
|
||||
return PLATFORM_IOS;
|
||||
if (arg == "tvos")
|
||||
return PLATFORM_TVOS;
|
||||
if (arg == "watchos")
|
||||
return PLATFORM_WATCHOS;
|
||||
if (arg == "bridgeos")
|
||||
return PLATFORM_BRIDGEOS;
|
||||
if (arg == "mac-catalyst")
|
||||
return PLATFORM_MACCATALYST;
|
||||
if (arg == "ios-simulator")
|
||||
return PLATFORM_IOSSIMULATOR;
|
||||
if (arg == "tvos-simulator")
|
||||
return PLATFORM_TVOSSIMULATOR;
|
||||
if (arg == "watchos-simulator")
|
||||
return PLATFORM_WATCHOSSIMULATOR;
|
||||
if (arg == "driverkit")
|
||||
return PLATFORM_DRIVERKIT;
|
||||
Fatal(ctx) << "unknown -platform_version name: " << arg;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 parse_version(Context<E> &ctx, std::string_view arg) {
|
||||
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)",
|
||||
std::regex_constants::ECMAScript);
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(arg.data(), arg.data() + arg.size(), m, re))
|
||||
Fatal(ctx) << "malformed version number: " << arg;
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
|
||||
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 parse_hex(Context<E> &ctx, std::string_view arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::icase;
|
||||
static std::regex re(R"((?:0x)?[0-9a-f]+)", flags);
|
||||
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(arg.begin(), arg.end(), re))
|
||||
Fatal(ctx) << "malformed hexadecimal number: " << arg;
|
||||
|
||||
if (arg.starts_with("0x") || arg.starts_with("0X"))
|
||||
return std::stoll(std::string(arg.substr(2)), nullptr, 16);
|
||||
return std::stoll(std::string(arg), nullptr, 16);
|
||||
}
|
||||
|
||||
static bool is_directory(std::filesystem::path path) {
|
||||
std::error_code ec;
|
||||
return std::filesystem::is_directory(path, ec) && !ec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string>
|
||||
read_lines(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);
|
||||
|
||||
std::vector<std::string> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t pos = data.find('\n');
|
||||
std::string_view line;
|
||||
|
||||
if (pos == data.npos) {
|
||||
line = data;
|
||||
data = "";
|
||||
} else {
|
||||
line = data.substr(0, pos);
|
||||
data = data.substr(pos + 1);
|
||||
}
|
||||
|
||||
line = string_trim(line);
|
||||
if (!line.empty() && !line.starts_with('#'))
|
||||
vec.push_back(std::string(line));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::vector<std::string_view> &args = ctx.cmdline_args;
|
||||
std::vector<std::string> remaining;
|
||||
i64 i = 1;
|
||||
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
bool nostdlib = false;
|
||||
bool version_shown = false;
|
||||
std::optional<i64> pagezero_size;
|
||||
|
||||
while (i < args.size()) {
|
||||
std::string_view arg;
|
||||
std::string_view arg2;
|
||||
std::string_view arg3;
|
||||
u64 hex_arg;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg2 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 2)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
arg2 = args[i + 2];
|
||||
i += 3;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg3 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 3)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
arg2 = args[i + 2];
|
||||
arg3 = args[i + 3];
|
||||
i += 4;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_joined = [&](std::string name) {
|
||||
if (read_arg(name))
|
||||
return true;
|
||||
if (args[i].starts_with(name)) {
|
||||
arg = args[i].substr(name.size());
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_flag = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_hex = [&](std::string name) {
|
||||
if (!read_arg(name))
|
||||
return false;
|
||||
|
||||
size_t pos;
|
||||
hex_arg = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed " << name << ": " << arg;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (args[i].starts_with('@')) {
|
||||
std::vector<std::string_view> vec =
|
||||
read_response_file(ctx, args[i].substr(1));
|
||||
args.erase(args.begin() + i);
|
||||
args.insert(args.begin() + i, vec.begin(), vec.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_flag("-help") || read_flag("--help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (read_joined("-F")) {
|
||||
framework_paths.push_back(std::string(arg));
|
||||
} else if (read_joined("-L")) {
|
||||
library_paths.push_back(std::string(arg));
|
||||
} else if (read_flag("-Z")) {
|
||||
nostdlib = true;
|
||||
} else if (read_flag("-ObjC")) {
|
||||
ctx.arg.ObjC = true;
|
||||
} else if (read_arg("-U")) {
|
||||
ctx.arg.U.push_back(std::string(arg));
|
||||
} else if (read_arg("-add_ast_path")) {
|
||||
ctx.arg.add_ast_path.push_back(std::string(arg));
|
||||
} else if (read_arg2("-add_empty_section")) {
|
||||
ctx.arg.add_empty_section.push_back({arg, arg2});
|
||||
} else if (read_flag("-adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = true;
|
||||
} else if (read_flag("-no_adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = false;
|
||||
} else if (read_flag("-all_load")) {
|
||||
remaining.push_back("-all_load");
|
||||
} else if (read_flag("-noall_load")) {
|
||||
remaining.push_back("-noall_load");
|
||||
} else if (read_flag("-application_extension")) {
|
||||
ctx.arg.application_extension = true;
|
||||
} else if (read_flag("-no_application_extension")) {
|
||||
ctx.arg.application_extension = false;
|
||||
} else if (read_arg("-arch")) {
|
||||
if (arg == "x86_64")
|
||||
ctx.arg.arch = CPU_TYPE_X86_64;
|
||||
else if (arg == "arm64")
|
||||
ctx.arg.arch = CPU_TYPE_ARM64;
|
||||
else
|
||||
Fatal(ctx) << "unknown -arch: " << arg;
|
||||
} else if (read_flag("-bundle")) {
|
||||
ctx.output_type = MH_BUNDLE;
|
||||
} else if (read_arg("-bundle_loader")) {
|
||||
ctx.arg.bundle_loader = arg;
|
||||
} else if (read_arg("-compatibility_version") ||
|
||||
read_arg("-dylib_compatibility_version")) {
|
||||
ctx.arg.compatibility_version = parse_version(ctx, arg);
|
||||
} else if (read_arg("-current_version") ||
|
||||
read_arg("-dylib_current_version")) {
|
||||
ctx.arg.current_version = parse_version(ctx, arg);
|
||||
} else if (read_flag("-color-diagnostics") ||
|
||||
read_flag("--color-diagnostics")) {
|
||||
ctx.arg.color_diagnostics = true;
|
||||
} else if (read_flag("-dead_strip")) {
|
||||
ctx.arg.dead_strip = true;
|
||||
} else if (read_flag("-dead_strip_dylibs")) {
|
||||
ctx.arg.dead_strip_dylibs = true;
|
||||
} else if (read_flag("-debug_variant")) {
|
||||
} else if (read_flag("-demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_arg("-dependency_info")) {
|
||||
ctx.arg.dependency_info = arg;
|
||||
} else if (read_flag("-dylib")) {
|
||||
ctx.output_type = MH_DYLIB;
|
||||
} else if (read_hex("-headerpad")) {
|
||||
ctx.arg.headerpad = hex_arg;
|
||||
} else if (read_flag("-headerpad_max_install_names")) {
|
||||
ctx.arg.headerpad = 1024;
|
||||
} else if (read_flag("-dynamic")) {
|
||||
ctx.arg.dynamic = true;
|
||||
} else if (read_arg("-e")) {
|
||||
ctx.arg.entry = get_symbol(ctx, arg);
|
||||
} else if (read_flag("-execute")) {
|
||||
ctx.output_type = MH_EXECUTE;
|
||||
} else if (read_flag("-export_dynamic")) {
|
||||
ctx.arg.export_dynamic = true;
|
||||
} else if (read_arg("-exported_symbol")) {
|
||||
if (!ctx.arg.exported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-exported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-exported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.exported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-exported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_arg("-fatal_warnings")) {
|
||||
} else if (read_arg("-filelist")) {
|
||||
remaining.push_back("-filelist");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-final_output")) {
|
||||
ctx.arg.final_output = arg;
|
||||
} else if (read_arg("-force_load")) {
|
||||
remaining.push_back("-force_load");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-framework")) {
|
||||
remaining.push_back("-framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-lto_library")) {
|
||||
ctx.arg.lto_library = arg;
|
||||
} else if (read_arg("-macos_version_min")) {
|
||||
ctx.arg.platform = PLATFORM_MACOS;
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg);
|
||||
} else if (read_joined("-hidden-l")) {
|
||||
remaining.push_back("-hidden-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-ignore_optimization_hints")) {
|
||||
ctx.arg.ignore_optimization_hints = true;
|
||||
} else if (read_flag("-enable_optimization_hints")) {
|
||||
ctx.arg.ignore_optimization_hints = false;
|
||||
} else if (read_arg("-install_name") || read_arg("-dylib_install_name")) {
|
||||
ctx.arg.install_name = arg;
|
||||
} else if (read_joined("-l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-map")) {
|
||||
ctx.arg.map = arg;
|
||||
} else if (read_flag("-mark_dead_strippable_dylib")) {
|
||||
ctx.arg.mark_dead_strippable_dylib = true;
|
||||
} else if (read_arg("-mllvm")) {
|
||||
ctx.arg.mllvm.push_back(std::string(arg));
|
||||
} else if (read_joined("-needed-l")) {
|
||||
remaining.push_back("-needed-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-needed_framework")) {
|
||||
remaining.push_back("-needed_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-no_deduplicate")) {
|
||||
} else if (read_flag("-no_function_starts")) {
|
||||
ctx.arg.function_starts = false;
|
||||
} else if (read_flag("-no_uuid")) {
|
||||
ctx.arg.uuid = UUID_NONE;
|
||||
} else if (read_arg("-o")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg("-objc_abi_version")) {
|
||||
} else if (read_arg("-object_path_lto")) {
|
||||
ctx.arg.object_path_lto = arg;
|
||||
} else if (read_arg("-order_file")) {
|
||||
ctx.arg.order_file = read_lines(ctx, arg);
|
||||
} else if (read_hex("-pagezero_size")) {
|
||||
pagezero_size = hex_arg;
|
||||
} else if (read_flag("-perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_arg3("-platform_version")) {
|
||||
ctx.arg.platform = parse_platform(ctx, arg);
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg2);
|
||||
ctx.arg.platform_sdk_version = parse_version(ctx, arg3);
|
||||
} else if (read_flag("-quick_exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag("-no_quick_exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_flag("-random_uuid")) {
|
||||
ctx.arg.uuid = UUID_RANDOM;
|
||||
} else if (read_joined("-reexport-l")) {
|
||||
remaining.push_back("-reexport-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-rpath")) {
|
||||
ctx.arg.rpath.push_back(std::string(arg));
|
||||
} else if (read_flag("-search_paths_first")) {
|
||||
ctx.arg.search_paths_first = true;
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
ctx.arg.search_paths_first = false;
|
||||
} else if (read_arg3("-sectalign")) {
|
||||
u64 val = parse_hex(ctx, arg3);
|
||||
std::string key = std::string(arg) + "," + std::string(arg2);
|
||||
if (!has_single_bit(val))
|
||||
Fatal(ctx) << "-sectalign: invalid alignment value: " << arg3;
|
||||
ctx.arg.sectalign.push_back({arg, arg2, (u8)std::countl_zero(val)});
|
||||
} else if (read_arg3("-sectcreate")) {
|
||||
ctx.arg.sectcreate.push_back({arg, arg2, arg3});
|
||||
} else if (read_hex("-stack_size")) {
|
||||
ctx.arg.stack_size = hex_arg;
|
||||
} else if (read_flag("-stats")) {
|
||||
ctx.arg.stats = true;
|
||||
Counter::enabled = true;
|
||||
} else if (read_arg("-syslibroot")) {
|
||||
ctx.arg.syslibroot.push_back(std::string(arg));
|
||||
} else if (read_flag("-t")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_arg("-thread_count")) {
|
||||
ctx.arg.thread_count = std::stoi(std::string(arg));
|
||||
} else if (read_arg("-u")) {
|
||||
ctx.arg.u.push_back(std::string(arg));
|
||||
} else if (read_arg("-unexported_symbol")) {
|
||||
if (!ctx.arg.unexported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-unexported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-unexported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.unexported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-unexported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_flag("-v")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
version_shown = true;
|
||||
} else if (read_arg("-weak_framework")) {
|
||||
remaining.push_back("-weak_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_joined("-weak-l")) {
|
||||
remaining.push_back("-weak-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else {
|
||||
if (args[i][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[i];
|
||||
remaining.push_back(std::string(args[i]));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.arg.entry)
|
||||
ctx.arg.entry = get_symbol(ctx, "_main");
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
|
||||
if (!ctx.arg.bundle_loader.empty() && ctx.output_type != MH_BUNDLE)
|
||||
Fatal(ctx) << "-bundle_loader cannot be specified without -bundle";
|
||||
|
||||
auto add_search_path = [&](std::vector<std::string> &vec, std::string path) {
|
||||
if (!path.starts_with('/') || ctx.arg.syslibroot.empty()) {
|
||||
if (is_directory(path))
|
||||
vec.push_back(path);
|
||||
return;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (std::string &dir : ctx.arg.syslibroot) {
|
||||
std::string str = path_clean(dir + "/" + path);
|
||||
if (is_directory(str)) {
|
||||
vec.push_back(str);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found && is_directory(path))
|
||||
vec.push_back(path);
|
||||
};
|
||||
|
||||
for (std::string &path : library_paths)
|
||||
add_search_path(ctx.arg.library_paths, path);
|
||||
|
||||
if (!nostdlib) {
|
||||
add_search_path(ctx.arg.library_paths, "/usr/lib");
|
||||
add_search_path(ctx.arg.library_paths, "/usr/local/lib");
|
||||
}
|
||||
|
||||
for (std::string &path : framework_paths)
|
||||
add_search_path(ctx.arg.framework_paths, path);
|
||||
|
||||
if (!nostdlib) {
|
||||
add_search_path(ctx.arg.framework_paths, "/Library/Frameworks");
|
||||
add_search_path(ctx.arg.framework_paths, "/System/Library/Frameworks");
|
||||
}
|
||||
|
||||
if (pagezero_size.has_value()) {
|
||||
if (ctx.output_type != MH_EXECUTE)
|
||||
Fatal(ctx) << "-pagezero_size option can only be used when"
|
||||
<< " linking a main executable";
|
||||
ctx.arg.pagezero_size = *pagezero_size;
|
||||
} else {
|
||||
ctx.arg.pagezero_size = (ctx.output_type == MH_EXECUTE) ? 0x100000000 : 0;
|
||||
}
|
||||
|
||||
if (ctx.arg.final_output == "") {
|
||||
if (ctx.arg.install_name != "")
|
||||
ctx.arg.final_output = ctx.arg.install_name;
|
||||
else
|
||||
ctx.arg.final_output = ctx.arg.output;
|
||||
}
|
||||
|
||||
if (ctx.arg.uuid == UUID_RANDOM)
|
||||
memcpy(ctx.uuid, get_uuid_v4().data(), 16);
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
return remaining;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template i64 parse_version(Context<E> &, std::string_view);
|
||||
template std::vector<std::string> parse_nonpositional_args(Context<E> &);
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Subsection<E> *> collect_root_set(Context<E> &ctx) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
|
||||
std::vector<Subsection<E> *> rootset;
|
||||
|
||||
auto add = [&](Symbol<E> *sym) {
|
||||
if (sym->subsec)
|
||||
rootset.push_back(sym->subsec);
|
||||
};
|
||||
|
||||
auto keep = [&](Symbol<E> *sym) {
|
||||
if (sym->no_dead_strip)
|
||||
return true;
|
||||
if (ctx.output_type == MH_DYLIB || ctx.output_type == MH_BUNDLE)
|
||||
if (sym->scope == SCOPE_EXTERN || sym->referenced_dynamically)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && keep(sym))
|
||||
add(sym);
|
||||
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
if (const MachSection &hdr = subsec->isec.hdr;
|
||||
(hdr.attr & S_ATTR_NO_DEAD_STRIP) ||
|
||||
hdr.type == S_MOD_INIT_FUNC_POINTERS ||
|
||||
hdr.type == S_MOD_TERM_FUNC_POINTERS)
|
||||
rootset.push_back(subsec);
|
||||
}
|
||||
|
||||
for (std::string_view name : ctx.arg.u)
|
||||
if (Symbol<E> *sym = get_symbol(ctx, name); sym->file)
|
||||
add(sym);
|
||||
|
||||
add(ctx.arg.entry);
|
||||
add(get_symbol(ctx, "dyld_stub_binder"));
|
||||
return rootset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, Subsection<E> &subsec) {
|
||||
if (!fast_mark(subsec.is_alive))
|
||||
return;
|
||||
|
||||
for (Relocation<E> &rel : subsec.get_rels()) {
|
||||
if (rel.sym) {
|
||||
if (rel.sym->subsec)
|
||||
visit(ctx, *rel.sym->subsec);
|
||||
} else {
|
||||
visit(ctx, *rel.subsec);
|
||||
}
|
||||
}
|
||||
|
||||
for (UnwindRecord<E> &rec : subsec.get_unwind_records()) {
|
||||
visit(ctx, *rec.subsec);
|
||||
if (rec.lsda)
|
||||
visit(ctx, *rec.lsda);
|
||||
if (rec.personality && rec.personality->subsec)
|
||||
visit(ctx, *rec.personality->subsec);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool refers_live_subsection(Subsection<E> &subsec) {
|
||||
for (Relocation<E> &rel : subsec.get_rels()) {
|
||||
if (rel.sym) {
|
||||
if (rel.sym->subsec && rel.sym->subsec->is_alive)
|
||||
return true;
|
||||
} else {
|
||||
if (rel.subsec->is_alive)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
subsec->is_alive.store(false, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
|
||||
for (Subsection<E> *subsec : rootset)
|
||||
visit(ctx, *subsec);
|
||||
|
||||
bool repeat;
|
||||
do {
|
||||
repeat = false;
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (Subsection<E> *subsec : file->subsections) {
|
||||
if ((subsec->isec.hdr.attr & S_ATTR_LIVE_SUPPORT) &&
|
||||
!subsec->is_alive &&
|
||||
refers_live_subsection(*subsec)) {
|
||||
visit(ctx, *subsec);
|
||||
repeat = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (repeat);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void sweep(Context<E> &ctx) {
|
||||
Timer t(ctx, "sweep");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *&sym : file->syms)
|
||||
if (sym->file == file && sym->subsec && !sym->subsec->is_alive)
|
||||
sym = nullptr;
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::erase_if(file->subsections, [](Subsection<E> *subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx) {
|
||||
Timer t(ctx, "dead_strip");
|
||||
|
||||
std::vector<Subsection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void dead_strip(Context<E> &);
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,998 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
if (file.archive_name.empty())
|
||||
out << path_clean(file.filename);
|
||||
else
|
||||
out << path_clean(file.archive_name) << "(" << path_clean(file.filename) + ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
// This function is actually racy, in that "tearing" might be observed
|
||||
// during the read or write of `sym->file`. However, on most platforms
|
||||
// read/write of pointers never tears, and even if it did, it would be
|
||||
// harmless, as `sym->file` will not become equal to `sym`, or even if
|
||||
// it did by chance, we just wipe a symbol twice.
|
||||
//
|
||||
// Doing this in a standard-compliant way is too cumbersome, hence
|
||||
// here we just pretend a compiler will never optimize this into a
|
||||
// form that actually exploits the UB.
|
||||
template <typename E>
|
||||
__attribute__((no_sanitize("thread")))
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : syms) {
|
||||
if (sym->file == this) {
|
||||
sym->file = nullptr;
|
||||
sym->scope = SCOPE_LOCAL;
|
||||
sym->is_imported = false;
|
||||
sym->is_weak = false;
|
||||
sym->no_dead_strip = false;
|
||||
sym->subsec = nullptr;
|
||||
sym->value = 0;
|
||||
sym->is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>(mf);
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty() || ctx.all_load;
|
||||
obj->is_hidden = ctx.hidden_l;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE) {
|
||||
// Open an compiler IR file
|
||||
load_lto_plugin(ctx);
|
||||
this->lto_module =
|
||||
ctx.lto.module_create_from_memory(this->mf->data, this->mf->size);
|
||||
if (!this->lto_module)
|
||||
Fatal(ctx) << *this << ": lto_module_create_from_memory failed";
|
||||
|
||||
// Read a symbol table
|
||||
parse_lto_symbols(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
parse_sections(ctx);
|
||||
parse_symbols(ctx);
|
||||
|
||||
MachHeader &mach_hdr = *(MachHeader *)this->mf->data;
|
||||
if (mach_hdr.flags & MH_SUBSECTIONS_VIA_SYMBOLS)
|
||||
split_subsections_via_symbols(ctx);
|
||||
else
|
||||
init_subsections(ctx);
|
||||
|
||||
sort(subsections, [](Subsection<E> *a, Subsection<E> *b) {
|
||||
return a->input_addr < b->input_addr;
|
||||
});
|
||||
|
||||
fix_subsec_members(ctx);
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
isec->parse_relocations(ctx);
|
||||
|
||||
if (unwind_sec)
|
||||
parse_compact_unwind(ctx, *unwind_sec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
|
||||
SegmentCommand *cmd = (SegmentCommand *)find_load_command(ctx, LC_SEGMENT_64);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
MachSection *mach_sec = (MachSection *)((u8 *)cmd + sizeof(*cmd));
|
||||
|
||||
for (i64 i = 0; i < cmd->nsects; i++) {
|
||||
MachSection &msec = mach_sec[i];
|
||||
sections.push_back(nullptr);
|
||||
|
||||
if (msec.match("__LD", "__compact_unwind")) {
|
||||
unwind_sec = &msec;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.match("__DATA", "__objc_imageinfo") ||
|
||||
msec.match("__DATA_CONST", "__objc_imageinfo")) {
|
||||
if (msec.size != sizeof(ObjcImageInfo))
|
||||
Fatal(ctx) << *this << ": __objc_imageinfo: invalid size";
|
||||
|
||||
objc_image_info =
|
||||
(ObjcImageInfo *)(this->mf->get_contents().data() + msec.offset);
|
||||
|
||||
if (objc_image_info->version != 0)
|
||||
Fatal(ctx) << *this << ": __objc_imageinfo: unknown version: "
|
||||
<< (u32)objc_image_info->version;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.attr & S_ATTR_DEBUG)
|
||||
continue;
|
||||
|
||||
sections.back().reset(new InputSection<E>(ctx, *this, msec, i));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_symbols(Context<E> &ctx) {
|
||||
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
|
||||
this->syms.reserve(mach_syms.size());
|
||||
|
||||
i64 nlocal = 0;
|
||||
for (MachSym &msym : mach_syms)
|
||||
if (!msym.is_extern)
|
||||
nlocal++;
|
||||
local_syms.reserve(nlocal);
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
|
||||
|
||||
if (msym.is_extern) {
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
local_syms.emplace_back(name);
|
||||
Symbol<E> &sym = local_syms.back();
|
||||
|
||||
sym.file = this;
|
||||
sym.subsec = nullptr;
|
||||
sym.scope = SCOPE_LOCAL;
|
||||
sym.is_common = false;
|
||||
sym.is_weak = false;
|
||||
if (msym.type == N_ABS)
|
||||
sym.value = msym.value;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
this->syms.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u32 symidx;
|
||||
bool is_alt_entry;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SplitInfo<E>>
|
||||
split_regular_sections(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<SplitInfo<E>> vec(file.sections.size());
|
||||
|
||||
for (i64 i = 0; i < file.sections.size(); i++)
|
||||
if (InputSection<E> *isec = file.sections[i].get())
|
||||
if (isec->hdr.type != S_CSTRING_LITERALS)
|
||||
vec[i].isec = isec;
|
||||
|
||||
// Find all symbols whose type is N_SECT.
|
||||
for (i64 i = 0; i < file.mach_syms.size(); i++) {
|
||||
MachSym &msym = file.mach_syms[i];
|
||||
if (!msym.stab && msym.type == N_SECT && vec[msym.sect - 1].isec) {
|
||||
SplitRegion r;
|
||||
r.offset = msym.value - vec[msym.sect - 1].isec->hdr.addr;
|
||||
r.symidx = i;
|
||||
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
|
||||
vec[msym.sect - 1].regions.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
|
||||
|
||||
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
|
||||
return a.isec->hdr.addr < b.isec->hdr.addr;
|
||||
});
|
||||
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
sort(info.regions, [](const SplitRegion &a, const SplitRegion &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
}
|
||||
|
||||
// If two symbols point to the same location, we create only one
|
||||
// subsection.
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
i64 last = -1;
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry) {
|
||||
if (r.offset == last)
|
||||
r.is_alt_entry = true;
|
||||
last = r.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix regions so that they cover the entire section without overlapping.
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
std::vector<SplitRegion> &r = info.regions;
|
||||
|
||||
if (r.empty()) {
|
||||
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r[0].offset > 0)
|
||||
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
|
||||
|
||||
for (i64 i = 1; i < r.size(); i++)
|
||||
if (r[i - 1].offset == r[i].offset)
|
||||
r[i++].is_alt_entry = true;
|
||||
|
||||
i64 last = -1;
|
||||
for (i64 i = 0; i < r.size(); i++) {
|
||||
if (!r[i].is_alt_entry) {
|
||||
if (last != -1)
|
||||
r[last].size = r[i].offset - r[last].offset;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != -1)
|
||||
r[last].size = info.isec->hdr.size - r[last].offset;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::split_subsections_via_symbols(Context<E> &ctx) {
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
auto add = [&](InputSection<E> &isec, u32 offset, u32 size, u8 p2align,
|
||||
bool is_cstring) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = isec,
|
||||
.input_offset = offset,
|
||||
.input_size = size,
|
||||
.input_addr = (u32)(isec.hdr.addr + offset),
|
||||
.p2align = p2align,
|
||||
.is_cstring = is_cstring,
|
||||
};
|
||||
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections.push_back(subsec);
|
||||
};
|
||||
|
||||
// Split regular sections into subsections.
|
||||
for (SplitInfo<E> &info : split_regular_sections(ctx, *this)) {
|
||||
InputSection<E> &isec = *info.isec;
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry)
|
||||
add(isec, r.offset, r.size, isec.hdr.p2align, false);
|
||||
if (r.symidx != -1)
|
||||
sym_to_subsec[r.symidx] = subsections.back();
|
||||
}
|
||||
}
|
||||
|
||||
// Split __cstring section.
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections) {
|
||||
if (isec && isec->hdr.type == S_CSTRING_LITERALS) {
|
||||
std::string_view str = isec->contents;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < str.size()) {
|
||||
size_t end = str.find('\0', pos);
|
||||
if (end == str.npos)
|
||||
Fatal(ctx) << *this << " corruupted cstring section: " << *isec;
|
||||
|
||||
end = str.find_first_not_of('\0', end);
|
||||
if (end == str.npos)
|
||||
end = str.size();
|
||||
|
||||
// A constant string in __cstring has no alignment info, so we
|
||||
// need to infer it.
|
||||
u8 p2align = std::min<u8>(isec->hdr.p2align, std::countr_zero(pos));
|
||||
add(*isec, pos, end - pos, p2align, true);
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::init_subsections(Context<E> &ctx) {
|
||||
subsections.resize(sections.size());
|
||||
|
||||
for (i64 i = 0; i < sections.size(); i++) {
|
||||
if (InputSection<E> *isec = sections[i].get()) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_offset = 0,
|
||||
.input_size = (u32)isec->hdr.size,
|
||||
.input_addr = (u32)isec->hdr.addr,
|
||||
.p2align = (u8)isec->hdr.p2align,
|
||||
};
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections[i] = subsec;
|
||||
}
|
||||
}
|
||||
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.stab && msym.type == N_SECT)
|
||||
sym_to_subsec[i] = subsections[msym.sect - 1];
|
||||
}
|
||||
|
||||
std::erase(subsections, nullptr);
|
||||
}
|
||||
|
||||
// Fix local symbols `subsec` members.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::fix_subsec_members(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
|
||||
if (!msym.stab && !msym.is_extern && msym.type == N_SECT) {
|
||||
Subsection<E> *subsec = sym_to_subsec[i];
|
||||
if (!subsec)
|
||||
subsec = find_subsection(ctx, msym.sect - 1, msym.value);
|
||||
|
||||
if (subsec) {
|
||||
sym.subsec = subsec;
|
||||
sym.value = msym.value - subsec->input_addr;
|
||||
} else {
|
||||
// Subsec is null if a symbol is in a __compact_unwind.
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
|
||||
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE)) {
|
||||
data_in_code_entries = {
|
||||
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
|
||||
cmd->datasize / sizeof(DataInCodeEntry),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> ObjectFile<E>::get_linker_options(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE)
|
||||
return {};
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
std::vector<std::string> vec;
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
|
||||
if (lc.cmd == LC_LINKER_OPTION) {
|
||||
LinkerOptionCommand *cmd = (LinkerOptionCommand *)&lc;
|
||||
char *buf = (char *)cmd + sizeof(*cmd);
|
||||
for (i64 i = 0; i < cmd->count; i++) {
|
||||
vec.push_back(buf);
|
||||
buf += vec.back().size() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
|
||||
if (!this->mf)
|
||||
return nullptr;
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
if (lc.cmd == type)
|
||||
return &lc;
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Subsection<E> *
|
||||
ObjectFile<E>::find_subsection(Context<E> &ctx, u32 secidx, u32 addr) {
|
||||
assert(subsections.size() > 0);
|
||||
auto begin = subsections.begin();
|
||||
auto end = subsections.end();
|
||||
auto upper = std::upper_bound(begin, end, addr,
|
||||
[](u32 addr, Subsection<E> *subsec) { return addr < subsec->input_addr; });
|
||||
|
||||
if (upper == begin)
|
||||
return nullptr;
|
||||
Subsection<E> *subsec = *(upper - 1);
|
||||
if (subsec->isec.secidx == secidx)
|
||||
return subsec;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Symbol<E> *ObjectFile<E>::find_symbol(Context<E> &ctx, u32 addr) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++)
|
||||
if (MachSym &msym = mach_syms[i]; msym.is_extern && msym.value == addr)
|
||||
return this->syms[i];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
if (hdr.size % sizeof(CompactUnwindEntry))
|
||||
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
|
||||
|
||||
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
|
||||
unwind_records.reserve(num_entries);
|
||||
|
||||
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
|
||||
|
||||
// Read compact unwind entries
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
|
||||
|
||||
// Read relocations
|
||||
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = mach_rels[i];
|
||||
if (r.offset >= hdr.size)
|
||||
Fatal(ctx) << *this << ": relocation offset too large: " << i;
|
||||
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i
|
||||
<< " " << *this->syms[r.idx];
|
||||
};
|
||||
|
||||
if (r.is_pcrel || r.p2size != 3 || r.type)
|
||||
error();
|
||||
|
||||
switch (r.offset % sizeof(CompactUnwindEntry)) {
|
||||
case offsetof(CompactUnwindEntry, code_start): {
|
||||
Subsection<E> *target;
|
||||
if (r.is_extern)
|
||||
target = sym_to_subsec[r.idx];
|
||||
else
|
||||
target = find_subsection(ctx, r.idx - 1, src[idx].code_start);
|
||||
|
||||
if (!target)
|
||||
error();
|
||||
|
||||
dst.subsec = target;
|
||||
dst.offset = src[idx].code_start - target->input_addr;
|
||||
break;
|
||||
}
|
||||
case offsetof(CompactUnwindEntry, personality):
|
||||
if (r.is_extern) {
|
||||
dst.personality = this->syms[r.idx];
|
||||
} else {
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
dst.personality = find_symbol(ctx, addr);
|
||||
}
|
||||
|
||||
if (!dst.personality)
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported "
|
||||
<< "personality reference: " << i;
|
||||
break;
|
||||
case offsetof(CompactUnwindEntry, lsda): {
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
|
||||
Subsection<E> *target;
|
||||
if (r.is_extern)
|
||||
target = sym_to_subsec[r.idx];
|
||||
else
|
||||
target = find_subsection(ctx, r.idx - 1, addr);
|
||||
|
||||
if (!target)
|
||||
error();
|
||||
|
||||
dst.lsda = target;
|
||||
dst.lsda_offset = addr - target->input_addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error();
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
if (!unwind_records[i].subsec)
|
||||
Fatal(ctx) << *this << "_: _compact_unwind: missing relocation at " << i;
|
||||
|
||||
// Sort unwind entries by offset
|
||||
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
|
||||
return std::tuple(a.subsec->input_addr, a.offset) <
|
||||
std::tuple(b.subsec->input_addr, b.offset);
|
||||
});
|
||||
|
||||
// Associate unwind entries to subsections
|
||||
for (i64 i = 0; i < num_entries;) {
|
||||
Subsection<E> &subsec = *unwind_records[i].subsec;
|
||||
subsec.unwind_offset = i;
|
||||
|
||||
i64 j = i + 1;
|
||||
while (j < num_entries && unwind_records[j].subsec == &subsec)
|
||||
j++;
|
||||
subsec.nunwind = j - i;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
// Here is the list of priorities, from the highest to the lowest.
|
||||
//
|
||||
// 1. Strong defined symbol
|
||||
// 2. Weak defined symbol
|
||||
// 3. Strong defined symbol in a DSO/archive
|
||||
// 4. Weak Defined symbol in a DSO/archive
|
||||
// 5. Common symbol
|
||||
// 6. Common symbol in an archive
|
||||
// 7. Unclaimed (nonexistent) symbol
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, bool is_common, bool is_weak) {
|
||||
if (is_common) {
|
||||
assert(!file->is_dylib);
|
||||
if (!file->is_alive)
|
||||
return (6 << 24) + file->priority;
|
||||
return (5 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (file->is_dylib || !file->is_alive) {
|
||||
if (is_weak)
|
||||
return (4 << 24) + file->priority;
|
||||
return (3 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (is_weak)
|
||||
return (2 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 get_rank(Symbol<E> &sym) {
|
||||
if (!sym.file)
|
||||
return 7 << 24;
|
||||
return get_rank(sym.file, sym.is_common, sym.is_weak);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
auto is_private_extern = [&](MachSym &msym) {
|
||||
return this->is_hidden || msym.is_private_extern ||
|
||||
((msym.desc & N_WEAK_REF) && (msym.desc & N_WEAK_DEF));
|
||||
};
|
||||
|
||||
auto merge_scope = [&](Symbol<E> &sym, MachSym &msym) {
|
||||
// If at least one symbol defines it as an EXTERN symbol,
|
||||
// the result is an EXTERN symbol instead of PRIVATE_EXTERN,
|
||||
// so that the symbol is exported.
|
||||
if (sym.scope == SCOPE_EXTERN)
|
||||
return SCOPE_EXTERN;
|
||||
return is_private_extern(msym) ? SCOPE_PRIVATE_EXTERN : SCOPE_EXTERN;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern || msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
bool is_weak = (msym.desc & N_WEAK_DEF);
|
||||
|
||||
sym.scope = merge_scope(sym, msym);
|
||||
|
||||
if (get_rank(this, msym.is_common(), is_weak) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.is_imported = false;
|
||||
sym.is_weak = is_weak;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
assert(msym.is_common());
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = true;
|
||||
break;
|
||||
case N_ABS:
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
case N_SECT:
|
||||
sym.subsec = sym_to_subsec[i];
|
||||
sym.value = msym.value - sym.subsec->input_addr;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
if (isec->hdr.match("__DATA", "__objc_catlist") ||
|
||||
(isec->hdr.get_segname() == "__TEXT" &&
|
||||
isec->hdr.get_sectname().starts_with("__swift")))
|
||||
return true;
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++)
|
||||
if (!mach_syms[i].is_undef() && mach_syms[i].is_extern &&
|
||||
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void
|
||||
ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(ObjectFile<E> *)> feeder) {
|
||||
assert(this->is_alive);
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
if (msym.is_undef() || (msym.is_common() && !sym.is_common))
|
||||
if (InputFile<E> *file = sym.file)
|
||||
if (fast_mark(file->is_alive) && !file->is_dylib)
|
||||
feeder((ObjectFile<E> *)file);
|
||||
}
|
||||
|
||||
for (Subsection<E> *subsec : subsections)
|
||||
for (UnwindRecord<E> &rec : subsec->get_unwind_records())
|
||||
if (Symbol<E> *sym = rec.personality)
|
||||
if (InputFile<E> *file = sym->file)
|
||||
if (fast_mark(file->is_alive) && !file->is_dylib)
|
||||
feeder((ObjectFile<E> *)file);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
|
||||
if (sym.file == this && sym.is_common) {
|
||||
InputSection<E> *isec = get_common_sec(ctx);
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_size = (u32)msym.value,
|
||||
.p2align = (u8)msym.common_p2align,
|
||||
};
|
||||
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.is_imported = false;
|
||||
sym.is_weak = false;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> *sym = this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (sym && sym->file && sym->file != this && !msym.is_undef() &&
|
||||
!msym.is_common() && !(msym.desc & N_WEAK_DEF))
|
||||
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
|
||||
<< ": " << *sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
if (!common_sec) {
|
||||
MachSection *hdr = new MachSection;
|
||||
common_hdr.reset(hdr);
|
||||
|
||||
memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->set_segname("__DATA");
|
||||
hdr->set_sectname("__common");
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr, sections.size());
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_lto_symbols(Context<E> &ctx) {
|
||||
i64 nsyms = ctx.lto.module_get_num_symbols(this->lto_module);
|
||||
this->syms.reserve(nsyms);
|
||||
this->mach_syms2.reserve(nsyms);
|
||||
|
||||
for (i64 i = 0; i < nsyms; i++) {
|
||||
std::string_view name = ctx.lto.module_get_symbol_name(this->lto_module, i);
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
|
||||
u32 attr = ctx.lto.module_get_symbol_attribute(this->lto_module, i);
|
||||
MachSym msym = {};
|
||||
|
||||
switch (attr & LTO_SYMBOL_DEFINITION_MASK) {
|
||||
case LTO_SYMBOL_DEFINITION_REGULAR:
|
||||
case LTO_SYMBOL_DEFINITION_TENTATIVE:
|
||||
case LTO_SYMBOL_DEFINITION_WEAK:
|
||||
msym.type = N_ABS;
|
||||
break;
|
||||
case LTO_SYMBOL_DEFINITION_UNDEFINED:
|
||||
case LTO_SYMBOL_DEFINITION_WEAKUNDEF:
|
||||
msym.type = N_UNDF;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
switch (attr & LTO_SYMBOL_SCOPE_MASK) {
|
||||
case 0:
|
||||
case LTO_SYMBOL_SCOPE_INTERNAL:
|
||||
case LTO_SYMBOL_SCOPE_HIDDEN:
|
||||
break;
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT:
|
||||
case LTO_SYMBOL_SCOPE_PROTECTED:
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN:
|
||||
msym.is_extern = true;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
mach_syms2.push_back(msym);
|
||||
}
|
||||
|
||||
mach_syms = mach_syms2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string_view ObjectFile<E>::get_linker_optimization_hints(Context<E> &ctx) {
|
||||
LinkEditDataCommand *cmd =
|
||||
(LinkEditDataCommand *)find_load_command(ctx, LC_LINKER_OPTIMIZATION_HINT);
|
||||
|
||||
if (cmd)
|
||||
return {(char *)this->mf->data + cmd->dataoff, cmd->datasize};
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E>::DylibFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
: InputFile<E>(mf) {
|
||||
this->is_dylib = true;
|
||||
this->is_alive = (ctx.needed_l || !ctx.arg.dead_strip_dylibs);
|
||||
this->is_weak = ctx.weak_l;
|
||||
this->is_reexported = ctx.reexport_l;
|
||||
ctx.dylib_pool.emplace_back(this);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *file = new DylibFile<E>(ctx, mf);
|
||||
file->parse(ctx);
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *
|
||||
find_external_lib(Context<E> &ctx, std::string_view parent, std::string path) {
|
||||
if (!path.starts_with('/'))
|
||||
return MappedFile<Context<E>>::open(ctx, path);
|
||||
|
||||
for (const std::string &root : ctx.arg.syslibroot) {
|
||||
if (path.ends_with(".tbd")) {
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.ends_with(".dylib")) {
|
||||
std::string stem(path.substr(0, path.size() - 6));
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + stem + ".tbd"))
|
||||
return file;
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
}
|
||||
|
||||
for (std::string extn : {".tbd", ".dylib"})
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path + extn))
|
||||
return file;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse(Context<E> &ctx) {
|
||||
switch (get_file_type(this->mf)) {
|
||||
case FileType::TAPI:
|
||||
parse_tapi(ctx);
|
||||
break;
|
||||
case FileType::MACH_DYLIB:
|
||||
parse_dylib(ctx);
|
||||
break;
|
||||
case FileType::MACH_EXE:
|
||||
parse_dylib(ctx);
|
||||
dylib_idx = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": is not a dylib";
|
||||
}
|
||||
|
||||
// Read reexported libraries if any
|
||||
for (std::string_view path : reexported_libs) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
find_external_lib(ctx, install_name, std::string(path));
|
||||
if (!mf)
|
||||
Fatal(ctx) << install_name << ": cannot open reexported library " << path;
|
||||
|
||||
DylibFile<E> *child = DylibFile<E>::create(ctx, mf);
|
||||
exports.merge(child->exports);
|
||||
weak_exports.merge(child->weak_exports);
|
||||
}
|
||||
|
||||
// Initialize syms and is_weak_symbols vectors
|
||||
for (std::string_view s : exports) {
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
is_weak_symbol.push_back(false);
|
||||
}
|
||||
|
||||
for (std::string_view s : weak_exports) {
|
||||
if (!exports.contains(s)) {
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
is_weak_symbol.push_back(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::read_trie(Context<E> &ctx, u8 *start, i64 offset,
|
||||
const std::string &prefix) {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (*buf) {
|
||||
read_uleb(buf); // size
|
||||
i64 flags = read_uleb(buf) & ~EXPORT_SYMBOL_FLAGS_KIND_MASK;
|
||||
read_uleb(buf); // addr
|
||||
|
||||
std::string_view name = save_string(ctx, prefix);
|
||||
if (flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION)
|
||||
weak_exports.insert(name);
|
||||
else
|
||||
exports.insert(name);
|
||||
|
||||
if (flags & EXPORT_SYMBOL_FLAGS_REEXPORT)
|
||||
read_uleb(buf); // skip a library ordinal
|
||||
} else {
|
||||
buf++;
|
||||
}
|
||||
|
||||
i64 nchild = *buf++;
|
||||
|
||||
for (i64 i = 0; i < nchild; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(ctx, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_tapi(Context<E> &ctx) {
|
||||
TextDylib tbd = parse_tbd(ctx, this->mf);
|
||||
|
||||
install_name = tbd.install_name;
|
||||
reexported_libs = std::move(tbd.reexported_libs);
|
||||
exports = std::move(tbd.exports);
|
||||
weak_exports = std::move(tbd.weak_exports);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
if (ctx.arg.application_extension && !(hdr.flags & MH_APP_EXTENSION_SAFE))
|
||||
Warn(ctx) << "linking against a dylib which is not safe for use in "
|
||||
<< "application extensions: " << *this;
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_ID_DYLIB: {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
install_name = (char *)p + cmd.nameoff;
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)p;
|
||||
if (cmd.export_off)
|
||||
read_trie(ctx, this->mf->data + cmd.export_off);
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_EXPORTS_TRIE: {
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)p;
|
||||
read_trie(ctx, this->mf->data + cmd.dataoff);
|
||||
break;
|
||||
}
|
||||
case LC_REEXPORT_DYLIB:
|
||||
if (!(hdr.flags & MH_NO_REEXPORTED_DYLIBS)) {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
reexported_libs.push_back((char *)p + cmd.nameoff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, false, false) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.scope = SCOPE_LOCAL;
|
||||
sym.is_imported = true;
|
||||
sym.is_weak = (this->is_weak || is_weak_symbol[i]);
|
||||
sym.no_dead_strip = false;
|
||||
sym.subsec = nullptr;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template class InputFile<E>;
|
||||
template class ObjectFile<E>;
|
||||
template class DylibFile<E>;
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &);
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
OutputSection<E> &get_output_section(Context<E> &ctx, const MachSection &hdr) {
|
||||
static std::unordered_set<std::string_view> set = {
|
||||
"__got", "__auth_got", "__auth_ptr", "__nl_symbol_ptr", "__const",
|
||||
"__cfstring", "__mod_init_func", "__mod_term_func", "__objc_classlist",
|
||||
"__objc_nlclslist", "__objc_catlist", "__objc_nlcatlist", "__objc_protolist",
|
||||
};
|
||||
|
||||
std::string_view seg = hdr.get_segname();
|
||||
std::string_view sect = hdr.get_sectname();
|
||||
|
||||
if (seg == "__DATA" && set.contains(sect)) {
|
||||
seg = "__DATA_CONST";
|
||||
} else if (seg == "__TEXT" && sect == "__StaticInit") {
|
||||
sect = "__text";
|
||||
}
|
||||
|
||||
return *OutputSection<E>::get_instance(ctx, seg, sect);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr, u32 secidx)
|
||||
: file(file), hdr(hdr), secidx(secidx), osec(get_output_section(ctx, hdr)) {
|
||||
if (hdr.type != S_ZEROFILL)
|
||||
contents = file.mf->get_contents().substr(hdr.offset, hdr.size);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
// Parse mach-o relocations to fill `rels` vector
|
||||
rels = read_relocations(ctx, file, hdr);
|
||||
|
||||
// Sort `rels` vector
|
||||
sort(rels, [](const Relocation<E> &a, const Relocation<E> &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
// Find subsections this relocation section refers to
|
||||
auto cmp = [](Subsection<E> *subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
};
|
||||
|
||||
auto begin = std::lower_bound(file.subsections.begin(),
|
||||
file.subsections.end(), hdr.addr, cmp);
|
||||
auto end = std::lower_bound(begin, file.subsections.end(),
|
||||
hdr.addr + hdr.size, cmp);
|
||||
|
||||
// Assign each subsection a group of relocations
|
||||
i64 i = 0;
|
||||
for (auto it = begin; it < end; it++) {
|
||||
Subsection<E> &subsec = **it;
|
||||
subsec.rel_offset = i;
|
||||
while (i < rels.size() &&
|
||||
rels[i].offset < subsec.input_offset + subsec.input_size) {
|
||||
rels[i].offset -= subsec.input_offset;
|
||||
i++;
|
||||
}
|
||||
subsec.nrels = i - subsec.rel_offset;
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template class InputSection<E>;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "lto.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
static void do_load_plugin(Context<E> &ctx) {
|
||||
void *handle = dlopen(ctx.arg.lto_library.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
ctx.lto.dlopen_handle = handle;
|
||||
|
||||
// I don't want to use a macro, but I couldn't find an easy way to
|
||||
// write the same thing without a macro.
|
||||
#define DLSYM(x) ctx.lto.x = (decltype(ctx.lto.x))dlsym(handle, "lto_" #x)
|
||||
DLSYM(get_version);
|
||||
DLSYM(get_error_message);
|
||||
DLSYM(module_is_object_file);
|
||||
DLSYM(module_is_object_file_for_target);
|
||||
DLSYM(module_has_objc_category);
|
||||
DLSYM(module_is_object_file_in_memory);
|
||||
DLSYM(module_is_object_file_in_memory_for_target);
|
||||
DLSYM(module_create);
|
||||
DLSYM(module_create_from_memory);
|
||||
DLSYM(module_create_from_memory_with_path);
|
||||
DLSYM(module_create_in_local_context);
|
||||
DLSYM(module_create_in_codegen_context);
|
||||
DLSYM(module_create_from_fd);
|
||||
DLSYM(module_create_from_fd_at_offset);
|
||||
DLSYM(module_dispose);
|
||||
DLSYM(module_get_target_triple);
|
||||
DLSYM(module_set_target_triple);
|
||||
DLSYM(module_get_num_symbols);
|
||||
DLSYM(module_get_symbol_name);
|
||||
DLSYM(module_get_symbol_attribute);
|
||||
DLSYM(module_get_linkeropts);
|
||||
DLSYM(module_get_macho_cputype);
|
||||
DLSYM(module_has_ctor_dtor);
|
||||
DLSYM(codegen_set_diagnostic_handler);
|
||||
DLSYM(codegen_create);
|
||||
DLSYM(codegen_create_in_local_context);
|
||||
DLSYM(codegen_dispose);
|
||||
DLSYM(codegen_add_module);
|
||||
DLSYM(codegen_set_module);
|
||||
DLSYM(codegen_set_debug_model);
|
||||
DLSYM(codegen_set_pic_model);
|
||||
DLSYM(codegen_set_cpu);
|
||||
DLSYM(codegen_set_assembler_path);
|
||||
DLSYM(codegen_set_assembler_args);
|
||||
DLSYM(codegen_add_must_preserve_symbol);
|
||||
DLSYM(codegen_write_merged_modules);
|
||||
DLSYM(codegen_compile);
|
||||
DLSYM(codegen_compile_to_file);
|
||||
DLSYM(codegen_optimize);
|
||||
DLSYM(codegen_compile_optimized);
|
||||
DLSYM(api_version);
|
||||
DLSYM(set_debug_options);
|
||||
DLSYM(codegen_debug_options);
|
||||
DLSYM(codegen_debug_options_array);
|
||||
DLSYM(initialize_disassembler);
|
||||
DLSYM(codegen_set_should_internalize);
|
||||
DLSYM(codegen_set_should_embed_uselists);
|
||||
#undef DLSYM
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx) {
|
||||
std::call_once(ctx.lto_plugin_loaded, [&] { do_load_plugin(ctx); });
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx) {
|
||||
LTOCodeGen *cg = ctx.lto.codegen_create();
|
||||
|
||||
// Add bitcode files to CodeGen.
|
||||
for (const std::string &opt : ctx.arg.mllvm)
|
||||
ctx.lto.codegen_debug_options(cg, opt.c_str());
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
ctx.lto.codegen_add_module(cg, file->lto_module);
|
||||
|
||||
// Mark symbols that have to be preserved. All symbols that are not
|
||||
// marked here may be internalized and deleted as an extenrally-
|
||||
// visible symbol.
|
||||
if (ctx.output_type == MH_DYLIB || ctx.arg.export_dynamic) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (!file->lto_module) {
|
||||
for (i64 i = 0; i < file->mach_syms.size(); i++) {
|
||||
MachSym &msym = file->mach_syms[i];
|
||||
Symbol<E> &sym = *file->syms[i];
|
||||
if (msym.is_undef() && !sym.file->is_dylib &&
|
||||
((ObjectFile<E> *)sym.file)->lto_module)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym.name.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && sym->scope != SCOPE_LOCAL)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym->name.data());
|
||||
}
|
||||
|
||||
if (ctx.arg.entry->file)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, ctx.arg.entry->name.data());
|
||||
|
||||
// Run the compiler backend to do LTO.
|
||||
size_t size;
|
||||
u8 *data = (u8 *)ctx.lto.codegen_compile(cg, &size);
|
||||
if (!data)
|
||||
Fatal(ctx) << "lto_codegen_compile failed: " << ctx.lto.get_error_message();
|
||||
|
||||
if (!ctx.arg.object_path_lto.empty()) {
|
||||
FILE *out = fopen(ctx.arg.object_path_lto.c_str(), "w");
|
||||
if (!out)
|
||||
Fatal(ctx) << "-object_path_lto: cannot open " << ctx.arg.object_path_lto
|
||||
<< ": " << errno_string();
|
||||
fwrite(data, size, 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// Remove bitcode object files from ctx.objs.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->lto_module) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a result of LTO as a new object file.
|
||||
MappedFile<Context<E>> *mf = new MappedFile<Context<E>>;
|
||||
mf->name = "<LTO>";
|
||||
mf->data = data;
|
||||
mf->size = size;
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
|
||||
ObjectFile<E> *obj = ObjectFile<E>::create(ctx, mf, "");
|
||||
obj->parse(ctx);
|
||||
obj->is_alive = true;
|
||||
obj->priority = 100; // regular file starts with priority 10,000
|
||||
obj->resolve_symbols(ctx);
|
||||
ctx.objs.push_back(obj);
|
||||
}
|
||||
|
||||
#ifdef MOLD_ARM64
|
||||
LTOPlugin::~LTOPlugin() {
|
||||
if (dlopen_handle)
|
||||
dlclose(dlopen_handle);
|
||||
}
|
||||
#endif
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void load_lto_plugin(Context<E> &);
|
||||
template void do_lto(Context<E> &);
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "lto.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx) {}
|
||||
|
||||
#ifdef MOLD_ARM64
|
||||
LTOPlugin::~LTOPlugin() {}
|
||||
#endif
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void load_lto_plugin(Context<E> &);
|
||||
template void do_lto(Context<E> &);
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifdef _WIN32
|
||||
# include "lto-win32.cc"
|
||||
#else
|
||||
# include "lto-unix.cc"
|
||||
#endif
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
enum LTOSymbolAttributes {
|
||||
LTO_SYMBOL_ALIGNMENT_MASK = 0x0000001F,
|
||||
LTO_SYMBOL_PERMISSIONS_MASK = 0x000000E0,
|
||||
LTO_SYMBOL_PERMISSIONS_CODE = 0x000000A0,
|
||||
LTO_SYMBOL_PERMISSIONS_DATA = 0x000000C0,
|
||||
LTO_SYMBOL_PERMISSIONS_RODATA = 0x00000080,
|
||||
LTO_SYMBOL_DEFINITION_MASK = 0x00000700,
|
||||
LTO_SYMBOL_DEFINITION_REGULAR = 0x00000100,
|
||||
LTO_SYMBOL_DEFINITION_TENTATIVE = 0x00000200,
|
||||
LTO_SYMBOL_DEFINITION_WEAK = 0x00000300,
|
||||
LTO_SYMBOL_DEFINITION_UNDEFINED = 0x00000400,
|
||||
LTO_SYMBOL_DEFINITION_WEAKUNDEF = 0x00000500,
|
||||
LTO_SYMBOL_SCOPE_MASK = 0x00003800,
|
||||
LTO_SYMBOL_SCOPE_INTERNAL = 0x00000800,
|
||||
LTO_SYMBOL_SCOPE_HIDDEN = 0x00001000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT = 0x00001800,
|
||||
LTO_SYMBOL_SCOPE_PROTECTED = 0x00002000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN = 0x00002800,
|
||||
LTO_SYMBOL_COMDAT = 0x00004000,
|
||||
LTO_SYMBOL_ALIAS = 0x00008000,
|
||||
};
|
||||
|
||||
enum LTODebugModel {
|
||||
LTO_DEBUG_MODEL_NONE = 0,
|
||||
LTO_DEBUG_MODEL_DWARF = 1,
|
||||
};
|
||||
|
||||
enum LTOCodegenModel {
|
||||
LTO_CODEGEN_PIC_MODEL_STATIC = 0,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC = 1,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC_NO_PIC = 2,
|
||||
LTO_CODEGEN_PIC_MODEL_DEFAULT = 3,
|
||||
};
|
||||
|
||||
enum LTOCodegenDiagnosticSeverity {
|
||||
LTO_DS_ERROR = 0,
|
||||
LTO_DS_WARNING = 1,
|
||||
LTO_DS_REMARK = 3,
|
||||
LTO_DS_NOTE = 2,
|
||||
};
|
||||
|
||||
struct LTOObjectBuffer {
|
||||
const char *buffer;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
using LTOModule = void;
|
||||
using LTOCodeGen = void;
|
||||
|
||||
typedef void LTODiagnosticHandler(LTOCodegenDiagnosticSeverity severity,
|
||||
const char *diag, void *ctxt);
|
||||
|
||||
struct LTOPlugin {
|
||||
void *dlopen_handle = nullptr;
|
||||
~LTOPlugin();
|
||||
|
||||
char (*get_version)();
|
||||
|
||||
char (*get_error_message)();
|
||||
|
||||
bool (*module_is_object_file)(const char *path);
|
||||
|
||||
bool (*module_is_object_file_for_target)(const char *path,
|
||||
const char *target_triple_prefix);
|
||||
|
||||
bool (*module_has_objc_category)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory_for_target)(
|
||||
const void *mem, size_t length, const char *target_triple_prefix);
|
||||
|
||||
LTOModule *(*module_create)(const char *path);
|
||||
|
||||
LTOModule *(*module_create_from_memory)(const void *mem, size_t length);
|
||||
|
||||
LTOModule *(*module_create_from_memory_with_path)(const void *mem,
|
||||
size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_local_context)(const void *mem, size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_codegen_context)(const void *mem, size_t length,
|
||||
const char *path,
|
||||
LTOCodeGen *cg);
|
||||
|
||||
LTOModule *(*module_create_from_fd)(int fd, const char *path,
|
||||
size_t file_size);
|
||||
|
||||
LTOModule *(*module_create_from_fd_at_offset)(int fd, const char *path,
|
||||
size_t file_size,
|
||||
size_t map_size, uint64_t offset);
|
||||
|
||||
void (*module_dispose)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_target_triple)(LTOModule *mod);
|
||||
|
||||
void (*module_set_target_triple)(LTOModule *mod, const char *triple);
|
||||
|
||||
unsigned (*module_get_num_symbols)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_symbol_name)(LTOModule *mod, unsigned index);
|
||||
|
||||
uint32_t (*module_get_symbol_attribute)(LTOModule *mod, unsigned index);
|
||||
|
||||
const char *(*module_get_linkeropts)(LTOModule *mod);
|
||||
|
||||
bool (*module_get_macho_cputype)(LTOModule *mod, unsigned *out_cputype,
|
||||
unsigned *out_cpusubtype);
|
||||
|
||||
bool (*module_has_ctor_dtor)(LTOModule *mod);
|
||||
|
||||
void (*codegen_set_diagnostic_handler)(LTOCodeGen *cg, LTODiagnosticHandler,
|
||||
void *);
|
||||
|
||||
LTOCodeGen *(*codegen_create)();
|
||||
|
||||
LTOCodeGen *(*codegen_create_in_local_context)();
|
||||
|
||||
void (*codegen_dispose)(LTOCodeGen *cg);
|
||||
|
||||
bool (*codegen_add_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
void (*codegen_set_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
bool (*codegen_set_debug_model)(LTOCodeGen *cg, LTODebugModel);
|
||||
|
||||
bool (*codegen_set_pic_model)(LTOCodeGen *cg, LTOCodegenModel);
|
||||
|
||||
void (*codegen_set_cpu)(LTOCodeGen *cg, const char *cpu);
|
||||
|
||||
void (*codegen_set_assembler_path)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
void (*codegen_set_assembler_args)(LTOCodeGen *cg, const char **args,
|
||||
int nargs);
|
||||
|
||||
void (*codegen_add_must_preserve_symbol)(LTOCodeGen *cg, const char *symbol);
|
||||
|
||||
bool (*codegen_write_merged_modules)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
const void *(*codegen_compile)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
bool (*codegen_compile_to_file)(LTOCodeGen *cg, const char **name);
|
||||
|
||||
bool (*codegen_optimize)(LTOCodeGen *cg);
|
||||
|
||||
const void *(*codegen_compile_optimized)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
unsigned (*api_version)();
|
||||
|
||||
void (*set_debug_options)(const char *const *options, int number);
|
||||
|
||||
void (*codegen_debug_options)(LTOCodeGen *cg, const char *);
|
||||
|
||||
void (*codegen_debug_options_array)(LTOCodeGen *cg, const char *const *,
|
||||
int number);
|
||||
|
||||
void (*initialize_disassembler)();
|
||||
|
||||
void (*codegen_set_should_internalize)(LTOCodeGen *cg,
|
||||
bool should_internalize);
|
||||
|
||||
void (*codegen_set_should_embed_uselists)(LTOCodeGen *cg,
|
||||
bool should_embed_use_lists);
|
||||
};
|
||||
|
||||
} // namespace mold::macho
|
||||
+771
@@ -0,0 +1,771 @@
|
||||
#pragma once
|
||||
|
||||
#include "../inttypes.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct ARM64;
|
||||
struct X86_64;
|
||||
|
||||
template <typename E>
|
||||
std::string rel_to_string(u8 r_type);
|
||||
|
||||
static constexpr u32 FAT_MAGIC = 0xcafebabe;
|
||||
|
||||
static constexpr u32 MH_OBJECT = 0x1;
|
||||
static constexpr u32 MH_EXECUTE = 0x2;
|
||||
static constexpr u32 MH_FVMLIB = 0x3;
|
||||
static constexpr u32 MH_CORE = 0x4;
|
||||
static constexpr u32 MH_PRELOAD = 0x5;
|
||||
static constexpr u32 MH_DYLIB = 0x6;
|
||||
static constexpr u32 MH_DYLINKER = 0x7;
|
||||
static constexpr u32 MH_BUNDLE = 0x8;
|
||||
static constexpr u32 MH_DYLIB_STUB = 0x9;
|
||||
static constexpr u32 MH_DSYM = 0xa;
|
||||
static constexpr u32 MH_KEXT_BUNDLE = 0xb;
|
||||
|
||||
static constexpr u32 MH_NOUNDEFS = 0x1;
|
||||
static constexpr u32 MH_INCRLINK = 0x2;
|
||||
static constexpr u32 MH_DYLDLINK = 0x4;
|
||||
static constexpr u32 MH_BINDATLOAD = 0x8;
|
||||
static constexpr u32 MH_PREBOUND = 0x10;
|
||||
static constexpr u32 MH_SPLIT_SEGS = 0x20;
|
||||
static constexpr u32 MH_LAZY_INIT = 0x40;
|
||||
static constexpr u32 MH_TWOLEVEL = 0x80;
|
||||
static constexpr u32 MH_FORCE_FLAT = 0x100;
|
||||
static constexpr u32 MH_NOMULTIDEFS = 0x200;
|
||||
static constexpr u32 MH_NOFIXPREBINDING = 0x400;
|
||||
static constexpr u32 MH_PREBINDABLE = 0x800;
|
||||
static constexpr u32 MH_ALLMODSBOUND = 0x1000;
|
||||
static constexpr u32 MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000;
|
||||
static constexpr u32 MH_CANONICAL = 0x4000;
|
||||
static constexpr u32 MH_WEAK_DEFINES = 0x8000;
|
||||
static constexpr u32 MH_BINDS_TO_WEAK = 0x10000;
|
||||
static constexpr u32 MH_ALLOW_STACK_EXECUTION = 0x20000;
|
||||
static constexpr u32 MH_ROOT_SAFE = 0x40000;
|
||||
static constexpr u32 MH_SETUID_SAFE = 0x80000;
|
||||
static constexpr u32 MH_NO_REEXPORTED_DYLIBS = 0x100000;
|
||||
static constexpr u32 MH_PIE = 0x200000;
|
||||
static constexpr u32 MH_DEAD_STRIPPABLE_DYLIB = 0x400000;
|
||||
static constexpr u32 MH_HAS_TLV_DESCRIPTORS = 0x800000;
|
||||
static constexpr u32 MH_NO_HEAP_EXECUTION = 0x1000000;
|
||||
static constexpr u32 MH_APP_EXTENSION_SAFE = 0x02000000;
|
||||
static constexpr u32 MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000;
|
||||
static constexpr u32 MH_SIM_SUPPORT = 0x08000000;
|
||||
|
||||
static constexpr u32 VM_PROT_READ = 0x1;
|
||||
static constexpr u32 VM_PROT_WRITE = 0x2;
|
||||
static constexpr u32 VM_PROT_EXECUTE = 0x4;
|
||||
static constexpr u32 VM_PROT_NO_CHANGE = 0x8;
|
||||
static constexpr u32 VM_PROT_COPY = 0x10;
|
||||
static constexpr u32 VM_PROT_WANTS_COPY = 0x10;
|
||||
|
||||
static constexpr u32 LC_REQ_DYLD = 0x80000000;
|
||||
|
||||
static constexpr u32 LC_SEGMENT = 0x1;
|
||||
static constexpr u32 LC_SYMTAB = 0x2;
|
||||
static constexpr u32 LC_SYMSEG = 0x3;
|
||||
static constexpr u32 LC_THREAD = 0x4;
|
||||
static constexpr u32 LC_UNIXTHREAD = 0x5;
|
||||
static constexpr u32 LC_LOADFVMLIB = 0x6;
|
||||
static constexpr u32 LC_IDFVMLIB = 0x7;
|
||||
static constexpr u32 LC_IDENT = 0x8;
|
||||
static constexpr u32 LC_FVMFILE = 0x9;
|
||||
static constexpr u32 LC_PREPAGE = 0xa;
|
||||
static constexpr u32 LC_DYSYMTAB = 0xb;
|
||||
static constexpr u32 LC_LOAD_DYLIB = 0xc;
|
||||
static constexpr u32 LC_ID_DYLIB = 0xd;
|
||||
static constexpr u32 LC_LOAD_DYLINKER = 0xe;
|
||||
static constexpr u32 LC_ID_DYLINKER = 0xf;
|
||||
static constexpr u32 LC_PREBOUND_DYLIB = 0x10;
|
||||
static constexpr u32 LC_ROUTINES = 0x11;
|
||||
static constexpr u32 LC_SUB_FRAMEWORK = 0x12;
|
||||
static constexpr u32 LC_SUB_UMBRELLA = 0x13;
|
||||
static constexpr u32 LC_SUB_CLIENT = 0x14;
|
||||
static constexpr u32 LC_SUB_LIBRARY = 0x15;
|
||||
static constexpr u32 LC_TWOLEVEL_HINTS = 0x16;
|
||||
static constexpr u32 LC_PREBIND_CKSUM = 0x17;
|
||||
static constexpr u32 LC_LOAD_WEAK_DYLIB = (0x18 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_SEGMENT_64 = 0x19;
|
||||
static constexpr u32 LC_ROUTINES_64 = 0x1a;
|
||||
static constexpr u32 LC_UUID = 0x1b;
|
||||
static constexpr u32 LC_RPATH = (0x1c | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_CODE_SIGNATURE = 0x1d;
|
||||
static constexpr u32 LC_SEGMENT_SPLIT_INFO = 0x1e;
|
||||
static constexpr u32 LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_LAZY_LOAD_DYLIB = 0x20;
|
||||
static constexpr u32 LC_ENCRYPTION_INFO = 0x21;
|
||||
static constexpr u32 LC_DYLD_INFO = 0x22;
|
||||
static constexpr u32 LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_VERSION_MIN_MACOSX = 0x24;
|
||||
static constexpr u32 LC_VERSION_MIN_IPHONEOS = 0x25;
|
||||
static constexpr u32 LC_FUNCTION_STARTS = 0x26;
|
||||
static constexpr u32 LC_DYLD_ENVIRONMENT = 0x27;
|
||||
static constexpr u32 LC_MAIN = (0x28 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_DATA_IN_CODE = 0x29;
|
||||
static constexpr u32 LC_SOURCE_VERSION = 0x2A;
|
||||
static constexpr u32 LC_DYLIB_CODE_SIGN_DRS = 0x2B;
|
||||
static constexpr u32 LC_ENCRYPTION_INFO_64 = 0x2C;
|
||||
static constexpr u32 LC_LINKER_OPTION = 0x2D;
|
||||
static constexpr u32 LC_LINKER_OPTIMIZATION_HINT = 0x2E;
|
||||
static constexpr u32 LC_VERSION_MIN_TVOS = 0x2F;
|
||||
static constexpr u32 LC_VERSION_MIN_WATCHOS = 0x30;
|
||||
static constexpr u32 LC_NOTE = 0x31;
|
||||
static constexpr u32 LC_BUILD_VERSION = 0x32;
|
||||
static constexpr u32 LC_DYLD_EXPORTS_TRIE = (0x33 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_DYLD_CHAINED_FIXUPS = (0x34 | LC_REQ_DYLD);
|
||||
|
||||
static constexpr u32 SG_HIGHVM = 0x1;
|
||||
static constexpr u32 SG_FVMLIB = 0x2;
|
||||
static constexpr u32 SG_NORELOC = 0x4;
|
||||
static constexpr u32 SG_PROTECTED_VERSION_1 = 0x8;
|
||||
static constexpr u32 SG_READ_ONLY = 0x10;
|
||||
|
||||
static constexpr u32 S_REGULAR = 0x0;
|
||||
static constexpr u32 S_ZEROFILL = 0x1;
|
||||
static constexpr u32 S_CSTRING_LITERALS = 0x2;
|
||||
static constexpr u32 S_4BYTE_LITERALS = 0x3;
|
||||
static constexpr u32 S_8BYTE_LITERALS = 0x4;
|
||||
static constexpr u32 S_LITERAL_POINTERS = 0x5;
|
||||
static constexpr u32 S_NON_LAZY_SYMBOL_POINTERS = 0x6;
|
||||
static constexpr u32 S_LAZY_SYMBOL_POINTERS = 0x7;
|
||||
static constexpr u32 S_SYMBOL_STUBS = 0x8;
|
||||
static constexpr u32 S_MOD_INIT_FUNC_POINTERS = 0x9;
|
||||
static constexpr u32 S_MOD_TERM_FUNC_POINTERS = 0xa;
|
||||
static constexpr u32 S_COALESCED = 0xb;
|
||||
static constexpr u32 S_GB_ZEROFILL = 0xc;
|
||||
static constexpr u32 S_INTERPOSING = 0xd;
|
||||
static constexpr u32 S_16BYTE_LITERALS = 0xe;
|
||||
static constexpr u32 S_DTRACE_DOF = 0xf;
|
||||
static constexpr u32 S_LAZY_DYLIB_SYMBOL_POINTERS = 0x10;
|
||||
static constexpr u32 S_THREAD_LOCAL_REGULAR = 0x11;
|
||||
static constexpr u32 S_THREAD_LOCAL_ZEROFILL = 0x12;
|
||||
static constexpr u32 S_THREAD_LOCAL_VARIABLES = 0x13;
|
||||
static constexpr u32 S_THREAD_LOCAL_VARIABLE_POINTERS = 0x14;
|
||||
static constexpr u32 S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15;
|
||||
static constexpr u32 S_INIT_FUNC_OFFSETS = 0x16;
|
||||
|
||||
static constexpr u32 S_ATTR_LOC_RELOC = 0x1;
|
||||
static constexpr u32 S_ATTR_EXT_RELOC = 0x2;
|
||||
static constexpr u32 S_ATTR_SOME_INSTRUCTIONS = 0x4;
|
||||
|
||||
static constexpr u32 S_ATTR_DEBUG = 0x20000;
|
||||
static constexpr u32 S_ATTR_SELF_MODIFYING_CODE = 0x40000;
|
||||
static constexpr u32 S_ATTR_LIVE_SUPPORT = 0x80000;
|
||||
static constexpr u32 S_ATTR_NO_DEAD_STRIP = 0x100000;
|
||||
static constexpr u32 S_ATTR_STRIP_STATIC_SYMS = 0x200000;
|
||||
static constexpr u32 S_ATTR_NO_TOC = 0x400000;
|
||||
static constexpr u32 S_ATTR_PURE_INSTRUCTIONS = 0x800000;
|
||||
|
||||
static constexpr u32 CPU_TYPE_X86_64 = 0x1000007;
|
||||
static constexpr u32 CPU_TYPE_ARM64 = 0x100000c;
|
||||
|
||||
static constexpr u32 CPU_SUBTYPE_X86_64_ALL = 3;
|
||||
static constexpr u32 CPU_SUBTYPE_ARM64_ALL = 0;
|
||||
|
||||
static constexpr u32 REBASE_TYPE_POINTER = 1;
|
||||
static constexpr u32 REBASE_TYPE_TEXT_ABSOLUTE32 = 2;
|
||||
static constexpr u32 REBASE_TYPE_TEXT_PCREL32 = 3;
|
||||
|
||||
static constexpr u32 REBASE_OPCODE_MASK = 0xf0;
|
||||
static constexpr u32 REBASE_IMMEDIATE_MASK = 0x0f;
|
||||
static constexpr u32 REBASE_OPCODE_DONE = 0x00;
|
||||
static constexpr u32 REBASE_OPCODE_SET_TYPE_IMM = 0x10;
|
||||
static constexpr u32 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x20;
|
||||
static constexpr u32 REBASE_OPCODE_ADD_ADDR_ULEB = 0x30;
|
||||
static constexpr u32 REBASE_OPCODE_ADD_ADDR_IMM_SCALED = 0x40;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES = 0x60;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = 0x70;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB = 0x80;
|
||||
|
||||
static constexpr u32 BIND_TYPE_POINTER = 1;
|
||||
static constexpr u32 BIND_TYPE_TEXT_ABSOLUTE32 = 2;
|
||||
static constexpr u32 BIND_TYPE_TEXT_PCREL32 = 3;
|
||||
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_SELF = 0;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3;
|
||||
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_WEAK_IMPORT = 0x1;
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION = 0x8;
|
||||
|
||||
static constexpr u32 BIND_OPCODE_MASK = 0xF0;
|
||||
static constexpr u32 BIND_IMMEDIATE_MASK = 0x0F;
|
||||
static constexpr u32 BIND_OPCODE_DONE = 0x00;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = 0x10;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = 0x20;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = 0x30;
|
||||
static constexpr u32 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = 0x40;
|
||||
static constexpr u32 BIND_OPCODE_SET_TYPE_IMM = 0x50;
|
||||
static constexpr u32 BIND_OPCODE_SET_ADDEND_SLEB = 0x60;
|
||||
static constexpr u32 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x70;
|
||||
static constexpr u32 BIND_OPCODE_ADD_ADDR_ULEB = 0x80;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND = 0x90;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = 0xA0;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = 0xB0;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB = 0xC0;
|
||||
static constexpr u32 BIND_OPCODE_THREADED = 0xD0;
|
||||
static constexpr u32 BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB = 0x00;
|
||||
static constexpr u32 BIND_SUBOPCODE_THREADED_APPLY = 0x01;
|
||||
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_MASK = 0x03;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_REGULAR = 0x00;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL = 0x01;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE = 0x02;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION = 0x04;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_REEXPORT = 0x08;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER = 0x10;
|
||||
|
||||
static constexpr u32 DICE_KIND_DATA = 1;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE8 = 2;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE16 = 3;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE32 = 4;
|
||||
static constexpr u32 DICE_KIND_ABS_JUMP_TABLE32 = 5;
|
||||
|
||||
static constexpr u32 N_UNDF = 0;
|
||||
static constexpr u32 N_ABS = 1;
|
||||
static constexpr u32 N_INDR = 5;
|
||||
static constexpr u32 N_PBUD = 6;
|
||||
static constexpr u32 N_SECT = 7;
|
||||
|
||||
static constexpr u32 N_GSYM = 0x20;
|
||||
static constexpr u32 N_FNAME = 0x22;
|
||||
static constexpr u32 N_FUN = 0x24;
|
||||
static constexpr u32 N_STSYM = 0x26;
|
||||
static constexpr u32 N_LCSYM = 0x28;
|
||||
static constexpr u32 N_BNSYM = 0x2e;
|
||||
static constexpr u32 N_AST = 0x32;
|
||||
static constexpr u32 N_OPT = 0x3c;
|
||||
static constexpr u32 N_RSYM = 0x40;
|
||||
static constexpr u32 N_SLINE = 0x44;
|
||||
static constexpr u32 N_ENSYM = 0x4e;
|
||||
static constexpr u32 N_SSYM = 0x60;
|
||||
static constexpr u32 N_SO = 0x64;
|
||||
static constexpr u32 N_OSO = 0x66;
|
||||
static constexpr u32 N_LSYM = 0x80;
|
||||
static constexpr u32 N_BINCL = 0x82;
|
||||
static constexpr u32 N_SOL = 0x84;
|
||||
static constexpr u32 N_PARAMS = 0x86;
|
||||
static constexpr u32 N_VERSION = 0x88;
|
||||
static constexpr u32 N_OLEVEL = 0x8A;
|
||||
static constexpr u32 N_PSYM = 0xa0;
|
||||
static constexpr u32 N_EINCL = 0xa2;
|
||||
static constexpr u32 N_ENTRY = 0xa4;
|
||||
static constexpr u32 N_LBRAC = 0xc0;
|
||||
static constexpr u32 N_EXCL = 0xc2;
|
||||
static constexpr u32 N_RBRAC = 0xe0;
|
||||
static constexpr u32 N_BCOMM = 0xe2;
|
||||
static constexpr u32 N_ECOMM = 0xe4;
|
||||
static constexpr u32 N_ECOML = 0xe8;
|
||||
static constexpr u32 N_LENG = 0xfe;
|
||||
static constexpr u32 N_PC = 0x30;
|
||||
|
||||
static constexpr u32 REFERENCE_TYPE = 0xf;
|
||||
static constexpr u32 REFERENCE_FLAG_UNDEFINED_NON_LAZY = 0;
|
||||
static constexpr u32 REFERENCE_FLAG_UNDEFINED_LAZY = 1;
|
||||
static constexpr u32 REFERENCE_FLAG_DEFINED = 2;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_DEFINED = 3;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY = 4;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5;
|
||||
|
||||
static constexpr u32 REFERENCED_DYNAMICALLY = 0x0010;
|
||||
|
||||
static constexpr u32 SELF_LIBRARY_ORDINAL = 0x0;
|
||||
static constexpr u32 MAX_LIBRARY_ORDINAL = 0xfd;
|
||||
static constexpr u32 DYNAMIC_LOOKUP_ORDINAL = 0xfe;
|
||||
static constexpr u32 EXECUTABLE_ORDINAL = 0xff;
|
||||
|
||||
static constexpr u32 N_NO_DEAD_STRIP = 0x0020;
|
||||
static constexpr u32 N_DESC_DISCARDED = 0x0020;
|
||||
static constexpr u32 N_WEAK_REF = 0x0040;
|
||||
static constexpr u32 N_WEAK_DEF = 0x0080;
|
||||
static constexpr u32 N_REF_TO_WEAK = 0x0080;
|
||||
static constexpr u32 N_ARM_THUMB_DEF = 0x0008;
|
||||
static constexpr u32 N_SYMBOL_RESOLVER = 0x0100;
|
||||
static constexpr u32 N_ALT_ENTRY = 0x0200;
|
||||
|
||||
static constexpr u32 PLATFORM_MACOS = 1;
|
||||
static constexpr u32 PLATFORM_IOS = 2;
|
||||
static constexpr u32 PLATFORM_TVOS = 3;
|
||||
static constexpr u32 PLATFORM_WATCHOS = 4;
|
||||
static constexpr u32 PLATFORM_BRIDGEOS = 5;
|
||||
static constexpr u32 PLATFORM_MACCATALYST = 6;
|
||||
static constexpr u32 PLATFORM_IOSSIMULATOR = 7;
|
||||
static constexpr u32 PLATFORM_TVOSSIMULATOR = 8;
|
||||
static constexpr u32 PLATFORM_WATCHOSSIMULATOR = 9;
|
||||
static constexpr u32 PLATFORM_DRIVERKIT = 10;
|
||||
|
||||
static constexpr u32 TOOL_CLANG = 1;
|
||||
static constexpr u32 TOOL_SWIFT = 2;
|
||||
static constexpr u32 TOOL_LD = 3;
|
||||
static constexpr u32 TOOL_MOLD = 54321; // Randomly chosen!
|
||||
|
||||
static constexpr u32 OBJC_IMAGE_SUPPORTS_GC = 1 << 1;
|
||||
static constexpr u32 OBJC_IMAGE_REQUIRES_GC = 1 << 2;
|
||||
static constexpr u32 OBJC_IMAGE_OPTIMIZED_BY_DYLD = 1 << 3;
|
||||
static constexpr u32 OBJC_IMAGE_SUPPORTS_COMPACTION = 1 << 4;
|
||||
static constexpr u32 OBJC_IMAGE_IS_SIMULATED = 1 << 5;
|
||||
static constexpr u32 OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES = 1 << 6;
|
||||
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADRP = 1;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR = 2;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD_LDR = 3;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_LDR = 4;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD_STR = 5;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_STR = 6;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD = 7;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT = 8;
|
||||
|
||||
static constexpr u32 ARM64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 ARM64_RELOC_SUBTRACTOR = 1;
|
||||
static constexpr u32 ARM64_RELOC_BRANCH26 = 2;
|
||||
static constexpr u32 ARM64_RELOC_PAGE21 = 3;
|
||||
static constexpr u32 ARM64_RELOC_PAGEOFF12 = 4;
|
||||
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGE21 = 5;
|
||||
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGEOFF12 = 6;
|
||||
static constexpr u32 ARM64_RELOC_POINTER_TO_GOT = 7;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGE21 = 8;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGEOFF12 = 9;
|
||||
static constexpr u32 ARM64_RELOC_ADDEND = 10;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<ARM64>(u8 type) {
|
||||
switch (type) {
|
||||
case ARM64_RELOC_UNSIGNED: return "ARM64_RELOC_UNSIGNED";
|
||||
case ARM64_RELOC_SUBTRACTOR: return "ARM64_RELOC_SUBTRACTOR";
|
||||
case ARM64_RELOC_BRANCH26: return "ARM64_RELOC_BRANCH26";
|
||||
case ARM64_RELOC_PAGE21: return "ARM64_RELOC_PAGE21";
|
||||
case ARM64_RELOC_PAGEOFF12: return "ARM64_RELOC_PAGEOFF12";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21: return "ARM64_RELOC_GOT_LOAD_PAGE21";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12: return "ARM64_RELOC_GOT_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_POINTER_TO_GOT: return "ARM64_RELOC_POINTER_TO_GOT";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21: return "ARM64_RELOC_TLVP_LOAD_PAGE21";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: return "ARM64_RELOC_TLVP_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_ADDEND: return "ARM64_RELOC_ADDEND";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 X86_64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED = 1;
|
||||
static constexpr u32 X86_64_RELOC_BRANCH = 2;
|
||||
static constexpr u32 X86_64_RELOC_GOT_LOAD = 3;
|
||||
static constexpr u32 X86_64_RELOC_GOT = 4;
|
||||
static constexpr u32 X86_64_RELOC_SUBTRACTOR = 5;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_1 = 6;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_2 = 7;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_4 = 8;
|
||||
static constexpr u32 X86_64_RELOC_TLV = 9;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<X86_64>(u8 type) {
|
||||
switch (type) {
|
||||
case X86_64_RELOC_UNSIGNED: return "X86_64_RELOC_UNSIGNED";
|
||||
case X86_64_RELOC_SIGNED: return "X86_64_RELOC_SIGNED";
|
||||
case X86_64_RELOC_BRANCH: return "X86_64_RELOC_BRANCH";
|
||||
case X86_64_RELOC_GOT_LOAD: return "X86_64_RELOC_GOT_LOAD";
|
||||
case X86_64_RELOC_GOT: return "X86_64_RELOC_GOT";
|
||||
case X86_64_RELOC_SUBTRACTOR: return "X86_64_RELOC_SUBTRACTOR";
|
||||
case X86_64_RELOC_SIGNED_1: return "X86_64_RELOC_SIGNED_1";
|
||||
case X86_64_RELOC_SIGNED_2: return "X86_64_RELOC_SIGNED_2";
|
||||
case X86_64_RELOC_SIGNED_4: return "X86_64_RELOC_SIGNED_4";
|
||||
case X86_64_RELOC_TLV: return "X86_64_RELOC_TLV";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
struct FatHeader {
|
||||
ub32 magic;
|
||||
ub32 nfat_arch;
|
||||
};
|
||||
|
||||
struct FatArch {
|
||||
ub32 cputype;
|
||||
ub32 cpusubtype;
|
||||
ub32 offset;
|
||||
ub32 size;
|
||||
ub32 align;
|
||||
};
|
||||
|
||||
struct MachHeader {
|
||||
ul32 magic;
|
||||
ul32 cputype;
|
||||
ul32 cpusubtype;
|
||||
ul32 filetype;
|
||||
ul32 ncmds;
|
||||
ul32 sizeofcmds;
|
||||
ul32 flags;
|
||||
ul32 reserved;
|
||||
};
|
||||
|
||||
struct LoadCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
};
|
||||
|
||||
struct SegmentCommand {
|
||||
std::string_view get_segname() const {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
char segname[16];
|
||||
ul64 vmaddr;
|
||||
ul64 vmsize;
|
||||
ul64 fileoff;
|
||||
ul64 filesize;
|
||||
ul32 maxprot;
|
||||
ul32 initprot;
|
||||
ul32 nsects;
|
||||
ul32 flags;
|
||||
};
|
||||
|
||||
struct MachSection {
|
||||
void set_segname(std::string_view name) {
|
||||
assert(name.size() <= sizeof(segname));
|
||||
memcpy(segname, name.data(), name.size());
|
||||
}
|
||||
|
||||
std::string_view get_segname() const {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
void set_sectname(std::string_view name) {
|
||||
assert(name.size() <= sizeof(sectname));
|
||||
memcpy(sectname, name.data(), name.size());
|
||||
}
|
||||
|
||||
std::string_view get_sectname() const {
|
||||
return {sectname, strnlen(sectname, sizeof(sectname))};
|
||||
}
|
||||
|
||||
bool match(std::string_view segname, std::string_view sectname) const {
|
||||
return get_segname() == segname && get_sectname() == sectname;
|
||||
}
|
||||
|
||||
char sectname[16];
|
||||
char segname[16];
|
||||
ul64 addr;
|
||||
ul64 size;
|
||||
ul32 offset;
|
||||
ul32 p2align;
|
||||
ul32 reloff;
|
||||
ul32 nreloc;
|
||||
u8 type;
|
||||
ul24 attr;
|
||||
ul32 reserved1;
|
||||
ul32 reserved2;
|
||||
ul32 reserved3;
|
||||
};
|
||||
|
||||
struct DylibCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
ul32 timestamp;
|
||||
ul32 current_version;
|
||||
ul32 compatibility_version;
|
||||
};
|
||||
|
||||
struct DylinkerCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
};
|
||||
|
||||
struct SymtabCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 symoff;
|
||||
ul32 nsyms;
|
||||
ul32 stroff;
|
||||
ul32 strsize;
|
||||
};
|
||||
|
||||
struct DysymtabCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 ilocalsym;
|
||||
ul32 nlocalsym;
|
||||
ul32 iextdefsym;
|
||||
ul32 nextdefsym;
|
||||
ul32 iundefsym;
|
||||
ul32 nundefsym;
|
||||
ul32 tocoff;
|
||||
ul32 ntoc;
|
||||
ul32 modtaboff;
|
||||
ul32 nmodtab;
|
||||
ul32 extrefsymoff;
|
||||
ul32 nextrefsyms;
|
||||
ul32 indirectsymoff;
|
||||
ul32 nindirectsyms;
|
||||
ul32 extreloff;
|
||||
ul32 nextrel;
|
||||
ul32 locreloff;
|
||||
ul32 nlocrel;
|
||||
};
|
||||
|
||||
struct VersionMinCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 version;
|
||||
ul32 sdk;
|
||||
};
|
||||
|
||||
struct DyldInfoCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 rebase_off;
|
||||
ul32 rebase_size;
|
||||
ul32 bind_off;
|
||||
ul32 bind_size;
|
||||
ul32 weak_bind_off;
|
||||
ul32 weak_bind_size;
|
||||
ul32 lazy_bind_off;
|
||||
ul32 lazy_bind_size;
|
||||
ul32 export_off;
|
||||
ul32 export_size;
|
||||
};
|
||||
|
||||
struct UUIDCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
u8 uuid[16];
|
||||
};
|
||||
|
||||
struct RpathCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 path_off;
|
||||
};
|
||||
|
||||
struct LinkEditDataCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 dataoff;
|
||||
ul32 datasize;
|
||||
};
|
||||
|
||||
struct BuildToolVersion {
|
||||
ul32 tool;
|
||||
ul32 version;
|
||||
};
|
||||
|
||||
struct BuildVersionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 platform;
|
||||
ul32 minos;
|
||||
ul32 sdk;
|
||||
ul32 ntools;
|
||||
};
|
||||
|
||||
struct EntryPointCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 entryoff;
|
||||
ul64 stacksize;
|
||||
};
|
||||
|
||||
struct SourceVersionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 version;
|
||||
};
|
||||
|
||||
struct DataInCodeEntry {
|
||||
ul32 offset;
|
||||
ul16 length;
|
||||
ul16 kind;
|
||||
};
|
||||
|
||||
struct LinkerOptionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 count;
|
||||
};
|
||||
|
||||
// This struct is named `n_list` on BSD and macOS.
|
||||
struct MachSym {
|
||||
bool is_undef() const {
|
||||
return type == N_UNDF && !is_common();
|
||||
}
|
||||
|
||||
bool is_common() const {
|
||||
return type == N_UNDF && is_extern && value;
|
||||
}
|
||||
|
||||
ul32 stroff;
|
||||
|
||||
union {
|
||||
u8 n_type;
|
||||
struct {
|
||||
u8 is_extern : 1;
|
||||
u8 type : 3;
|
||||
u8 is_private_extern : 1;
|
||||
u8 stab : 3;
|
||||
};
|
||||
};
|
||||
|
||||
u8 sect;
|
||||
|
||||
union {
|
||||
ul16 desc;
|
||||
struct {
|
||||
u8 padding;
|
||||
u8 common_p2align : 4;
|
||||
};
|
||||
};
|
||||
|
||||
ul64 value;
|
||||
};
|
||||
|
||||
// This struct is named `relocation_info` on BSD and macOS.
|
||||
struct MachRel {
|
||||
ul32 offset;
|
||||
ul24 idx;
|
||||
u8 is_pcrel : 1;
|
||||
u8 p2size : 2;
|
||||
u8 is_extern : 1;
|
||||
u8 type : 4;
|
||||
};
|
||||
|
||||
// __TEXT,__unwind_info section contents
|
||||
|
||||
static constexpr u32 UNWIND_SECTION_VERSION = 1;
|
||||
static constexpr u32 UNWIND_SECOND_LEVEL_REGULAR = 2;
|
||||
static constexpr u32 UNWIND_SECOND_LEVEL_COMPRESSED = 3;
|
||||
static constexpr u32 UNWIND_PERSONALITY_MASK = 0x30000000;
|
||||
|
||||
struct UnwindSectionHeader {
|
||||
ul32 version;
|
||||
ul32 encoding_offset;
|
||||
ul32 encoding_count;
|
||||
ul32 personality_offset;
|
||||
ul32 personality_count;
|
||||
ul32 page_offset;
|
||||
ul32 page_count;
|
||||
};
|
||||
|
||||
struct UnwindFirstLevelPage {
|
||||
ul32 func_addr;
|
||||
ul32 page_offset;
|
||||
ul32 lsda_offset;
|
||||
};
|
||||
|
||||
struct UnwindSecondLevelPage {
|
||||
ul32 kind;
|
||||
ul16 page_offset;
|
||||
ul16 page_count;
|
||||
ul16 encoding_offset;
|
||||
ul16 encoding_count;
|
||||
};
|
||||
|
||||
struct UnwindLsdaEntry {
|
||||
ul32 func_addr;
|
||||
ul32 lsda_addr;
|
||||
};
|
||||
|
||||
struct UnwindPageEntry {
|
||||
ul24 func_addr;
|
||||
u8 encoding;
|
||||
};
|
||||
|
||||
// __LD,__compact_unwind section contents
|
||||
|
||||
struct CompactUnwindEntry {
|
||||
ul64 code_start;
|
||||
ul32 code_len;
|
||||
ul32 encoding;
|
||||
ul64 personality;
|
||||
ul64 lsda;
|
||||
};
|
||||
|
||||
// __LINKEDIT,__code_signature
|
||||
|
||||
static constexpr u32 CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0;
|
||||
static constexpr u32 CS_SUPPORTSEXECSEG = 0x20400;
|
||||
static constexpr u32 CSMAGIC_CODEDIRECTORY = 0xfade0c02;
|
||||
static constexpr u32 CSSLOT_CODEDIRECTORY = 0;
|
||||
static constexpr u32 CS_ADHOC = 0x00000002;
|
||||
static constexpr u32 CS_LINKER_SIGNED = 0x00020000;
|
||||
static constexpr u32 CS_EXECSEG_MAIN_BINARY = 1;
|
||||
static constexpr u32 CS_HASHTYPE_SHA256 = 2;
|
||||
|
||||
struct CodeSignatureHeader {
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 count;
|
||||
};
|
||||
|
||||
struct CodeSignatureBlobIndex {
|
||||
ub32 type;
|
||||
ub32 offset;
|
||||
ub32 padding;
|
||||
};
|
||||
|
||||
struct CodeSignatureDirectory {
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 version;
|
||||
ub32 flags;
|
||||
ub32 hash_offset;
|
||||
ub32 ident_offset;
|
||||
ub32 n_special_slots;
|
||||
ub32 n_code_slots;
|
||||
ub32 code_limit;
|
||||
u8 hash_size;
|
||||
u8 hash_type;
|
||||
u8 platform;
|
||||
u8 page_size;
|
||||
ub32 spare2;
|
||||
ub32 scatter_offset;
|
||||
ub32 team_offset;
|
||||
ub32 spare3;
|
||||
ub64 code_limit64;
|
||||
ub64 exec_seg_base;
|
||||
ub64 exec_seg_limit;
|
||||
ub64 exec_seg_flags;
|
||||
};
|
||||
|
||||
// __DATA,__objc_imageinfo
|
||||
struct ObjcImageInfo {
|
||||
ul32 version = 0;
|
||||
u8 flags = 0;
|
||||
u8 swift_version = 0;
|
||||
ul16 swift_lang_version = 0;
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_ARM64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 abs_rel = ARM64_RELOC_UNSIGNED;
|
||||
static constexpr u32 stub_size = 12;
|
||||
static constexpr u32 stub_helper_hdr_size = 24;
|
||||
static constexpr u32 stub_helper_size = 12;
|
||||
};
|
||||
|
||||
struct X86_64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_X86_64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_X86_64_ALL;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 abs_rel = X86_64_RELOC_UNSIGNED;
|
||||
static constexpr u32 stub_size = 6;
|
||||
static constexpr u32 stub_helper_hdr_size = 16;
|
||||
static constexpr u32 stub_helper_size = 10;
|
||||
};
|
||||
|
||||
static constexpr size_t word_size = 8;
|
||||
|
||||
} // namespace mold::macho
|
||||
+1137
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,79 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Sym {
|
||||
u64 addr;
|
||||
u32 size;
|
||||
u32 fileidx;
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
std::ofstream out(ctx.arg.map.c_str());
|
||||
if (!out.is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.map << ": " << errno_string();
|
||||
|
||||
out << "# Path: " << ctx.arg.output << "\n"
|
||||
<< "# Arch: x86-64\n"
|
||||
<< "# Object files:\n";
|
||||
|
||||
std::vector<Sym> syms;
|
||||
|
||||
for (i64 i = 0; i < ctx.objs.size(); i++){
|
||||
ObjectFile<E> &file = *ctx.objs[i];
|
||||
|
||||
if (file.is_alive) {
|
||||
out << "[" << std::setw(3) << i << "] " << file << "\n";
|
||||
for (Symbol<E> *sym : file.syms)
|
||||
if (sym && sym->file == &file)
|
||||
if (!sym->subsec || sym->subsec->is_alive)
|
||||
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
|
||||
}
|
||||
}
|
||||
|
||||
sort(syms, [](const Sym &a, const Sym &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
|
||||
out << "# Sections:\n"
|
||||
<< "# Address Size Segment Section\n";
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments) {
|
||||
for (Chunk<E> *chunk : seg->chunks) {
|
||||
if (!chunk->is_hidden) {
|
||||
out << "0x" << std::hex
|
||||
<< std::setw(8) << std::setfill('0') << chunk->hdr.addr
|
||||
<< " 0x"
|
||||
<< std::setw(8) << std::setfill('0') << chunk->hdr.size
|
||||
<< " " << std::setw(7) << std::left << std::setfill(' ')
|
||||
<< chunk->hdr.get_segname()
|
||||
<< " " << chunk->hdr.get_sectname() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out << "# Symbols:\n"
|
||||
<< "# Address Size File Name\n";
|
||||
|
||||
for (Sym &sym : syms) {
|
||||
out << "0x" << std::hex
|
||||
<< std::setw(8) << std::setfill('0') << sym.addr
|
||||
<< " 0x"
|
||||
<< std::setw(8) << std::setfill('0') << 0
|
||||
<< " ["
|
||||
<< std::setw(3) << std::right << std::setfill(' ') << std::dec
|
||||
<< sym.fileidx
|
||||
<< "] " << sym.name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void print_map(Context<E> &);
|
||||
|
||||
} // namespace mold::macho
|
||||
+1047
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+297
@@ -0,0 +1,297 @@
|
||||
// On macOS, you can pass a test file describing a dylib instead of an
|
||||
// actual dylib file to link against a dynamic library. Such text file
|
||||
// should be in the YAML format and contains dylib's exported symbols
|
||||
// as well as the file's various attributes. The extension of the text
|
||||
// file is `.tbd`.
|
||||
//
|
||||
// .tbd files allows users to link against a library without
|
||||
// distributing the binary of the library file itself.
|
||||
//
|
||||
// This file contains functions to parse the .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename T, typename U>
|
||||
static void merge(std::set<T> &a, U &&b) {
|
||||
a.insert(b.begin(), b.end());
|
||||
}
|
||||
|
||||
static std::vector<YamlNode>
|
||||
get_vector(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
if (auto it = map->find(key); it != map->end())
|
||||
if (auto *vec = std::get_if<std::vector<YamlNode>>(&it->second.data))
|
||||
return *vec;
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::vector<std::string_view>
|
||||
get_string_vector(YamlNode &node, std::string_view key) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
vec.push_back(*val);
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::optional<std::string_view>
|
||||
get_string(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
if (auto it = map->find(key); it != map->end())
|
||||
if (auto *str = std::get_if<std::string_view>(&it->second.data))
|
||||
return *str;
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool contains(const std::vector<YamlNode> &vec, std::string_view key) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
if (*val == key)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool match_arch(const std::vector<YamlNode> &vec, std::string_view arch) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
if (*val == arch || val->starts_with(std::string(arch) + "-"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::optional<TextDylib>
|
||||
to_tbd(Context<E> &ctx, YamlNode &node, std::string_view arch,
|
||||
std::string_view filename) {
|
||||
if (!match_arch(get_vector(node, "targets"), arch))
|
||||
return {};
|
||||
|
||||
if (ctx.arg.application_extension &&
|
||||
contains(get_vector(node, "flags"), "not_app_extension_safe"))
|
||||
Warn(ctx) << "linking against a dylib which is not safe for use in "
|
||||
<< "application extensions: " << filename;
|
||||
|
||||
TextDylib tbd;
|
||||
|
||||
if (auto val = get_string(node, "install-name"))
|
||||
tbd.install_name = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "reexported-libraries"))
|
||||
if (match_arch(get_vector(mem, "targets"), arch))
|
||||
append(tbd.reexported_libs, get_string_vector(mem, "libraries"));
|
||||
|
||||
auto concat = [&](const std::string &x, std::string_view y) {
|
||||
return save_string(ctx, x + std::string(y));
|
||||
};
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"}) {
|
||||
for (YamlNode &mem : get_vector(node, key)) {
|
||||
if (match_arch(get_vector(mem, "targets"), arch)) {
|
||||
merge(tbd.exports, get_string_vector(mem, "symbols"));
|
||||
merge(tbd.weak_exports, get_string_vector(mem, "weak-symbols"));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-classes")) {
|
||||
tbd.exports.insert(concat("_OBJC_CLASS_$_", s));
|
||||
tbd.exports.insert(concat("_OBJC_METACLASS_$_", s));
|
||||
}
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-eh-types"))
|
||||
tbd.exports.insert(concat("_OBJC_EHTYPE_$_", s));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-ivars"))
|
||||
tbd.exports.insert(concat("_OBJC_IVAR_$_", s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tbd;
|
||||
}
|
||||
|
||||
static i64 parse_version(const std::string &arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)", flags);
|
||||
|
||||
std::smatch m;
|
||||
[[maybe_unused]] bool ok = std::regex_match(arg, m, re);
|
||||
assert(ok);
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
|
||||
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
// Dylib can contain special symbols whose name starts with "$ld$".
|
||||
// Such symbols aren't actually symbols but linker directives.
|
||||
// We interpret such symbols in this function.
|
||||
template <typename E>
|
||||
static void interpret_ld_symbols(Context<E> &ctx, TextDylib &tbd) {
|
||||
std::set<std::string_view> syms;
|
||||
std::unordered_set<std::string_view> hidden_syms;
|
||||
|
||||
auto string_view = [](const std::csub_match &sub) {
|
||||
return std::string_view{sub.first, (size_t)sub.length()};
|
||||
};
|
||||
|
||||
for (std::string_view s : tbd.exports) {
|
||||
if (!s.starts_with("$ld$"))
|
||||
continue;
|
||||
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
std::cmatch m;
|
||||
|
||||
// $ld$previous$ symbol replaces the default install name with a
|
||||
// specified one if the platform OS version is in a specified range.
|
||||
static std::regex previous_re(
|
||||
R"(\$ld\$previous\$([^$]+)\$([\d.]*)\$(\d+)\$([\d.]+)\$([\d.]+)\$(.*)\$)",
|
||||
flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, previous_re)) {
|
||||
std::string_view install_name = string_view(m[1]);
|
||||
i64 platform = std::stoi(m[3].str());
|
||||
i64 min_version = parse_version(m[4]);
|
||||
i64 max_version = parse_version(m[5]);
|
||||
std::string_view symbol_name = string_view(m[6]);
|
||||
|
||||
if (!symbol_name.empty()) {
|
||||
// ld64 source seems to have implemented a feature to give an
|
||||
// alternative install name for a matching symbol, but it didn't
|
||||
// work in practice (or I may be using the feature in a wrong way.)
|
||||
// Ignore such symbol for now.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (platform == ctx.arg.platform &&
|
||||
min_version <= ctx.arg.platform_min_version &&
|
||||
ctx.arg.platform_min_version < max_version) {
|
||||
tbd.install_name = install_name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$add$os_version$symbol adds a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex add_re(R"(\$ld\$add\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, add_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
syms.insert(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$hide$os_version$symbol hides a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex hidden_re(R"(\$ld\$hide\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, hidden_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
hidden_syms.insert(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$install_name$os_version$name changes the install name to a
|
||||
// given name.
|
||||
static std::regex
|
||||
install_name_re(R"(\$ld\$install_name\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, install_name_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
tbd.install_name = string_view(m[2]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::string_view s : tbd.exports)
|
||||
if (!s.starts_with("$ld$") && !hidden_syms.contains(s))
|
||||
syms.insert(s);
|
||||
|
||||
tbd.exports = std::move(syms);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string_view arch);
|
||||
|
||||
// A single YAML file may contain multiple text dylibs. The first text
|
||||
// dylib is the main file followed by optional other text dylibs for
|
||||
// re-exported libraries.
|
||||
//
|
||||
// This fucntion squashes multiple text dylibs into a single text dylib
|
||||
// by copying symbols of re-exported text dylibs to the main text dylib.
|
||||
template <typename E>
|
||||
static TextDylib
|
||||
squash(Context<E> &ctx, std::span<TextDylib> tbds, std::string_view arch) {
|
||||
std::unordered_map<std::string_view, TextDylib> map;
|
||||
std::vector<std::string_view> remainings;
|
||||
|
||||
TextDylib main = std::move(tbds[0]);
|
||||
for (TextDylib &tbd : tbds.subspan(1))
|
||||
map[tbd.install_name] = std::move(tbd);
|
||||
|
||||
std::function<void(TextDylib &)> visit = [&](TextDylib &tbd) {
|
||||
for (std::string_view lib : tbd.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
|
||||
if (it != map.end()) {
|
||||
TextDylib &child = it->second;
|
||||
merge(main.exports, child.exports);
|
||||
merge(main.weak_exports, child.weak_exports);
|
||||
visit(child);
|
||||
} else {
|
||||
remainings.push_back(lib);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
visit(main);
|
||||
main.reexported_libs = remainings;
|
||||
return main;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string_view arch) {
|
||||
std::string_view contents = mf->get_contents();
|
||||
std::variant<std::vector<YamlNode>, YamlError> res = parse_yaml(contents);
|
||||
|
||||
if (YamlError *err = std::get_if<YamlError>(&res)) {
|
||||
i64 lineno = std::count(contents.begin(), contents.begin() + err->pos, '\n');
|
||||
Fatal(ctx) << mf->name << ":" << (lineno + 1)
|
||||
<< ": YAML parse error: " << err->msg;
|
||||
}
|
||||
|
||||
std::vector<YamlNode> &nodes = std::get<std::vector<YamlNode>>(res);
|
||||
if (nodes.empty())
|
||||
Fatal(ctx) << mf->name << ": malformed TBD file";
|
||||
|
||||
std::vector<TextDylib> vec;
|
||||
for (YamlNode &node : nodes)
|
||||
if (std::optional<TextDylib> dylib = to_tbd(ctx, node, arch, mf->name))
|
||||
vec.push_back(*dylib);
|
||||
|
||||
for (TextDylib &tbd : vec)
|
||||
interpret_ld_symbols(ctx, tbd);
|
||||
|
||||
return squash(ctx, vec, arch);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
TextDylib parse_tbd(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
return parse(ctx, mf, "arm64");
|
||||
if constexpr (std::is_same_v<E, X86_64>)
|
||||
return parse(ctx, mf, "x86_64");
|
||||
unreachable();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template TextDylib parse_tbd(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
} // namespace mold::macho
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
// On macOS, a .tbd file can be used instead of a .dylib to link
|
||||
// against a dynamic library. A .tbd file is a text file in the YAML
|
||||
// format, so the linker has to be able to parse a YAML file. This
|
||||
// file implements a YAML parser.
|
||||
//
|
||||
// We implemented a YAML parser ourselves instead of using an external
|
||||
// library. We didn't want to add an dependency to an external library
|
||||
// just to read .tdb files.
|
||||
//
|
||||
// YAML as a format is very complicated. For example, the official
|
||||
// YAML specification allows embedding a JSON string into YAML; so it
|
||||
// is strictly larger than JSON. It has surprisingly many features
|
||||
// that most users are not aware of. Fortunately, we have to support
|
||||
// only a small portion of the spec to read a .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
|
||||
struct Token {
|
||||
u8 kind = 0;
|
||||
std::string_view str;
|
||||
};
|
||||
|
||||
class YamlParser {
|
||||
public:
|
||||
YamlParser(std::string_view input) : input(input) {}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError> parse();
|
||||
|
||||
private:
|
||||
std::optional<YamlError> tokenize();
|
||||
|
||||
void tokenize_bare_string(std::string_view &str);
|
||||
|
||||
std::optional<YamlError> tokenize_list(std::string_view &str);
|
||||
std::optional<YamlError> tokenize_string(std::string_view &str, char end);
|
||||
|
||||
std::variant<YamlNode, YamlError> parse_element(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_list(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_map(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_flow_element(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_flow_list(std::span<Token> &tok);
|
||||
|
||||
std::string_view input;
|
||||
std::vector<Token> tokens;
|
||||
};
|
||||
|
||||
// A tokenizer for YAML. YAML represents blocks by indentation. This
|
||||
// tokenizer inserts INDENT and DEDENT special tokens before and after
|
||||
// each indented text block.
|
||||
std::optional<YamlError> YamlParser::tokenize() {
|
||||
std::vector<i64> indents = {0};
|
||||
|
||||
auto indent = [&](std::string_view str, i64 depth) {
|
||||
tokens.push_back({INDENT, str});
|
||||
indents.push_back(depth);
|
||||
};
|
||||
|
||||
auto dedent = [&](std::string_view str) {
|
||||
assert(indents.size() > 1);
|
||||
tokens.push_back({DEDENT, str});
|
||||
indents.pop_back();
|
||||
};
|
||||
|
||||
auto skip_line = [](std::string_view &str) {
|
||||
size_t pos = str.find('\n');
|
||||
if (pos == str.npos)
|
||||
str = str.substr(str.size());
|
||||
else
|
||||
str = str.substr(pos + 1);
|
||||
};
|
||||
|
||||
auto tokenize_line = [&](std::string_view &str) -> std::optional<YamlError> {
|
||||
const char *start = str.data();
|
||||
|
||||
if (str.starts_with("---")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({END, str.substr(0, 3)});
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with("...")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({END, str.substr(0, 3)});
|
||||
str = str.substr(str.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos || str[pos] == '#' || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (indents.back() != pos) {
|
||||
if (indents.back() < pos) {
|
||||
indent(str, pos);
|
||||
} else {
|
||||
while (indents.back() != pos) {
|
||||
if (pos < indents.back())
|
||||
dedent(str);
|
||||
else
|
||||
return YamlError{"bad indentation", start - input.data()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
|
||||
while (!str.empty()) {
|
||||
if (str[0] == '\n') {
|
||||
str = str.substr(1);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with("- ")) {
|
||||
tokens.push_back({'-', str.substr(0, 1)});
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t", 1);
|
||||
if (pos == str.npos || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
indent(str, str.data() - start);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('['))
|
||||
return tokenize_list(str);
|
||||
|
||||
if (str.starts_with('\'')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '\''))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('"')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '"'))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('#')) {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with(':')) {
|
||||
tokens.push_back({':', str.substr(0, 1)});
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t", 1);
|
||||
if (pos == str.npos || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
tokenize_bare_string(str);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
std::string_view str = input;
|
||||
while (!str.empty())
|
||||
if (std::optional<YamlError> err = tokenize_line(str))
|
||||
return err;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<YamlError> YamlParser::tokenize_list(std::string_view &str) {
|
||||
const char *start = str.data();
|
||||
|
||||
tokens.push_back({'[', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
|
||||
while (!str.empty() && str[0] != ']') {
|
||||
if (size_t pos = str.find_first_not_of(" \t\n"); pos) {
|
||||
str = str.substr(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('\'')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '\''))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('"')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '"'))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with(',')) {
|
||||
tokens.push_back({',', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
tokenize_bare_string(str);
|
||||
}
|
||||
|
||||
if (str.empty())
|
||||
return YamlError{"unclosed list", start - input.data()};
|
||||
|
||||
const char *bracket = str.data();
|
||||
tokens.push_back({']', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
|
||||
while (!str.empty() && (str[0] == ' ' || str[0] == '\t'))
|
||||
str = str.substr(1);
|
||||
if (str.empty() || str[0] != '\n')
|
||||
return YamlError{"no newline after ']'", bracket - input.data()};
|
||||
str = str.substr(1);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<YamlError>
|
||||
YamlParser::tokenize_string(std::string_view &str, char end) {
|
||||
const char *start = str.data();
|
||||
size_t pos = str.find(end, 1);
|
||||
if (pos == str.npos)
|
||||
return YamlError{"unterminated string literal", start - input.data()};
|
||||
|
||||
tokens.push_back({STRING, str.substr(1, pos - 1)});
|
||||
str = str.substr(pos + 1);
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
YamlParser::tokenize_bare_string(std::string_view &str) {
|
||||
size_t pos = str.find_first_not_of(
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-/.");
|
||||
if (pos == str.npos)
|
||||
pos = str.size();
|
||||
tokens.push_back({STRING, str.substr(0, pos)});
|
||||
str = str.substr(pos);
|
||||
}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
if (std::optional<YamlError> err = tokenize())
|
||||
return *err;
|
||||
|
||||
std::span<Token> tok(tokens);
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0].kind == END) {
|
||||
tok = tok.subspan(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind != END)
|
||||
return YamlError{"stray token", tok[0].str.data() - input.data()};
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == INDENT) {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
assert(tok[0].kind == DEDENT);
|
||||
tok = tok.subspan(1);
|
||||
return elem;
|
||||
}
|
||||
|
||||
if (tok[0].kind == '-')
|
||||
return parse_list(tok);
|
||||
|
||||
if (tok.size() > 2 && tok[0].kind == STRING && tok[1].kind == ':')
|
||||
return parse_map(tok);
|
||||
|
||||
return parse_flow_element(tok);
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (tok[0].kind != END && tok[0].kind != DEDENT) {
|
||||
if (tok[0].kind != '-')
|
||||
return YamlError{"list element expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
}
|
||||
return YamlNode{vec};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_map(std::span<Token> &tok) {
|
||||
std::map<std::string_view, YamlNode> map;
|
||||
|
||||
while (tok[0].kind != END && tok[0].kind != DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != STRING || tok[1].kind != ':')
|
||||
return YamlError{"map key expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view key = tok[0].str;
|
||||
tok = tok.subspan(2);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
map[key] = std::get<YamlNode>(elem);
|
||||
}
|
||||
return YamlNode{map};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_flow_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == '[') {
|
||||
tok = tok.subspan(1);
|
||||
return parse_flow_list(tok);
|
||||
}
|
||||
|
||||
if (tok[0].kind != STRING)
|
||||
return YamlError{"scalar expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view val = tok[0].str;
|
||||
tok = tok.subspan(1);
|
||||
return YamlNode{val};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_flow_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
const char *start = tok[0].str.data();
|
||||
|
||||
while (tok[0].kind != ']' && tok[0].kind != END) {
|
||||
std::variant<YamlNode, YamlError> elem = parse_flow_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind == ']')
|
||||
break;
|
||||
if (tok[0].kind != ',')
|
||||
return YamlError{"comma expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok[0].kind == END)
|
||||
return YamlError{"unterminated flow list", start - input.data()};
|
||||
|
||||
tok = tok.subspan(1);
|
||||
return YamlNode{vec};
|
||||
}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError>
|
||||
parse_yaml(std::string_view str) {
|
||||
return YamlParser(str).parse();
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string mold_version_string = MOLD_VERSION;
|
||||
|
||||
namespace elf {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
namespace macho {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
static std::string get_mold_version() {
|
||||
if (mold_git_hash.empty())
|
||||
return "mold " MOLD_VERSION " (compatible with GNU ld)";
|
||||
return "mold " MOLD_VERSION " (" + mold_git_hash + "; compatible with GNU ld)";
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
std::string errno_string() {
|
||||
// strerror is not thread-safe, so guard it with a lock.
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#ifdef __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS or structured
|
||||
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
#ifdef _WIN32
|
||||
|
||||
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
PEXCEPTION_RECORD exception_record = exception_info->ExceptionRecord;
|
||||
ULONG_PTR *exception_information = exception_record->ExceptionInformation;
|
||||
if (exception_record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
|
||||
(ULONG_PTR)output_buffer_start <= exception_information[1] &&
|
||||
exception_information[1] < (ULONG_PTR)output_buffer_end) {
|
||||
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
AddVectoredExceptionHandler(0, vectored_handler);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
if ((signo == SIGSEGV || signo == SIGBUS) &&
|
||||
output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
mold::mold_version = mold::get_mold_version();
|
||||
|
||||
std::string cmd = mold::filepath(argv[0]).filename().string();
|
||||
if (cmd == "ld64" || cmd == "ld64.mold")
|
||||
return mold::macho::main(argc, argv);
|
||||
return mold::elf::main(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,882 @@
|
||||
#pragma once
|
||||
|
||||
#include "inttypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <io.h>
|
||||
#else
|
||||
# include <sys/mman.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define XXH_INLINE_ALL 1
|
||||
#include "third-party/xxhash/xxhash.h"
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define unreachable() __builtin_unreachable()
|
||||
#else
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
inline uint64_t hash_string(std::string_view str) {
|
||||
return XXH3_64bits(str.data(), str.size());
|
||||
}
|
||||
|
||||
class HashCmp {
|
||||
public:
|
||||
static size_t hash(const std::string_view &k) {
|
||||
return hash_string(k);
|
||||
}
|
||||
|
||||
static bool equal(const std::string_view &k1, const std::string_view &k2) {
|
||||
return k1 == k2;
|
||||
}
|
||||
};
|
||||
|
||||
namespace mold {
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
template <typename C> class OutputFile;
|
||||
|
||||
inline char *output_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
inline std::string mold_version;
|
||||
extern std::string mold_version_string;
|
||||
extern std::string mold_git_hash;
|
||||
|
||||
std::string errno_string();
|
||||
std::string get_self_path();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
i64 get_default_thread_count();
|
||||
|
||||
//
|
||||
// Error output
|
||||
//
|
||||
|
||||
template <typename C>
|
||||
class SyncOut {
|
||||
public:
|
||||
SyncOut(C &ctx, std::ostream &out = std::cout) : out(out) {
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
}
|
||||
|
||||
~SyncOut() {
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << "\n";
|
||||
}
|
||||
|
||||
template <class T> SyncOut &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static inline std::mutex mu;
|
||||
|
||||
private:
|
||||
std::ostream &out;
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
static std::string add_color(C &ctx, std::string msg) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
|
||||
return "mold: " + msg + ": ";
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(C &ctx) : out(ctx, std::cerr) {
|
||||
out << add_color(ctx, "fatal");
|
||||
}
|
||||
|
||||
[[noreturn]] ~Fatal() {
|
||||
out.~SyncOut();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <class T> Fatal &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
class Error {
|
||||
public:
|
||||
Error(C &ctx) : out(ctx, std::cerr) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << add_color(ctx, "warning");
|
||||
} else {
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> Error &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(C &ctx) : out(ctx, std::cerr) {
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
} else {
|
||||
out << add_color(ctx, "warning");
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> Warn &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
};
|
||||
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
// Some C++ libraries haven't implemented std::has_single_bit yet.
|
||||
inline bool has_single_bit(u64 val) {
|
||||
return std::popcount(val) == 1;
|
||||
}
|
||||
|
||||
// Some C++ libraries haven't implemented std::bit_ceil yet.
|
||||
inline u64 bit_ceil(u64 val) {
|
||||
if (has_single_bit(val))
|
||||
return val;
|
||||
return 1LL << (64 - std::countl_zero(val));
|
||||
}
|
||||
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
if (align == 0)
|
||||
return val;
|
||||
assert(has_single_bit(align));
|
||||
return (val + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 align_down(u64 val, u64 align) {
|
||||
assert(has_single_bit(align));
|
||||
return val & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 bit(u64 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
};
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
inline i64 sign_extend(u64 val, i64 size) {
|
||||
return (i64)(val << (63 - size)) >> (63 - size);
|
||||
};
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(new_val, old_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(old_val, new_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
// An optimized "mark" operation for parallel mark-and-sweep algorithms.
|
||||
// Returns true if `visited` was false and updated to true.
|
||||
inline bool fast_mark(std::atomic<bool> &visited) {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return !visited.load(std::memory_order_relaxed) &&
|
||||
!visited.exchange(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
|
||||
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
|
||||
std::vector<T> ret;
|
||||
for (std::vector<T> &v : vec)
|
||||
append(ret, v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void sort(T &vec) {
|
||||
std::stable_sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void sort(T &vec, U less) {
|
||||
std::stable_sort(vec.begin(), vec.end(), less);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void remove_duplicates(std::vector<T> &vec) {
|
||||
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
|
||||
}
|
||||
|
||||
inline i64 write_string(void *buf, std::string_view str) {
|
||||
memcpy(buf, str.data(), str.size());
|
||||
*((u8 *)buf + str.size()) = '\0';
|
||||
return str.size() + 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline i64 write_vector(void *buf, const std::vector<T> &vec) {
|
||||
i64 sz = vec.size() * sizeof(T);
|
||||
memcpy(buf, vec.data(), sz);
|
||||
return sz;
|
||||
}
|
||||
|
||||
inline void encode_uleb(std::vector<u8> &vec, u64 val) {
|
||||
do {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
vec.push_back(val ? (byte | 0x80) : byte);
|
||||
} while (val);
|
||||
}
|
||||
|
||||
inline void encode_sleb(std::vector<u8> &vec, i64 val) {
|
||||
for (;;) {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
|
||||
bool neg = (byte & 0x40);
|
||||
if ((val == 0 && !neg) || (val == -1 && neg)) {
|
||||
vec.push_back(byte);
|
||||
break;
|
||||
}
|
||||
vec.push_back(byte | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
inline i64 write_uleb(u8 *buf, u64 val) {
|
||||
i64 i = 0;
|
||||
do {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
buf[i++] = val ? (byte | 0x80) : byte;
|
||||
} while (val);
|
||||
return i;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 *&buf) {
|
||||
u64 val = 0;
|
||||
u8 shift = 0;
|
||||
u8 byte;
|
||||
do {
|
||||
byte = *buf++;
|
||||
val |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 const*&buf) {
|
||||
return read_uleb(const_cast<u8 *&>(buf));
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view &str) {
|
||||
u8 *start = (u8 *)&str[0];
|
||||
u8 *ptr = start;
|
||||
u64 val = read_uleb(ptr);
|
||||
str = str.substr(ptr - start);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline i64 uleb_size(u64 val) {
|
||||
#if __GNUC__
|
||||
#pragma GCC unroll 8
|
||||
#endif
|
||||
for (int i = 1; i < 9; i++)
|
||||
if (val < (1LL << (7 * i)))
|
||||
return i;
|
||||
return 9;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::string_view save_string(C &ctx, const std::string &str) {
|
||||
u8 *buf = new u8[str.size() + 1];
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
ctx.string_pool.push_back(std::unique_ptr<u8[]>(buf));
|
||||
return {(char *)buf, str.size()};
|
||||
}
|
||||
|
||||
inline bool remove_prefix(std::string_view &s, std::string_view prefix) {
|
||||
if (s.starts_with(prefix)) {
|
||||
s = s.substr(prefix.size());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Concurrent Map
|
||||
//
|
||||
|
||||
// This is an implementation of a fast concurrent hash map. Unlike
|
||||
// ordinary hash tables, this impl just aborts if it becomes full.
|
||||
// So you need to give a correct estimation of the final size before
|
||||
// using it. We use this hash map to uniquify pieces of data in
|
||||
// mergeable sections.
|
||||
template <typename T>
|
||||
class ConcurrentMap {
|
||||
public:
|
||||
ConcurrentMap() {}
|
||||
|
||||
ConcurrentMap(i64 nbuckets) {
|
||||
resize(nbuckets);
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
if (keys) {
|
||||
free((void *)keys);
|
||||
free((void *)key_sizes);
|
||||
free((void *)values);
|
||||
}
|
||||
}
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
this->~ConcurrentMap();
|
||||
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
|
||||
key_sizes = (u32 *)calloc(nbuckets, sizeof(key_sizes[0]));
|
||||
values = (T *)calloc(nbuckets, sizeof(values[0]));
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
if (!keys)
|
||||
return {nullptr, false};
|
||||
|
||||
assert(has_single_bit(nbuckets));
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
|
||||
while (retry < MAX_RETRY) {
|
||||
const char *ptr = keys[idx];
|
||||
if (ptr == marker) {
|
||||
pause();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr == nullptr) {
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker))
|
||||
continue;
|
||||
new (values + idx) T(val);
|
||||
key_sizes[idx] = key.size();
|
||||
keys[idx] = key.data();
|
||||
return {values + idx, true};
|
||||
}
|
||||
|
||||
if (key.size() == key_sizes[idx] &&
|
||||
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
|
||||
return {values + idx, false};
|
||||
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
idx = (idx & ~mask) | ((idx + 1) & mask);
|
||||
retry++;
|
||||
}
|
||||
|
||||
assert(false && "ConcurrentMap is full");
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
bool has_key(i64 idx) {
|
||||
return keys[idx].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 2048;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 128;
|
||||
|
||||
i64 nbuckets = 0;
|
||||
std::atomic<const char *> *keys = nullptr;
|
||||
u32 *key_sizes = nullptr;
|
||||
T *values = nullptr;
|
||||
|
||||
private:
|
||||
static void pause() {
|
||||
#if defined(__x86_64__)
|
||||
asm volatile("pause");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr const char *marker = "marker";
|
||||
};
|
||||
|
||||
//
|
||||
// output-file.h
|
||||
//
|
||||
|
||||
template <typename C>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile<C>>
|
||||
open(C &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(C &ctx) = 0;
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
OutputFile(std::string path, i64 filesize, bool is_mmapped)
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
|
||||
class HyperLogLog {
|
||||
public:
|
||||
HyperLogLog() : buckets(NBUCKETS) {}
|
||||
|
||||
void insert(u32 hash) {
|
||||
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
|
||||
}
|
||||
|
||||
i64 get_cardinality() const;
|
||||
|
||||
void merge(const HyperLogLog &other) {
|
||||
for (i64 i = 0; i < NBUCKETS; i++)
|
||||
update_maximum(buckets[i], other.buckets[i]);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr i64 NBUCKETS = 2048;
|
||||
static constexpr double ALPHA = 0.79402;
|
||||
|
||||
std::vector<std::atomic_uint8_t> buckets;
|
||||
};
|
||||
|
||||
//
|
||||
// glob.cc
|
||||
//
|
||||
|
||||
class Glob {
|
||||
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
|
||||
|
||||
struct Element {
|
||||
Element(Kind k) : kind(k) {}
|
||||
Kind kind;
|
||||
std::string str;
|
||||
std::bitset<256> bitset;
|
||||
};
|
||||
|
||||
public:
|
||||
static std::optional<Glob> compile(std::string_view pat);
|
||||
bool match(std::string_view str);
|
||||
|
||||
private:
|
||||
Glob(std::vector<Element> &&vec) : elements(vec) {}
|
||||
static bool do_match(std::string_view str, std::span<Element> elements);
|
||||
|
||||
std::vector<Element> elements;
|
||||
};
|
||||
|
||||
//
|
||||
// multi-glob.cc
|
||||
//
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, u32 val);
|
||||
bool empty() const { return strings.empty(); }
|
||||
std::optional<u32> find(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
u32 value = -1;
|
||||
TrieNode *suffix_link = nullptr;
|
||||
std::unique_ptr<TrieNode> children[256];
|
||||
};
|
||||
|
||||
void compile();
|
||||
void fix_suffix_links(TrieNode &node);
|
||||
void fix_values();
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::unique_ptr<TrieNode> root;
|
||||
std::vector<std::pair<Glob, u32>> globs;
|
||||
std::once_flag once;
|
||||
bool is_compiled = false;
|
||||
};
|
||||
|
||||
//
|
||||
// uuid.cc
|
||||
//
|
||||
|
||||
std::array<u8, 16> get_uuid_v4();
|
||||
|
||||
//
|
||||
// filepath.cc
|
||||
//
|
||||
|
||||
template <typename T>
|
||||
std::filesystem::path filepath(const T &path) {
|
||||
return {path, std::filesystem::path::format::generic_format};
|
||||
}
|
||||
|
||||
std::string get_realpath(std::string_view path);
|
||||
std::string path_clean(std::string_view path);
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path);
|
||||
|
||||
//
|
||||
// demangle.cc
|
||||
//
|
||||
|
||||
std::string_view demangle(std::string_view name);
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name);
|
||||
|
||||
//
|
||||
// compress.cc
|
||||
//
|
||||
|
||||
class Compressor {
|
||||
public:
|
||||
virtual void write_to(u8 *buf) = 0;
|
||||
virtual ~Compressor() {}
|
||||
i64 compressed_size = 0;
|
||||
};
|
||||
|
||||
class ZlibCompressor : public Compressor {
|
||||
public:
|
||||
ZlibCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
u64 checksum = 0;
|
||||
};
|
||||
|
||||
class ZstdCompressor : public Compressor {
|
||||
public:
|
||||
ZstdCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled)
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled)
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename C>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(C &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
|
||||
// TarFile is a class to create a tar file.
|
||||
//
|
||||
// If you pass `--repro` to mold, mold collects all input files and
|
||||
// put them into `<output-file-path>.repro.tar`, so that it is easy to
|
||||
// run the same command with the same command line arguments.
|
||||
class TarWriter {
|
||||
public:
|
||||
static std::unique_ptr<TarWriter>
|
||||
open(std::string output_path, std::string basedir);
|
||||
|
||||
~TarWriter();
|
||||
void append(std::string path, std::string_view data);
|
||||
|
||||
private:
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
|
||||
|
||||
FILE *out = nullptr;
|
||||
std::string basedir;
|
||||
};
|
||||
|
||||
//
|
||||
// Memory-mapped file
|
||||
//
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
template <typename C>
|
||||
class MappedFile {
|
||||
public:
|
||||
static MappedFile *open(C &ctx, std::string path);
|
||||
static MappedFile *must_open(C &ctx, std::string path);
|
||||
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
|
||||
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
|
||||
|
||||
std::string_view get_contents() {
|
||||
return std::string_view((char *)data, size);
|
||||
}
|
||||
|
||||
i64 get_offset() const {
|
||||
return parent ? (data - parent->data + parent->get_offset()) : 0;
|
||||
}
|
||||
|
||||
// Returns a string that uniquely identify a file that is possibly
|
||||
// in an archive.
|
||||
std::string get_identifier() const {
|
||||
if (parent) {
|
||||
// We use the file offset within an archive as an identifier
|
||||
// because archive members may have the same name.
|
||||
return parent->name + ":" + std::to_string(get_offset());
|
||||
}
|
||||
|
||||
if (thin_parent) {
|
||||
// If this is a thin archive member, the filename part is
|
||||
// guaranteed to be unique.
|
||||
return thin_parent->name + ":" + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
u8 *data = nullptr;
|
||||
i64 size = 0;
|
||||
i64 mtime = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
int fd = -1;
|
||||
#ifdef _WIN32
|
||||
HANDLE file_handle = INVALID_HANDLE_VALUE;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
#else
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
|
||||
if (fd == -1)
|
||||
return nullptr;
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1)
|
||||
Fatal(ctx) << path << ": fstat failed: " << errno_string();
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
#ifdef _WIN32
|
||||
mf->mtime = st.st_mtime;
|
||||
#elif defined(__APPLE__)
|
||||
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
|
||||
#else
|
||||
mf->mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
|
||||
#endif
|
||||
|
||||
if (st.st_size > 0) {
|
||||
#ifdef _WIN32
|
||||
HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
|
||||
nullptr, PAGE_READWRITE, 0,
|
||||
st.st_size, nullptr);
|
||||
if (!handle)
|
||||
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
|
||||
mf->file_handle = handle;
|
||||
mf->data = (u8 *)MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, st.st_size);
|
||||
if (!mf->data)
|
||||
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
|
||||
#else
|
||||
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE, fd, 0);
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
#endif
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
|
||||
if (MappedFile *mf = MappedFile::open(ctx, path))
|
||||
return mf;
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *
|
||||
MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile<C>;
|
||||
mf->name = name;
|
||||
mf->data = data + start;
|
||||
mf->size = size;
|
||||
mf->parent = this;
|
||||
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
void MappedFile<C>::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
UnmapViewOfFile(data);
|
||||
if (file_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(file_handle);
|
||||
#else
|
||||
munmap(data, size);
|
||||
#endif
|
||||
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
Loaded 100 of 2457 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user