mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96c798d7be | ||
|
|
6214f7be34 | ||
|
|
9b8b666da8 | ||
|
|
6465232f0c | ||
|
|
cc32a6d559 | ||
|
|
6214e132f8 | ||
|
|
6ca242b8f0 | ||
|
|
3721c52d34 | ||
|
|
341c88e426 | ||
|
|
8f3f71663e | ||
|
|
1012a4804c | ||
|
|
5962014fd8 | ||
|
|
dac20fa243 | ||
|
|
8fa6fc86d3 | ||
|
|
1c64d40ab8 | ||
|
|
58cf042386 | ||
|
|
d2f8bffdba | ||
|
|
60a9e013d3 | ||
|
|
a8893494ae | ||
|
|
2479b81f8a | ||
|
|
4dd133a31e | ||
|
|
02678c8af0 | ||
|
|
29e8b83886 | ||
|
|
f0b933820d | ||
|
|
9a2f31164b | ||
|
|
8ef93b14d6 | ||
|
|
8d57a4419d | ||
|
|
0ef77ebd98 | ||
|
|
00d45d317f | ||
|
|
cdb63bb2a1 | ||
|
|
bcd77395d4 | ||
|
|
4074707891 | ||
|
|
079f914898 | ||
|
|
d59c55959c | ||
|
|
5e19601a17 | ||
|
|
3de3dcc001 | ||
|
|
0d8334e5a2 | ||
|
|
a731b4c58c | ||
|
|
971317f3a9 | ||
|
|
0a55058042 | ||
|
|
32746ebbb3 | ||
|
|
1f44438b63 | ||
|
|
b53197300b | ||
|
|
3e9321b9a0 | ||
|
|
142eeaba86 | ||
|
|
15f6b9f966 | ||
|
|
a310cd27d4 | ||
|
|
2d178eb1ed | ||
|
|
0fdffad57f | ||
|
|
d05272c4ee | ||
|
|
1683826f2f | ||
|
|
9ddec785b1 | ||
|
|
e4f867ad77 | ||
|
|
b7ce902e52 | ||
|
|
133613a493 | ||
|
|
b026d7119b | ||
|
|
fc0dd85d87 | ||
|
|
6577232458 | ||
|
|
b504cacf25 | ||
|
|
159ce3ba67 | ||
|
|
4c2dae0a2d | ||
|
|
275c53d615 | ||
|
|
fdd959880a | ||
|
|
26d827d6e7 | ||
|
|
858fd8c495 | ||
|
|
c13ecc99eb | ||
|
|
122268d147 | ||
|
|
b990467c16 | ||
|
|
3d866bea61 | ||
|
|
19a1bc651f | ||
|
|
18c813a21c | ||
|
|
af36625a02 | ||
|
|
53c1758503 | ||
|
|
67ceb41b79 | ||
|
|
7d99824c3d | ||
|
|
344dcff8e8 | ||
|
|
d0dffd5985 | ||
|
|
251041e371 | ||
|
|
4dd872ee8d | ||
|
|
bb1b9dda64 | ||
|
|
78ec306cf4 | ||
|
|
163c62f491 | ||
|
|
026fb54b9d | ||
|
|
cd162ba9f6 | ||
|
|
e08e7f6aa4 | ||
|
|
6be2e676d2 | ||
|
|
b6565025a3 | ||
|
|
118dc5de8e | ||
|
|
f3475d9f09 | ||
|
|
299904e56f | ||
|
|
fded2d8a77 | ||
|
|
f0e726024e | ||
|
|
3d29f1678d | ||
|
|
559f23ff81 | ||
|
|
3274bcbdf0 | ||
|
|
83dd353979 | ||
|
|
8bae43b842 | ||
|
|
4038cfd64d | ||
|
|
41079c64f5 | ||
|
|
02e410133b | ||
|
|
e70236a45a | ||
|
|
285c5bb509 | ||
|
|
975da12333 | ||
|
|
157b16adb5 | ||
|
|
564757a0f5 | ||
|
|
216b4a2325 | ||
|
|
954107e29c | ||
|
|
21e20e01da | ||
|
|
161cb6c99c | ||
|
|
21cfee556a | ||
|
|
06203c4a72 | ||
|
|
232067634f | ||
|
|
3f9aab7066 | ||
|
|
8b88595f26 | ||
|
|
fa09c562c9 | ||
|
|
9457d993c4 | ||
|
|
43810dfecd | ||
|
|
2d6061a233 | ||
|
|
3d6aad65b0 | ||
|
|
f7b1920f84 | ||
|
|
b42f978212 |
No files matched your search
@@ -3,8 +3,6 @@ name: Build all tarballs
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
release:
|
||||
types: [ published ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -5,7 +5,7 @@ apt-get install -y wget xz-utils
|
||||
|
||||
# Install a 32-bit RISC-V toolchain
|
||||
mkdir /rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-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
|
||||
@@ -18,7 +18,7 @@ done
|
||||
|
||||
# Install a LoongArch toolchain
|
||||
mkdir /larch
|
||||
wget -O- -q https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.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
|
||||
@@ -27,25 +27,39 @@ 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 -q https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
|
||||
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2024.11.01/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- -q https://sources.buildroot.net/toolchain-external-arm-aarch64-be/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64_be-none-linux-gnu.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
|
||||
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-none-linux-gnu/libc /usr/aarch64_be-linux-gnu
|
||||
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
|
||||
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
|
||||
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-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
|
||||
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- -q 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 -
|
||||
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++
|
||||
@@ -57,5 +71,5 @@ done
|
||||
|
||||
# Install Intel SDE CPU emulator for CET-related tests
|
||||
mkdir /sde
|
||||
wget -O- -q https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
|
||||
ln -s /sde/sde64 /usr/bin
|
||||
@@ -0,0 +1,80 @@
|
||||
name: Build & attach tarballs on tag push
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v[0-9]*']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
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@v4
|
||||
|
||||
- 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@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
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'
|
||||
+30
-26
@@ -44,7 +44,7 @@
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.37.1)
|
||||
project(mold VERSION 2.40.1)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
@@ -62,7 +62,11 @@ 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 -Wl,--gdb-index)
|
||||
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)
|
||||
@@ -160,13 +164,13 @@ check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
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()
|
||||
@@ -213,7 +217,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)
|
||||
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
else()
|
||||
@@ -289,10 +293,10 @@ check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
|
||||
-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 git-hash.cc
|
||||
BYPRODUCTS mold-git-hash.h
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
@@ -310,15 +314,19 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
|
||||
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64 ARC)
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
|
||||
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
|
||||
LOONGARCH32 LOONGARCH64)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
src/arch-arm32.cc
|
||||
src/arch-arm64.cc
|
||||
src/arch-loongarch.cc
|
||||
src/arch-riscv.cc
|
||||
src/arch-sh4.cc
|
||||
src/archive-file.cc
|
||||
src/cmdline.cc
|
||||
src/error.cc
|
||||
src/filetype.cc
|
||||
src/gc-sections.cc
|
||||
src/gdb-index.cc
|
||||
@@ -373,20 +381,15 @@ endforeach()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
git-hash.cc
|
||||
lib/compress.cc
|
||||
lib/aho-corasick.cc
|
||||
lib/crc32.cc
|
||||
lib/demangle.cc
|
||||
lib/filepath.cc
|
||||
lib/glob.cc
|
||||
lib/hyperloglog.cc
|
||||
lib/mimalloc.cc
|
||||
lib/multi-glob.cc
|
||||
lib/perf.cc
|
||||
lib/random.cc
|
||||
lib/tar.cc
|
||||
src/arch-arc.cc
|
||||
src/arch-arm32.cc
|
||||
src/arch-i386.cc
|
||||
src/arch-m68k.cc
|
||||
src/arch-ppc32.cc
|
||||
@@ -395,22 +398,23 @@ target_sources(mold PRIVATE
|
||||
src/arch-s390x.cc
|
||||
src/arch-sparc64.cc
|
||||
src/arch-x86-64.cc
|
||||
src/config.cc
|
||||
src/compress.cc
|
||||
src/elf.cc
|
||||
src/entry.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(mold PRIVATE
|
||||
lib/jobs-win32.cc
|
||||
lib/mapped-file-win32.cc
|
||||
lib/signal-win32.cc
|
||||
src/jobs-win32.cc
|
||||
src/mapped-file-win32.cc
|
||||
src/signal-win32.cc
|
||||
)
|
||||
else()
|
||||
target_sources(mold PRIVATE
|
||||
lib/jobs-unix.cc
|
||||
lib/mapped-file-unix.cc
|
||||
lib/signal-unix.cc
|
||||
src/jobs-unix.cc
|
||||
src/mapped-file-unix.cc
|
||||
src/signal-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ accept an absolute path as an argument for `-fuse-ld`.
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
@@ -131,7 +131,7 @@ example above, we use `clang` as a linker driver since it always accepts the
|
||||
may be able to remove the `linker = "clang"` line.
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
set -e
|
||||
apt-get install -y git bzip2 ninja-build pkg-config libglib2.0-dev python3-distutils-extra libpixman-1-dev
|
||||
|
||||
cd /
|
||||
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/qemu.git
|
||||
mkdir /qemu/build
|
||||
cd /qemu/build
|
||||
../configure --static --extra-cflags=-Wno-error
|
||||
ninja
|
||||
ln -s /qemu/build/qemu-arc /qemu/build/qemu-arc64 /usr/local/bin
|
||||
|
||||
|
||||
apt-get install -y libgmp-dev libmpfr-dev texinfo bison yacc flex
|
||||
cd /
|
||||
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/binutils-gdb.git
|
||||
cd /binutils-gdb
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --target=arc-linux
|
||||
make -j$(nproc)
|
||||
|
||||
|
||||
apt-get install -y mold
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DMOLD_USE_MOLD=1 /mold
|
||||
ninja
|
||||
TRIPLE=arc-linux-gnu bash -x /mold/test/hello-dynamic.sh
|
||||
@@ -126,10 +126,10 @@ RUN mkdir /build && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build LLVM 19
|
||||
# Build LLVM 20
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
wget -O- --no-check-certificate --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 && \
|
||||
@@ -195,14 +195,22 @@ dest=mold-$version-$arch-linux
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp="$(git log -1 --format=%ci)"
|
||||
timestamp=$(git log -1 --format=%ct)
|
||||
|
||||
# Build mold in a container.
|
||||
mkdir -p dist
|
||||
|
||||
# If the host is ARM64, `uname -m` in an ARM32 container running on it
|
||||
# reports it not as ARM32 but as ARM64, which confuses BLAKE3's cmake script
|
||||
# and erroneously enables NEON SIMD instructions. `setarch` can be used
|
||||
# to change the output of `uname -m`.
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
|
||||
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image bash -c "
|
||||
--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
|
||||
@@ -212,7 +220,7 @@ 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 | 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
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "MOLD" "1" "February 2025" ""
|
||||
.TH "MOLD" "1" "June 2025" ""
|
||||
.SH "NAME"
|
||||
\fBmold\fR \- a modern linker
|
||||
.SH "SYNOPSIS"
|
||||
@@ -70,6 +70,8 @@ Permit or do not permit mold to create a debug info file in the background\.
|
||||
.TP
|
||||
\fB\-\-fork\fR, \fB\-\-no\-fork\fR
|
||||
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 \fBmold\fR process\. \fB\-\-fork\fR hides that latency\. By default, it does fork\.
|
||||
.IP
|
||||
Note that \fB\-\-fork\fR 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 \fB\-\-no\-fork\fR\.
|
||||
.TP
|
||||
\fB\-\-perf\fR
|
||||
Print performance statistics\.
|
||||
|
||||
@@ -159,6 +159,10 @@ but as `-o magic`.
|
||||
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.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ gentoo)
|
||||
emerge-webrsync
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
|
||||
;;
|
||||
arch | archarm | artix | endeavouros)
|
||||
arch | archarm | artix | endeavouros | manjaro | cachyos)
|
||||
pacman -Sy --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
void)
|
||||
|
||||
@@ -11,7 +11,7 @@ set -x
|
||||
|
||||
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,arc}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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
|
||||
@@ -0,0 +1,78 @@
|
||||
#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
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
// This file implements a multi-threaded zlib and zstd compression
|
||||
// routine.
|
||||
//
|
||||
// zlib-compressed data can be merged just by concatenation as long as
|
||||
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
|
||||
// split input data into multiple shards, compress them individually
|
||||
// and concatenate them. We then append a header, a trailer and a
|
||||
// checksum so that the concatenated data is valid zlib-format data.
|
||||
//
|
||||
// zstd-compressed data can be merged in the same way.
|
||||
//
|
||||
// Using threads to compress data has a downside. Since the dictionary
|
||||
// is reset on boundaries of shards, compression ratio is sacrificed
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#define CHECK(fn) \
|
||||
do { \
|
||||
[[maybe_unused]] int r = (fn); \
|
||||
assert(r == Z_OK); \
|
||||
} while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
|
||||
static std::vector<std::string_view> split(std::string_view input) {
|
||||
std::vector<std::string_view> shards;
|
||||
|
||||
while (input.size() >= SHARD_SIZE) {
|
||||
shards.push_back(input.substr(0, SHARD_SIZE));
|
||||
input = input.substr(SHARD_SIZE);
|
||||
}
|
||||
if (!input.empty())
|
||||
shards.push_back(input);
|
||||
return shards;
|
||||
}
|
||||
|
||||
static std::vector<u8> zlib_compress(std::string_view input) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// 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 = (u8 *)input.data();
|
||||
|
||||
// Set an output buffer. deflateBound() returns an upper bound
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
|
||||
|
||||
// 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 = buf.size();
|
||||
strm.next_out = buf.data();
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three bit value indicating the start of an uncompressed data
|
||||
// block followed by four byte data 00 00 ff ff which indicates that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug that if that particular three
|
||||
// bit value happens to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = adlers[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 2; // +2 for header
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
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));
|
||||
buf.resize(sz);
|
||||
buf.shrink_to_fit();
|
||||
return buf;
|
||||
}
|
||||
|
||||
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]);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZstdCompressor::write_to(u8 *buf) {
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
+2
-2
@@ -54,14 +54,14 @@ dir=gentoo/$git_hash
|
||||
|
||||
mkdir -p "$dir"/success "$dir"/failure
|
||||
|
||||
$podman nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
|
||||
$podman chrt --idle 0 nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".mold "$dir"/success
|
||||
else
|
||||
mv "$dir"/"$filename".mold "$dir"/failure
|
||||
fi
|
||||
|
||||
$podman nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
|
||||
$podman chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".ld "$dir"/success
|
||||
else
|
||||
|
||||
+172
-76
@@ -1,17 +1,94 @@
|
||||
#include "common.h"
|
||||
// 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 {
|
||||
|
||||
std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
std::vector<Element> vec;
|
||||
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:
|
||||
//
|
||||
@@ -19,17 +96,14 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
vec.emplace_back(BRACKET);
|
||||
std::bitset<256> &bitset = vec.back().bitset;
|
||||
|
||||
bool negate = false;
|
||||
bool closed = 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);
|
||||
@@ -59,9 +133,9 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
bitset[i] = true;
|
||||
chars[i] = true;
|
||||
} else {
|
||||
bitset[(u8)pat[0]] = true;
|
||||
chars[pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
@@ -70,87 +144,109 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
bitset.flip();
|
||||
chars.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
vec.emplace_back(QUESTION);
|
||||
break;
|
||||
case '*':
|
||||
vec.emplace_back(STAR);
|
||||
break;
|
||||
case '\\':
|
||||
if (pat.empty())
|
||||
return {};
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.emplace_back(STRING);
|
||||
vec.back().str += pat[0];
|
||||
pat = pat.substr(1);
|
||||
break;
|
||||
default:
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.emplace_back(STRING);
|
||||
vec.back().str += c;
|
||||
chars[c] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {Glob{std::move(vec)}};
|
||||
vec.push_back({chars, false});
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
bool Glob::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
while (!elements.empty()) {
|
||||
Element &e = elements[0];
|
||||
elements = elements.subspan(1);
|
||||
void MultiGlob::compile() {
|
||||
if (patterns.empty())
|
||||
return;
|
||||
|
||||
switch (e.kind) {
|
||||
case STRING:
|
||||
if (!str.starts_with(e.str))
|
||||
return false;
|
||||
str = str.substr(e.str.size());
|
||||
break;
|
||||
case STAR:
|
||||
if (elements.empty())
|
||||
return true;
|
||||
ranges::stable_sort(patterns, ranges::greater(), &GlobPattern::value);
|
||||
|
||||
// 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)
|
||||
return false;
|
||||
if (do_match(str.substr(pos + elements[0].str.size()),
|
||||
elements.subspan(1)))
|
||||
return true;
|
||||
str = str.substr(pos + 1);
|
||||
}
|
||||
}
|
||||
std::vector<State> states;
|
||||
for (GlobPattern &p : patterns)
|
||||
append(states, p.states);
|
||||
i64 sz = states.size();
|
||||
|
||||
// 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;
|
||||
}
|
||||
start_states.resize(sz);
|
||||
for (i64 pos = 0; GlobPattern &p : patterns) {
|
||||
start_states[pos] = true;
|
||||
pos += p.states.size();
|
||||
}
|
||||
|
||||
return str.empty();
|
||||
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
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
+49
-352
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
#include "atomics.h"
|
||||
#include "integers.h"
|
||||
#include "bitvector.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@@ -64,199 +65,10 @@ namespace ranges = std::ranges;
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
inline char *output_tmpfile;
|
||||
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
extern std::string mold_git_hash;
|
||||
|
||||
std::string errno_string();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
|
||||
inline u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
//
|
||||
// Error output
|
||||
//
|
||||
|
||||
// Some C++ stdlibs don't support std::osyncstream even though
|
||||
// it's is in the C++20 standard. So we implement it ourselves.
|
||||
class SyncStream {
|
||||
public:
|
||||
SyncStream(std::ostream &out) : out(out) {}
|
||||
|
||||
~SyncStream() {
|
||||
emit();
|
||||
}
|
||||
|
||||
template <typename T> SyncStream &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void emit() {
|
||||
if (emitted)
|
||||
return;
|
||||
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << '\n';
|
||||
emitted = true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream &out;
|
||||
std::stringstream ss;
|
||||
bool emitted = false;
|
||||
static inline std::mutex mu;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class Out {
|
||||
public:
|
||||
Out(Context &ctx) {}
|
||||
|
||||
template <typename T> Out &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cout};
|
||||
};
|
||||
|
||||
static std::string_view fatal_mono = "mold: fatal: ";
|
||||
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
|
||||
static std::string_view error_mono = "mold: error: ";
|
||||
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
|
||||
static std::string_view warning_mono = "mold: warning: ";
|
||||
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
|
||||
|
||||
template <typename Context>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(Context &ctx) {
|
||||
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
|
||||
}
|
||||
|
||||
[[noreturn]] ~Fatal() {
|
||||
out.emit();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <typename T> Fatal &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cerr};
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class Error {
|
||||
public:
|
||||
Error(Context &ctx) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
} else {
|
||||
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> Error &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cerr};
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(Context &ctx) {
|
||||
if (ctx.arg.suppress_warnings)
|
||||
return;
|
||||
|
||||
out.emplace(std::cerr);
|
||||
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
} else {
|
||||
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> Warn &operator<<(T &&val) {
|
||||
if (out)
|
||||
*out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<SyncStream> out;
|
||||
};
|
||||
|
||||
//
|
||||
// Atomics
|
||||
//
|
||||
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// mimalloc.cc
|
||||
//
|
||||
|
||||
void set_mimalloc_options();
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
@@ -667,7 +479,7 @@ public:
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while (entries[end - 1].key)
|
||||
while (begin < end && entries[end - 1].key)
|
||||
vec.push_back(entries + --end);
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
@@ -737,58 +549,72 @@ private:
|
||||
};
|
||||
|
||||
//
|
||||
// glob.cc
|
||||
// aho-corasick.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;
|
||||
};
|
||||
|
||||
class AhoCorasick {
|
||||
public:
|
||||
static std::optional<Glob> compile(std::string_view pat);
|
||||
bool match(std::string_view str);
|
||||
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:
|
||||
Glob(std::vector<Element> &&vec) : elements(vec) {}
|
||||
static bool do_match(std::string_view str, std::span<Element> elements);
|
||||
struct TrieNode {
|
||||
TrieNode() { children.fill(-1); }
|
||||
i64 value = -1;
|
||||
i32 suffix_link = -1;
|
||||
std::array<i32, 256> children;
|
||||
};
|
||||
|
||||
std::vector<Element> elements;
|
||||
void fix_suffix_links(i64 idx);
|
||||
void fix_values();
|
||||
|
||||
std::vector<TrieNode> nodes;
|
||||
};
|
||||
|
||||
//
|
||||
// multi-glob.cc
|
||||
// glob.cc
|
||||
//
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return strings.empty(); }
|
||||
std::optional<i64> find(std::string_view str);
|
||||
bool empty() const { return patterns.empty(); }
|
||||
void compile();
|
||||
i64 find(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
i64 value = -1;
|
||||
TrieNode *suffix_link = nullptr;
|
||||
std::unique_ptr<TrieNode> children[256];
|
||||
struct State {
|
||||
std::bitset<256> incoming_edge;
|
||||
bool is_star = false;
|
||||
};
|
||||
|
||||
void compile();
|
||||
void fix_suffix_links(TrieNode &node);
|
||||
void fix_values();
|
||||
i64 find_aho_corasick(std::string_view str);
|
||||
struct GlobPattern {
|
||||
std::vector<State> states;
|
||||
i64 value = -1;
|
||||
};
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::unique_ptr<TrieNode> root;
|
||||
std::vector<std::pair<Glob, i64>> globs;
|
||||
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;
|
||||
bool prefix_match = false;
|
||||
|
||||
MultiGlob multi_glob;
|
||||
AhoCorasick aho_corasick;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -816,13 +642,6 @@ std::string get_self_path();
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name);
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name);
|
||||
|
||||
//
|
||||
// jbos.cc
|
||||
//
|
||||
|
||||
void acquire_global_lock();
|
||||
void release_global_lock();
|
||||
|
||||
//
|
||||
// crc32.cc
|
||||
//
|
||||
@@ -830,36 +649,6 @@ void release_global_lock();
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
@@ -884,96 +673,4 @@ private:
|
||||
std::string basedir;
|
||||
};
|
||||
|
||||
//
|
||||
// Memory-mapped file
|
||||
//
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
class MappedFile {
|
||||
public:
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
void close_fd();
|
||||
void reopen_fd(const std::string &path);
|
||||
|
||||
template <typename Context>
|
||||
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = name;
|
||||
mf->data = data + start;
|
||||
mf->size = size;
|
||||
mf->parent = this;
|
||||
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
return mf;
|
||||
}
|
||||
|
||||
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;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
|
||||
// For --dependency-file
|
||||
bool is_dependency = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE fd = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
int fd = -1;
|
||||
#endif
|
||||
};
|
||||
|
||||
MappedFile *open_file_impl(const std::string &path, std::string &error);
|
||||
|
||||
template <typename Context>
|
||||
MappedFile *open_file(Context &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
std::string error;
|
||||
MappedFile *mf = open_file_impl(path, error);
|
||||
if (!error.empty())
|
||||
Fatal(ctx) << error;
|
||||
|
||||
if (mf)
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
MappedFile *must_open_file(Context &ctx, std::string path) {
|
||||
MappedFile *mf = open_file(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
return mf;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,24 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
// Including mimalloc-new-delete.h overrides new/delete operators.
|
||||
// We need it only when we are using mimalloc as a dynamic library.
|
||||
#if MOLD_USE_SYSTEM_MIMALLOC
|
||||
# include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
// Silence mimalloc warning messages that users can just ignore.
|
||||
#if MOLD_USE_MIMALLOC
|
||||
# include <mimalloc.h>
|
||||
|
||||
namespace mold {
|
||||
void set_mimalloc_options() {
|
||||
mi_option_disable(mi_option_verbose);
|
||||
mi_option_disable(mi_option_show_errors);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
namespace mold {
|
||||
void set_mimalloc_options() {}
|
||||
}
|
||||
#endif
|
||||
@@ -1,186 +0,0 @@
|
||||
// This file implements the Aho-Corasick algorithm to match multiple
|
||||
// glob patterns to symbol strings as quickly as possible.
|
||||
//
|
||||
// Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// `*` is a wildcard that matches any substring. We sometimes have
|
||||
// hundreds of glob patterns and have to match them against millions
|
||||
// of symbol strings.
|
||||
//
|
||||
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
|
||||
// We handle such patterns with the Glob class. Glob is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be
|
||||
// OK.
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<i64> MultiGlob::find(std::string_view str) {
|
||||
std::call_once(once, [&] { compile(); });
|
||||
i64 val = -1;
|
||||
|
||||
// Match against simple glob patterns
|
||||
if (root)
|
||||
val = find_aho_corasick(str);
|
||||
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<Glob, i64> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
val = std::max(val, glob.second);
|
||||
|
||||
if (val == -1)
|
||||
return {};
|
||||
return val;
|
||||
}
|
||||
|
||||
i64 MultiGlob::find_aho_corasick(std::string_view str) {
|
||||
TrieNode *node = root.get();
|
||||
i64 val = -1;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (;;) {
|
||||
if (node->children[c]) {
|
||||
node = node->children[c].get();
|
||||
val = std::max(val, node->value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->suffix_link)
|
||||
return;
|
||||
node = node->suffix_link;
|
||||
}
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
|
||||
for (u8 c : str) {
|
||||
if (prefix_match && node == root.get())
|
||||
return val;
|
||||
walk(c);
|
||||
}
|
||||
|
||||
walk('\0');
|
||||
return val;
|
||||
}
|
||||
|
||||
static bool is_simple_pattern(std::string_view pat) {
|
||||
static std::regex re(R"(\*?[^*[?]+\*?)", std::regex_constants::optimize);
|
||||
return std::regex_match(pat.begin(), pat.end(), re);
|
||||
}
|
||||
|
||||
static std::string handle_stars(std::string_view pat) {
|
||||
std::string str(pat);
|
||||
|
||||
// Convert "foo" -> "\0foo\0", "*foo" -> "foo\0", "foo*" -> "\0foo"
|
||||
// and "*foo*" -> "foo". Aho-Corasick can do only substring matching,
|
||||
// so we use \0 as beginning/end-of-string markers.
|
||||
if (str.starts_with('*') && str.ends_with('*'))
|
||||
return str.substr(1, str.size() - 2);
|
||||
if (str.starts_with('*'))
|
||||
return str.substr(1) + "\0"s;
|
||||
if (str.ends_with('*'))
|
||||
return "\0"s + str.substr(0, str.size() - 1);
|
||||
return "\0"s + str + "\0"s;
|
||||
}
|
||||
|
||||
bool MultiGlob::add(std::string_view pat, i64 val) {
|
||||
assert(!is_compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
strings.emplace_back(pat);
|
||||
|
||||
// Complex glob pattern
|
||||
if (!is_simple_pattern(pat)) {
|
||||
if (std::optional<Glob> glob = Glob::compile(pat)) {
|
||||
globs.emplace_back(std::move(*glob), val);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simple glob pattern
|
||||
if (!root)
|
||||
root.reset(new TrieNode);
|
||||
TrieNode *node = root.get();
|
||||
|
||||
for (u8 c : handle_stars(pat)) {
|
||||
if (!node->children[c])
|
||||
node->children[c].reset(new TrieNode);
|
||||
node = node->children[c].get();
|
||||
}
|
||||
|
||||
node->value = std::max(node->value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiGlob::compile() {
|
||||
is_compiled = true;
|
||||
if (root) {
|
||||
fix_suffix_links(*root);
|
||||
fix_values();
|
||||
|
||||
// If no pattern starts with '*', set prefix_match to true.
|
||||
// We'll use this flag for optimization.
|
||||
prefix_match = true;
|
||||
for (i64 i = 1; i < 256; i++) {
|
||||
if (root->children[i]) {
|
||||
prefix_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiGlob::fix_suffix_links(TrieNode &node) {
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
if (!node.children[i])
|
||||
continue;
|
||||
|
||||
TrieNode &child = *node.children[i];
|
||||
|
||||
TrieNode *cur = node.suffix_link;
|
||||
for (;;) {
|
||||
if (!cur) {
|
||||
child.suffix_link = root.get();
|
||||
break;
|
||||
}
|
||||
|
||||
if (cur->children[i]) {
|
||||
child.suffix_link = cur->children[i].get();
|
||||
break;
|
||||
}
|
||||
|
||||
cur = cur->suffix_link;
|
||||
}
|
||||
|
||||
fix_suffix_links(child);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiGlob::fix_values() {
|
||||
std::queue<TrieNode *> queue;
|
||||
queue.push(root.get());
|
||||
|
||||
do {
|
||||
TrieNode *node = queue.front();
|
||||
queue.pop();
|
||||
|
||||
for (std::unique_ptr<TrieNode> &child : node->children) {
|
||||
if (!child)
|
||||
continue;
|
||||
child->value = std::max(child->value, child->suffix_link->value);
|
||||
queue.push(child.get());
|
||||
}
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// This file contains functions to create a tar file.
|
||||
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ftruncate _chsize_s
|
||||
|
||||
@@ -17,11 +17,11 @@ if(EXISTS "${SOURCE_DIR}/.git/HEAD")
|
||||
endif()
|
||||
|
||||
# Create new file contents and update a given file if necessary.
|
||||
set(NEW_CONTENTS "#include <string>
|
||||
namespace mold {
|
||||
std::string mold_git_hash = \"${HASH}\";
|
||||
}
|
||||
")
|
||||
if("${HASH}" STREQUAL "")
|
||||
set(NEW_CONTENTS "")
|
||||
else()
|
||||
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${OUTPUT_FILE}")
|
||||
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
|
||||
|
||||
-285
@@ -1,285 +0,0 @@
|
||||
// r31 is blink. r30 is I believe a scratch register.
|
||||
//
|
||||
// Programs might use one of several branch, jump, and link instructions
|
||||
// to control execution flow through direct and indirect function calls
|
||||
// and branching. For function calling, the conditional branch-and-link
|
||||
// instruction has a maximum branch range of ±1 MiB, and the target
|
||||
// address is 32-bit-aligned. The unconditional branch-and-link format has
|
||||
// a maximum branch range of ±16 MiB.
|
||||
//
|
||||
// https://github.com/foss-for-synopsys-dwc-arc-processors/arc-ABI-manual/blob/master/ARCv2_ABI.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = ARC;
|
||||
|
||||
// An integer-like class for the mixed-endian encoding
|
||||
class M32 {
|
||||
public:
|
||||
operator u32() const {
|
||||
return (buf[1] << 24) | (buf[0] << 16) | (buf[3] << 8) | buf[2];
|
||||
}
|
||||
|
||||
M32 &operator=(u32 x) {
|
||||
buf[0] = x >> 16;
|
||||
buf[1] = x >> 24;
|
||||
buf[2] = x;
|
||||
buf[3] = x >> 8;
|
||||
return *this;
|
||||
}
|
||||
|
||||
M32 &operator&=(u32 x) { return *this = *this & x; }
|
||||
M32 &operator|=(u32 x) { return *this = *this | x; }
|
||||
u8 buf[4];
|
||||
};
|
||||
|
||||
static void write_disp7u(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc |= (bits(val, 6, 3) << 4) | bits(val, 2, 0);
|
||||
}
|
||||
|
||||
static void write_disp9(u8 *loc, u32 val) {
|
||||
*(M32 *)loc |= bits(val, 8, 0);
|
||||
}
|
||||
|
||||
static void write_disp9ls(u8 *loc, u32 val) {
|
||||
*(M32 *)loc |= (bits(val, 7, 0) << 16) | (bit(val, 8) << 15);
|
||||
}
|
||||
|
||||
static void write_disp9s(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc |= bits(val, 8, 0);
|
||||
}
|
||||
|
||||
static void write_disp10u(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc |= bits(val, 9, 0);
|
||||
}
|
||||
|
||||
static void write_disp13s(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc |= bits(val, 12, 0);
|
||||
}
|
||||
|
||||
static void write_disp21h(u8 *loc, u32 val) {
|
||||
*(M32 *)loc |= (bits(val, 10, 1) << 17) | (bits(val, 20, 11) << 6);
|
||||
}
|
||||
|
||||
static void write_disp21w(u8 *loc, u32 val) {
|
||||
*(M32 *)loc |= (bits(val, 10, 2) << 18) | (bits(val, 20, 11) << 6);
|
||||
}
|
||||
|
||||
static void write_disp25h(u8 *loc, u32 val) {
|
||||
*(M32 *)loc |= (bits(val, 10, 1) << 17) | (bits(val, 20, 11) << 6) |
|
||||
bits(val, 24, 21);
|
||||
}
|
||||
|
||||
static void write_disp25w(u8 *loc, u32 val) {
|
||||
*(M32 *)loc |= (bits(val, 10, 2) << 18) | (bits(val, 20, 11) << 6) |
|
||||
bits(val, 24, 21);
|
||||
}
|
||||
|
||||
static void write_disps9(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc |= bits(val, 10, 2);
|
||||
}
|
||||
|
||||
static void write_disps12(u8 *loc, u32 val) {
|
||||
*(M32 *)loc |= (bits(val, 5, 0) << 6) | bits(val, 11, 6);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul16 insn[] = {
|
||||
0x2730, 0x7f8b, 0, 0, // ld r11, [pcl,0]
|
||||
0x2730, 0x7f8a, 0, 0, // ld r10, [pcl,0]
|
||||
0x2020, 0x0280, // j [r10]
|
||||
0, 0, // (address of GOTPLT)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(M32 *)(buf + 4) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
|
||||
*(M32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
|
||||
*(M32 *)(buf + 20) = ctx.gotplt->shdr.sh_addr;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul16 insn[] = {
|
||||
0x2730, 0x7f8c, 0, 0, // ld r12, [pcl,0]
|
||||
0x2021, 0x0300, // j.d [r12]
|
||||
0x240a, 0x1fc0, // mov r12,pcl
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(M32 *)(buf + 4) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul16 insn[] = {
|
||||
0x2730, 0x7f8c, 0, 0, // ld r12, [pcl,0]
|
||||
0x2021, 0x0300, // j.d [r12]
|
||||
0x240a, 0x1fc0, // mov r12,pcl
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(M32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx);
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_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_ARC_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_ARC_32_PCREL:
|
||||
*(ul32 *)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);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARC_32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARC_32_ME:
|
||||
*(M32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARC_S25H_PCREL:
|
||||
case R_ARC_S25H_PCREL_PLT:
|
||||
write_disp25h(loc, align_to(S + A - P, 2));
|
||||
break;
|
||||
case R_ARC_S25W_PCREL:
|
||||
case R_ARC_S25W_PCREL_PLT:
|
||||
write_disp25w(loc, align_to(S + A - P, 4));
|
||||
break;
|
||||
case R_ARC_PC32:
|
||||
*(M32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARC_32_PCREL:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARC_GOTPC32:
|
||||
*(M32 *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARC_TLS_LE_32:
|
||||
*(M32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARC_TLS_IE_GOT:
|
||||
*(M32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
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;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARC_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
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))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARC_32:
|
||||
break;
|
||||
case R_ARC_32_ME:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARC_S25H_PCREL:
|
||||
case R_ARC_S25W_PCREL:
|
||||
case R_ARC_32_PCREL:
|
||||
case R_ARC_PC32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARC_S25H_PCREL_PLT:
|
||||
case R_ARC_S25W_PCREL_PLT:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARC_GOTPC32:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARC_TLS_LE_32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARC_TLS_IE_GOT:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return 0x406;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+173
-103
@@ -31,18 +31,21 @@
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = ARM32;
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
ul32 *arm = (ul32 *)loc;
|
||||
ul16 *thm = (ul16 *)loc;
|
||||
U32<E> *arm = (U32<E> *)loc;
|
||||
U16<E> *thm = (U16<E> *)loc;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
@@ -59,7 +62,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
return (il32)*arm;
|
||||
return (I32<E>)*arm;
|
||||
case R_ARM_THM_JUMP8:
|
||||
return sign_extend(thm[0], 8) << 1;
|
||||
case R_ARM_THM_JUMP11:
|
||||
@@ -125,7 +128,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
static void write_arm_mov(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b21(u8 *loc, u32 val) {
|
||||
@@ -135,7 +138,7 @@ static void write_thm_b21(u8 *loc, u32 val) {
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
@@ -149,7 +152,7 @@ static void write_thm_b25(u8 *loc, u32 val) {
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
@@ -160,7 +163,7 @@ static void write_thm_mov(u8 *loc, u32 val) {
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
@@ -184,13 +187,13 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = val;
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xff00) | bits(val, 8, 1);
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
@@ -200,7 +203,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
@@ -209,7 +212,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
write_arm_mov(loc, val);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
@@ -236,7 +239,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
static constexpr ul32 plt_entry[] = {
|
||||
@@ -249,13 +252,13 @@ static constexpr ul32 plt_entry[] = {
|
||||
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;
|
||||
*(U32<E> *)(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_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -267,24 +270,16 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
*(ul32 *)loc = val;
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static Thunk<E> &get_reachable_thunk(OutputSection<E> &osec, u64 addr) {
|
||||
auto it = std::upper_bound(osec.thunks.begin(), osec.thunks.end(), addr,
|
||||
[](u64 addr, std::unique_ptr<Thunk<E>> &thunk) {
|
||||
return addr < thunk->get_addr();
|
||||
});
|
||||
return **it;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -312,7 +307,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
|
||||
|
||||
auto get_tlsdesc_trampoline_addr = [&] {
|
||||
return get_reachable_thunk(*output_section, P).get_addr();
|
||||
auto it = ranges::upper_bound(output_section->thunks, P, {},
|
||||
[](std::unique_ptr<Thunk<E>> &thunk) {
|
||||
return thunk->get_addr();
|
||||
});
|
||||
return (*it)->get_addr();
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -320,13 +319,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_TARGET1:
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -337,61 +336,61 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val2 = align_to(S + A - P, 4);
|
||||
|
||||
if (T && is_int(val1, 25)) {
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // BL
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, val1);
|
||||
} else if (!T && is_int(val2, 25)) {
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // BLX
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
|
||||
write_thm_b25(loc, val2);
|
||||
} else {
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // BL
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
*(U32<E> *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_ARM_GOTOFF32:
|
||||
*(ul32 *)loc = ((S + A) | T) - GOT;
|
||||
*(U32<E> *)loc = ((S + A) | T) - GOT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = GOT + G + A - P;
|
||||
*(U32<E> *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
*(U32<E> *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like THM_CALL, ARM_CALL relocation refers to 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);
|
||||
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (is_int(val, 26)) {
|
||||
if (T) {
|
||||
*(ul32 *)loc = 0xfa00'0000; // BLX
|
||||
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = 0xfa00'0000; // BLX
|
||||
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(val, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= 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
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -404,26 +403,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val = S + A - P;
|
||||
if (T || !is_int(val, 26))
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
check(S + A - P, -(1 << 8), 1 << 8);
|
||||
*(ul16 *)loc &= 0xff00;
|
||||
*(ul16 *)loc |= bits(S + A - P, 8, 1);
|
||||
*(U16<E> *)loc &= 0xff00;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc &= 0xf800;
|
||||
*(ul16 *)loc |= bits(S + A - P, 11, 1);
|
||||
*(U16<E> *)loc &= 0xf800;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
@@ -431,7 +430,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_ARM_THM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -454,8 +453,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc &= 0x8000'0000;
|
||||
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
*(U32<E> *)loc &= 0x8000'0000;
|
||||
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov(loc, (S + A) | T);
|
||||
@@ -473,19 +472,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_thm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
@@ -514,35 +513,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(ul32 *)loc = S - ctx.tp_addr;
|
||||
*(U32<E> *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = 0xeb00'0000; // bl 0
|
||||
*(ul32 *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // bl 0
|
||||
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
*(ul32 *)loc = 0xe320'f000; // nop
|
||||
*(U32<E> *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
|
||||
write_thm_b25(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
|
||||
// we use two instructions instead.
|
||||
*(ul16 *)loc = 0x4478; // add r0, pc
|
||||
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
*(U16<E> *)loc = 0x4478; // add r0, pc
|
||||
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
*(ul32 *)loc = 0x8000'f3af; // nop.w
|
||||
*(U32<E> *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -573,15 +572,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -703,7 +702,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
*(U32<E> *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
|
||||
buf += sizeof(entry);
|
||||
P += sizeof(entry);
|
||||
@@ -712,10 +711,14 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return EF_ARM_EABI_VER5;
|
||||
if constexpr (E::is_le)
|
||||
return EF_ARM_EABI_VER5;
|
||||
else
|
||||
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
|
||||
}
|
||||
|
||||
void create_arm_exidx_section(Context<E> &ctx) {
|
||||
template <>
|
||||
void create_arm_exidx_section<E>(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < ctx.chunks.size(); i++) {
|
||||
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
|
||||
|
||||
@@ -732,28 +735,20 @@ void create_arm_exidx_section(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::compute_section_size(Context<E> &ctx) {
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
output_section.compute_section_size(ctx);
|
||||
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::update_shdr(Context<E> &ctx) {
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// .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 (Chunk<E> *chunk = find_chunk(ctx, ".text"))
|
||||
this->shdr.sh_link = chunk->shndx;
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::remove_duplicate_entries(Context<E> &ctx) {
|
||||
this->shdr.sh_size = get_contents(ctx).size();
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::copy_buf(Context<E> &ctx) {
|
||||
std::vector<u8> contents = get_contents(ctx);
|
||||
assert(this->shdr.sh_size == contents.size());
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
// Returns the end of the text segment
|
||||
static u64 get_text_end(Context<E> &ctx) {
|
||||
u64 ret = 0;
|
||||
@@ -772,7 +767,8 @@ static u64 get_text_end(Context<E> &ctx) {
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function returns contents of .ARM.exidx.
|
||||
std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
template <>
|
||||
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
|
||||
// .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
|
||||
@@ -788,8 +784,8 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
const u32 CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
ul32 addr;
|
||||
ul32 val;
|
||||
U32<E> addr;
|
||||
U32<E> val;
|
||||
};
|
||||
|
||||
// We reserve one extra slot for the sentinel
|
||||
@@ -820,19 +816,13 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
std::sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
|
||||
|
||||
// Remove duplicate adjacent entries. That is, if two adjacent functions
|
||||
// have the same compact unwind info or are both CANTUNWIND, we can
|
||||
// merge them into a single address range.
|
||||
auto it = std::unique(ent, ent + num_entries,
|
||||
[](const Entry &a, const Entry &b) {
|
||||
return a.val == b.val;
|
||||
});
|
||||
|
||||
num_entries = it - ent;
|
||||
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
|
||||
num_entries -= tail.size();
|
||||
buf.resize(num_entries * sizeof(Entry));
|
||||
|
||||
// Make addresses relative to themselves.
|
||||
@@ -846,4 +836,84 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
|
||||
this->shdr.sh_size = get_contents(ctx).size();
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
|
||||
std::vector<u8> contents = get_contents(ctx);
|
||||
assert(this->shdr.sh_size == contents.size());
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
#if MOLD_ARM32BE
|
||||
// Even though using ARM32 in big-endian mode is very rare, the processor
|
||||
// technically supports both little- and big-endian modes. There are two
|
||||
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
|
||||
// data are encoded in big-endian. In BE8, instructions are encoded in
|
||||
// little-endian, and only data is in big-endian. BE8 is the de facto
|
||||
// standard for ARMv6 or later. We support only BE8.
|
||||
//
|
||||
// A tricky thing is that instructions in an object file are always
|
||||
// big-endian if the file is compiled for big-endian mode. In other words,
|
||||
// the compiler always emit code in BE32 if -mbig-endian is specified. It
|
||||
// is the linker's responsibility to rewrite instructions from big-endian
|
||||
// to little-endian for an BE8 output. This function does that.
|
||||
//
|
||||
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
|
||||
// Thumb instructions, and data. We need to distinguish them to swap 4
|
||||
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
|
||||
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
|
||||
// and $d, respectively. We use mapping symbols to determine what to do
|
||||
// with the text section.
|
||||
//
|
||||
// This function is called after we copy the input section contents to the
|
||||
// output file. We rewrite instructions in the output buffer in place.
|
||||
void arm32be_swap_bytes(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
// Collect mapping symbols
|
||||
std::vector<Symbol<E> *> syms;
|
||||
|
||||
for (Symbol<E> *sym : file->get_local_syms())
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
if (std::string_view x = sym->name();
|
||||
x == "$a" || x.starts_with("$a.") ||
|
||||
x == "$t" || x.starts_with("$t.") ||
|
||||
x == "$d" || x.starts_with("$d."))
|
||||
syms.push_back(sym);
|
||||
|
||||
// Group mapping symbols by input section and sort by address
|
||||
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
|
||||
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
|
||||
});
|
||||
|
||||
// Swap bytes
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
Symbol<E> &sym = *syms[i];
|
||||
if (sym.name().starts_with("$d"))
|
||||
continue;
|
||||
|
||||
InputSection<E> &isec = *sym.get_input_section();
|
||||
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
|
||||
u8 *start = base + sym.value;
|
||||
|
||||
u8 *end;
|
||||
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
|
||||
end = base + syms[i + 1]->value;
|
||||
else
|
||||
end = base + isec.sh_size;
|
||||
|
||||
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
|
||||
for (u8 *p = start; p < end; p += sz)
|
||||
std::reverse(p, p + sz);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -66,7 +66,7 @@ static u64 higher(u64 val, u64 pc) {
|
||||
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
|
||||
//
|
||||
// Compensating all the sign-extensions is a bit complicated. The
|
||||
// psABI gave the following formula.
|
||||
// psABI gives the following formula.
|
||||
val = val + 0x8000'0000 + ((val & 0x800) ? (0x1000 - 0x1'0000'0000) : 0);
|
||||
return page(val) - page(pc - 8);
|
||||
}
|
||||
|
||||
+5
-12
@@ -484,17 +484,12 @@ static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
|
||||
}
|
||||
|
||||
static ElfRel<E> *
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
|
||||
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
|
||||
[](const ElfRel<E> &r, i64 offset) {
|
||||
return r.r_offset < offset;
|
||||
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
|
||||
return (u64)r.r_offset;
|
||||
});
|
||||
|
||||
if (it == rels.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
if (it == rels.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
@@ -509,9 +504,7 @@ struct OpdSymbol {
|
||||
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())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
if (it == syms.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return it->sym;
|
||||
}
|
||||
|
||||
+6
-12
@@ -303,8 +303,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
assert(E::is_64);
|
||||
*(U32<E> *)loc = S + A;
|
||||
if (E::is_64)
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
break;
|
||||
@@ -321,12 +321,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val = S + A - P;
|
||||
i64 rd = get_rd(buf + rel.r_offset + 4);
|
||||
|
||||
// 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.
|
||||
if (sym.esym().is_undef_weak())
|
||||
val = 0;
|
||||
|
||||
if (removed_bytes == 4) {
|
||||
// auipc + jalr -> jal
|
||||
*(ul32 *)loc = (rd << 7) | 0b1101111;
|
||||
@@ -473,7 +467,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
case R_RISCV_TLSDESC_HI20:
|
||||
// RISC-V TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
// a TP-relative address in a0.
|
||||
//
|
||||
// .L0:
|
||||
// auipc tX, 0
|
||||
@@ -773,9 +767,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
@@ -785,6 +776,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
case R_RISCV_TLSDESC_ADD_LO12:
|
||||
case R_RISCV_TLSDESC_CALL:
|
||||
|
||||
@@ -23,12 +23,11 @@
|
||||
// see the contents of libc.a by running `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
namespace {
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
@@ -74,10 +73,11 @@ struct ArHdr {
|
||||
return {ar_name, sizeof(ar_name)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile *> vec;
|
||||
@@ -123,8 +123,9 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile *> vec;
|
||||
@@ -161,13 +162,27 @@ std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf)
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
|
||||
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"))
|
||||
return read_fat_archive_members(ctx, mf);
|
||||
|
||||
assert(str.starts_with("!<thin>\n"));
|
||||
return read_thin_archive_members(ctx, mf);
|
||||
}
|
||||
|
||||
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
|
||||
+30
-18
@@ -1,3 +1,4 @@
|
||||
#include "config.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <filesystem>
|
||||
@@ -587,6 +588,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
bool warn_shared_textrel = false;
|
||||
bool error_unresolved_symbols = true;
|
||||
std::optional<SeparateCodeKind> z_separate_code;
|
||||
std::optional<bool> allow_shlib_undefined;
|
||||
std::optional<bool> report_undefined;
|
||||
std::optional<bool> z_relro;
|
||||
std::optional<bool> z_dynamic_undefined_weak;
|
||||
@@ -705,21 +707,20 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("no-dynamic-linker")) {
|
||||
ctx.arg.dynamic_linker = "";
|
||||
} else if (read_flag("v")) {
|
||||
Out(ctx) << get_mold_version();
|
||||
Out(ctx) << mold_version;
|
||||
version_shown = true;
|
||||
} else if (read_flag("version")) {
|
||||
Out(ctx) << get_mold_version();
|
||||
Out(ctx) << mold_version;
|
||||
exit(0);
|
||||
} else if (read_flag("V")) {
|
||||
Out(ctx) << get_mold_version()
|
||||
Out(ctx) << mold_version
|
||||
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
|
||||
<< " aarch64elf\n aarch64linux\n aarch64elfb\n"
|
||||
<< " aarch64linuxb\n armelf_linux_eabi\n elf64lriscv\n"
|
||||
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
|
||||
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
|
||||
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
|
||||
<< " shelf_linux\n elf64loongarch\n elf32loongarch\n"
|
||||
<< " arclinux";
|
||||
<< " shelf_linux\n elf64loongarch\n elf32loongarch";
|
||||
version_shown = true;
|
||||
} else if (read_arg("mllvm")) {
|
||||
ctx.arg.plugin_opt.emplace_back(arg);
|
||||
@@ -733,7 +734,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (arg == "aarch64elfb" || arg == "aarch64linuxb") {
|
||||
ctx.arg.emulation = ARM64BE::name;
|
||||
} else if (arg == "armelf_linux_eabi") {
|
||||
ctx.arg.emulation = ARM32::name;
|
||||
ctx.arg.emulation = ARM32LE::name;
|
||||
} else if (arg == "armelfb_linux_eabi") {
|
||||
ctx.arg.emulation = ARM32BE::name;
|
||||
} else if (arg == "elf64lriscv") {
|
||||
ctx.arg.emulation = RV64LE::name;
|
||||
} else if (arg == "elf64briscv") {
|
||||
@@ -762,8 +765,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.emulation = LOONGARCH64::name;
|
||||
} else if (arg == "elf32loongarch") {
|
||||
ctx.arg.emulation = LOONGARCH32::name;
|
||||
} else if (arg == "arclinux") {
|
||||
ctx.arg.emulation = ARC::name;
|
||||
} else {
|
||||
Fatal(ctx) << "unknown -m argument: " << arg;
|
||||
}
|
||||
@@ -858,10 +859,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("sysroot")) {
|
||||
ctx.arg.sysroot = arg;
|
||||
} else if (read_arg("unique")) {
|
||||
std::optional<Glob> pat = Glob::compile(arg);
|
||||
if (!pat)
|
||||
if (!ctx.arg.unique.add(arg, 1))
|
||||
Fatal(ctx) << "-unique: invalid glob pattern: " << arg;
|
||||
ctx.arg.unique = std::move(*pat);
|
||||
} else if (read_arg("unresolved-symbols")) {
|
||||
if (arg == "report-all" || arg == "ignore-in-shared-libs")
|
||||
report_undefined = true;
|
||||
@@ -981,11 +980,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.execute_only = true;
|
||||
} else if (read_arg("compress-debug-sections")) {
|
||||
if (arg == "zlib" || arg == "zlib-gabi")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_ZLIB;
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
|
||||
else if (arg == "zstd")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_ZSTD;
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
|
||||
else if (arg == "none")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_NONE;
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_NONE;
|
||||
else
|
||||
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
|
||||
} else if (read_arg("wrap")) {
|
||||
@@ -1294,6 +1293,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
}
|
||||
} else if (read_flag("no-build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::NONE;
|
||||
} else if (read_flag("be8")) {
|
||||
ctx.arg.be8 = true;
|
||||
} else if (read_flag("be32")) {
|
||||
ctx.arg.be8 = false;
|
||||
} else if (read_arg("format") || read_arg("b")) {
|
||||
if (arg == "binary")
|
||||
Fatal(ctx)
|
||||
@@ -1306,9 +1309,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("filter") || read_arg("F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag("allow-shlib-undefined")) {
|
||||
ctx.arg.allow_shlib_undefined = true;
|
||||
allow_shlib_undefined = true;
|
||||
} else if (read_flag("no-allow-shlib-undefined")) {
|
||||
ctx.arg.allow_shlib_undefined = false;
|
||||
allow_shlib_undefined = false;
|
||||
} else if (read_arg("O")) {
|
||||
} else if (read_flag("EB")) {
|
||||
} else if (read_flag("EL")) {
|
||||
@@ -1415,6 +1418,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
if (allow_shlib_undefined)
|
||||
ctx.arg.allow_shlib_undefined = *allow_shlib_undefined;
|
||||
else
|
||||
ctx.arg.allow_shlib_undefined = ctx.arg.shared;
|
||||
|
||||
if (!report_undefined)
|
||||
report_undefined = !ctx.arg.shared;
|
||||
|
||||
@@ -1435,6 +1443,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.relocatable)
|
||||
ctx.arg.static_ = true;
|
||||
|
||||
if (ctx.arg.static_)
|
||||
ctx.arg.dynamic_linker = "";
|
||||
|
||||
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_SHUFFLE) {
|
||||
if (shuffle_sections_seed)
|
||||
ctx.arg.shuffle_sections_seed = *shuffle_sections_seed;
|
||||
@@ -1488,6 +1499,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.image_base % ctx.page_size)
|
||||
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
|
||||
|
||||
if (ctx.arg.emulation == ARM32BE::name && !ctx.arg.be8)
|
||||
Fatal(ctx) << "--be32 is not supported";
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
|
||||
@@ -1498,9 +1512,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::string ver = ctx.arg.soname;
|
||||
if (ver.empty())
|
||||
ver = path_filename(ctx.arg.output);
|
||||
|
||||
ctx.arg.version_definitions.push_back(ver);
|
||||
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
|
||||
}
|
||||
|
||||
for (std::string_view path : version_scripts) {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// This file implements multi-threaded compression routines. We simply
|
||||
// create multiple compressed data blocks and concatenate them together.
|
||||
// Tools supporting compressed sections generally read compressed sections
|
||||
// until the section end to handle multiple concatenated streams.
|
||||
//
|
||||
// Using threads to compress data has a downside. Since the dictionary
|
||||
// is not shared between compressed streams, the compression ratio is
|
||||
// slightly sacrificed. However, if the stream size is large enough,
|
||||
// that loss is negligible in practice.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
Compressor::~Compressor() {
|
||||
for (std::span<u8> shard : shards)
|
||||
delete[] shard.data();
|
||||
}
|
||||
|
||||
static std::vector<std::span<u8>> split(std::span<u8> input) {
|
||||
constexpr int BLOCK_SIZE = 1024 * 1024;
|
||||
std::vector<std::span<u8>> vec;
|
||||
while (!input.empty()) {
|
||||
i64 sz = std::min<i64>(BLOCK_SIZE, input.size());
|
||||
vec.push_back(input.subspan(0, sz));
|
||||
input = input.subspan(sz);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::span<u8> zlib_compress(std::span<u8> input) {
|
||||
// compression level; must be between 0 to 9
|
||||
int level = Z_BEST_SPEED; // = 1
|
||||
|
||||
unsigned long bufsize = compressBound(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
[[maybe_unused]] int r = compress2(buf, &bufsize, input.data(),
|
||||
input.size(), level);
|
||||
assert(r == Z_OK);
|
||||
return {buf, (size_t)bufsize};
|
||||
}
|
||||
|
||||
static std::span<u8> zstd_compress(std::span<u8> input) {
|
||||
// compression level; must be between 1 to 22
|
||||
int level = ZSTD_CLEVEL_DEFAULT; // = 3
|
||||
|
||||
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
return {buf, sz};
|
||||
}
|
||||
|
||||
Compressor::Compressor(i64 format, u8 *buf, i64 size) {
|
||||
assert(format == ELFCOMPRESS_ZLIB || format == ELFCOMPRESS_ZSTD);
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
if (format == ELFCOMPRESS_ZLIB)
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
else
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::span<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void Compressor::write_to(u8 *buf) {
|
||||
std::vector<i64> offsets(shards.size());
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,12 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string get_mold_version() {
|
||||
if (mold_git_hash.empty())
|
||||
return "mold "s + MOLD_VERSION + " (compatible with GNU ld)";
|
||||
return "mold "s + MOLD_VERSION + " (" + mold_git_hash +
|
||||
"; compatible with GNU ld)";
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+10
-73
@@ -1,11 +1,12 @@
|
||||
#include "elf.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static std::string unknown_type(u32 r_type) {
|
||||
char buf[50];
|
||||
snprintf(buf, sizeof(buf), "unknown (0x%x)", r_type);
|
||||
return buf;
|
||||
std::stringstream ss;
|
||||
ss << "unknown (0x" << std::hex << r_type << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#define CASE(x) case x: return #x
|
||||
@@ -232,7 +233,7 @@ std::string rel_to_string<ARM64BE>(u32 r_type) {
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM32>(u32 r_type) {
|
||||
std::string rel_to_string<ARM32LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
CASE(R_ARM_NONE);
|
||||
CASE(R_ARM_PC24);
|
||||
@@ -373,6 +374,11 @@ std::string rel_to_string<ARM32>(u32 r_type) {
|
||||
return unknown_type(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM32BE>(u32 r_type) {
|
||||
return rel_to_string<ARM32LE>(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<RV64LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
@@ -1038,73 +1044,4 @@ std::string rel_to_string<LOONGARCH32>(u32 r_type) {
|
||||
return rel_to_string<LOONGARCH64>(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARC>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
CASE(R_ARC_8);
|
||||
CASE(R_ARC_16);
|
||||
CASE(R_ARC_24);
|
||||
CASE(R_ARC_32);
|
||||
CASE(R_ARC_N8);
|
||||
CASE(R_ARC_N16);
|
||||
CASE(R_ARC_N24);
|
||||
CASE(R_ARC_N32);
|
||||
CASE(R_ARC_SDA);
|
||||
CASE(R_ARC_SECTOFF);
|
||||
CASE(R_ARC_S21H_PCREL);
|
||||
CASE(R_ARC_S21W_PCREL);
|
||||
CASE(R_ARC_S25H_PCREL);
|
||||
CASE(R_ARC_S25W_PCREL);
|
||||
CASE(R_ARC_SDA32);
|
||||
CASE(R_ARC_SDA_LDST);
|
||||
CASE(R_ARC_SDA_LDST1);
|
||||
CASE(R_ARC_SDA_LDST2);
|
||||
CASE(R_ARC_SDA16_LD);
|
||||
CASE(R_ARC_SDA16_LD1);
|
||||
CASE(R_ARC_SDA16_LD2);
|
||||
CASE(R_ARC_S13_PCREL);
|
||||
CASE(R_ARC_W);
|
||||
CASE(R_ARC_32_ME);
|
||||
CASE(R_ARC_32_ME_S);
|
||||
CASE(R_ARC_N32_ME);
|
||||
CASE(R_ARC_SECTOFF_ME);
|
||||
CASE(R_ARC_SDA32_ME);
|
||||
CASE(R_ARC_W_ME);
|
||||
CASE(R_ARC_SECTOFF_ME_1);
|
||||
CASE(R_ARC_SECTOFF_ME_2);
|
||||
CASE(R_ARC_SECTOFF_1);
|
||||
CASE(R_ARC_SECTOFF_2);
|
||||
CASE(R_ARC_SDA_12);
|
||||
CASE(R_ARC_SDA16_ST2);
|
||||
CASE(R_ARC_32_PCREL);
|
||||
CASE(R_ARC_PC32);
|
||||
CASE(R_ARC_GOT32);
|
||||
CASE(R_ARC_GOTPC32);
|
||||
CASE(R_ARC_PLT32);
|
||||
CASE(R_ARC_COPY);
|
||||
CASE(R_ARC_GLOB_DAT);
|
||||
CASE(R_ARC_JMP_SLOT);
|
||||
CASE(R_ARC_RELATIVE);
|
||||
CASE(R_ARC_GOTOFF);
|
||||
CASE(R_ARC_GOTPC);
|
||||
CASE(R_ARC_S21W_PCREL_PLT);
|
||||
CASE(R_ARC_S25H_PCREL_PLT);
|
||||
CASE(R_ARC_JLI_SECTOFF);
|
||||
CASE(R_ARC_TLS_DTPMOD);
|
||||
CASE(R_ARC_TLS_TPOFF);
|
||||
CASE(R_ARC_TLS_GD_GOT);
|
||||
CASE(R_ARC_TLS_GD_LD);
|
||||
CASE(R_ARC_TLS_GD_CALL);
|
||||
CASE(R_ARC_TLS_IE_GOT);
|
||||
CASE(R_ARC_TLS_DTPOFF);
|
||||
CASE(R_ARC_TLS_DTPOFF_S9);
|
||||
CASE(R_ARC_TLS_LE_S9);
|
||||
CASE(R_ARC_TLS_LE_32);
|
||||
CASE(R_ARC_S25W_PCREL_PLT);
|
||||
CASE(R_ARC_S21H_PCREL_PLT);
|
||||
CASE(R_ARC_NPS_CMEM16);
|
||||
}
|
||||
return unknown_type(r_type);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -13,7 +13,8 @@ struct X86_64;
|
||||
struct I386;
|
||||
struct ARM64LE;
|
||||
struct ARM64BE;
|
||||
struct ARM32;
|
||||
struct ARM32LE;
|
||||
struct ARM32BE;
|
||||
struct RV64LE;
|
||||
struct RV64BE;
|
||||
struct RV32LE;
|
||||
@@ -28,7 +29,6 @@ struct SH4LE;
|
||||
struct SH4BE;
|
||||
struct LOONGARCH64;
|
||||
struct LOONGARCH32;
|
||||
struct ARC;
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
@@ -89,7 +89,6 @@ enum : u32 {
|
||||
SHT_HIOS = 0x6fffffff,
|
||||
SHT_X86_64_UNWIND = 0x70000001,
|
||||
SHT_ARM_EXIDX = 0x70000001,
|
||||
SHT_ARC_ATTRIBUTES = 0x70000001,
|
||||
SHT_ARM_ATTRIBUTES = 0x70000003,
|
||||
SHT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
SHT_LOUSER = 0x80000000,
|
||||
@@ -237,7 +236,6 @@ enum : u32 {
|
||||
EM_SPARC64 = 43,
|
||||
EM_X86_64 = 62,
|
||||
EM_AARCH64 = 183,
|
||||
EM_ARC_COMPACT2 = 195,
|
||||
EM_RISCV = 243,
|
||||
EM_LOONGARCH = 258,
|
||||
};
|
||||
@@ -355,14 +353,16 @@ enum : u32 {
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
ELFCOMPRESS_NONE = 0,
|
||||
ELFCOMPRESS_ZLIB = 1,
|
||||
ELFCOMPRESS_ZSTD = 2,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
EF_ARM_ABI_FLOAT_SOFT = 0x00000200,
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400,
|
||||
EF_ARM_EABI_VER5 = 0x05000000,
|
||||
EF_ARM_ABI_FLOAT_SOFT = 0x200,
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x400,
|
||||
EF_ARM_BE8 = 0x800000,
|
||||
EF_ARM_EABI_VER5 = 0x5000000,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -1365,71 +1365,6 @@ enum : u32 {
|
||||
R_LARCH_TLS_DESC_PCREL20_S2 = 126,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
R_ARC_8 = 1,
|
||||
R_ARC_16 = 2,
|
||||
R_ARC_24 = 3,
|
||||
R_ARC_32 = 4,
|
||||
R_ARC_N8 = 8,
|
||||
R_ARC_N16 = 9,
|
||||
R_ARC_N24 = 10,
|
||||
R_ARC_N32 = 11,
|
||||
R_ARC_SDA = 12,
|
||||
R_ARC_SECTOFF = 13,
|
||||
R_ARC_S21H_PCREL = 14,
|
||||
R_ARC_S21W_PCREL = 15,
|
||||
R_ARC_S25H_PCREL = 16,
|
||||
R_ARC_S25W_PCREL = 17,
|
||||
R_ARC_SDA32 = 18,
|
||||
R_ARC_SDA_LDST = 19,
|
||||
R_ARC_SDA_LDST1 = 20,
|
||||
R_ARC_SDA_LDST2 = 21,
|
||||
R_ARC_SDA16_LD = 22,
|
||||
R_ARC_SDA16_LD1 = 23,
|
||||
R_ARC_SDA16_LD2 = 24,
|
||||
R_ARC_S13_PCREL = 25,
|
||||
R_ARC_W = 26,
|
||||
R_ARC_32_ME = 27,
|
||||
R_ARC_32_ME_S = 105,
|
||||
R_ARC_N32_ME = 28,
|
||||
R_ARC_SECTOFF_ME = 29,
|
||||
R_ARC_SDA32_ME = 30,
|
||||
R_ARC_W_ME = 31,
|
||||
R_ARC_SECTOFF_ME_1 = 41,
|
||||
R_ARC_SECTOFF_ME_2 = 42,
|
||||
R_ARC_SECTOFF_1 = 43,
|
||||
R_ARC_SECTOFF_2 = 44,
|
||||
R_ARC_SDA_12 = 45,
|
||||
R_ARC_SDA16_ST2 = 48,
|
||||
R_ARC_32_PCREL = 49,
|
||||
R_ARC_PC32 = 50,
|
||||
R_ARC_GOT32 = 59,
|
||||
R_ARC_GOTPC32 = 51,
|
||||
R_ARC_PLT32 = 52,
|
||||
R_ARC_COPY = 53,
|
||||
R_ARC_GLOB_DAT = 54,
|
||||
R_ARC_JMP_SLOT = 55,
|
||||
R_ARC_RELATIVE = 56,
|
||||
R_ARC_GOTOFF = 57,
|
||||
R_ARC_GOTPC = 58,
|
||||
R_ARC_S21W_PCREL_PLT = 60,
|
||||
R_ARC_S25H_PCREL_PLT = 61,
|
||||
R_ARC_JLI_SECTOFF = 63,
|
||||
R_ARC_TLS_DTPMOD = 66,
|
||||
R_ARC_TLS_TPOFF = 68,
|
||||
R_ARC_TLS_GD_GOT = 69,
|
||||
R_ARC_TLS_GD_LD = 70,
|
||||
R_ARC_TLS_GD_CALL = 71,
|
||||
R_ARC_TLS_IE_GOT = 72,
|
||||
R_ARC_TLS_DTPOFF = 67,
|
||||
R_ARC_TLS_DTPOFF_S9 = 73,
|
||||
R_ARC_TLS_LE_S9 = 74,
|
||||
R_ARC_TLS_LE_32 = 75,
|
||||
R_ARC_S25W_PCREL_PLT = 76,
|
||||
R_ARC_S21H_PCREL_PLT = 77,
|
||||
R_ARC_NPS_CMEM16 = 78,
|
||||
};
|
||||
|
||||
// Returns true if a given relocation is of type used for direct
|
||||
// function call.
|
||||
template <typename E>
|
||||
@@ -1878,7 +1813,8 @@ template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
|
||||
template <typename E> concept is_i386 = std::same_as<E, I386>;
|
||||
template <typename E> concept is_arm64le = std::same_as<E, ARM64LE>;
|
||||
template <typename E> concept is_arm64be = std::same_as<E, ARM64BE>;
|
||||
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
|
||||
template <typename E> concept is_arm32le = std::same_as<E, ARM32LE>;
|
||||
template <typename E> concept is_arm32be = std::same_as<E, ARM32BE>;
|
||||
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
|
||||
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
|
||||
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
|
||||
@@ -1893,9 +1829,9 @@ template <typename E> concept is_sh4le = std::same_as<E, SH4LE>;
|
||||
template <typename E> concept is_sh4be = std::same_as<E, SH4BE>;
|
||||
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
|
||||
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
|
||||
template <typename E> concept is_arc = std::same_as<E, ARC>;
|
||||
|
||||
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> concept is_arm32 = is_arm32le<E> || is_arm32be<E>;
|
||||
template <typename E> concept is_arm64 = is_arm64le<E> || is_arm64be<E>;
|
||||
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
|
||||
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
|
||||
@@ -1989,7 +1925,7 @@ struct ARM64BE : ARM64LE {
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct ARM32 {
|
||||
struct ARM32LE {
|
||||
static constexpr std::string_view name = "arm32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
@@ -2019,6 +1955,11 @@ struct ARM32 {
|
||||
};
|
||||
};
|
||||
|
||||
struct ARM32BE : ARM32LE {
|
||||
static constexpr std::string_view name = "arm32be";
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct RV64LE {
|
||||
static constexpr std::string_view name = "riscv64";
|
||||
static constexpr bool is_64 = true;
|
||||
@@ -2297,27 +2238,4 @@ struct LOONGARCH32 {
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26, R_LARCH_CALL36 };
|
||||
};
|
||||
|
||||
struct ARC {
|
||||
static constexpr std::string_view name = "arc";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 8192;
|
||||
static constexpr u32 e_machine = EM_ARC_COMPACT2;
|
||||
static constexpr u32 plt_hdr_size = 24;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0, 0, 0, 0 };
|
||||
|
||||
static constexpr u32 R_COPY = R_ARC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARC_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_ARC_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ARC_32;
|
||||
static constexpr u32 R_RELATIVE = R_ARC_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_ARC_TLS_DTPOFF;
|
||||
static constexpr u32 R_TPOFF = R_ARC_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_ARC_TLS_DTPMOD;
|
||||
static constexpr u32 R_FUNCALL[] = { R_ARC_S25W_PCREL_PLT };
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "config.h"
|
||||
#include "mold.h"
|
||||
#include "mold-git-hash.h"
|
||||
|
||||
#if MOLD_USE_MIMALLOC
|
||||
# include <mimalloc.h>
|
||||
#endif
|
||||
|
||||
#if MOLD_USE_SYSTEM_MIMALLOC
|
||||
// Including mimalloc-new-delete.h overrides the new/delete operators.
|
||||
// We need it only when using mimalloc as a dynamic library.
|
||||
// This header should be included in only one source file, so we do
|
||||
// it in this file.
|
||||
# include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
std::string mold::mold_version =
|
||||
#ifdef MOLD_GIT_HASH
|
||||
"mold " MOLD_VERSION " (" MOLD_GIT_HASH "; compatible with GNU ld)";
|
||||
#else
|
||||
"mold " MOLD_VERSION " (compatible with GNU ld)";
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#if MOLD_USE_MIMALLOC
|
||||
// Silence mimalloc warnings that users can ignore
|
||||
mi_option_disable(mi_option_verbose);
|
||||
mi_option_disable(mi_option_show_errors);
|
||||
#endif
|
||||
|
||||
return mold::mold_main<mold::X86_64>(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
static std::string_view fatal_mono = "mold: fatal: ";
|
||||
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
|
||||
static std::string_view error_mono = "mold: error: ";
|
||||
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
|
||||
static std::string_view warning_mono = "mold: warning: ";
|
||||
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
|
||||
|
||||
template <typename E>
|
||||
Fatal<E>::Fatal(Context<E> &ctx) {
|
||||
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
[[noreturn]] Fatal<E>::~Fatal() {
|
||||
out.emit();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Error<E>::Error(Context<E> &ctx) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
} else {
|
||||
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Warn<E>::Warn(Context<E> &ctx) {
|
||||
if (ctx.arg.suppress_warnings)
|
||||
return;
|
||||
|
||||
out.emplace(std::cerr);
|
||||
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
} else {
|
||||
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template class Fatal<E>;
|
||||
template class Error<E>;
|
||||
template class Warn<E>;
|
||||
|
||||
} // namespace mold
|
||||
+1
-4
@@ -1,5 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "../lib/archive-file.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -139,7 +138,7 @@ static std::string_view get_elf_type(u8 *buf) {
|
||||
case EM_X86_64:
|
||||
return X86_64::name;
|
||||
case EM_ARM:
|
||||
return ARM32::name;
|
||||
return is_le ? ARM32LE::name : ARM32BE::name;
|
||||
case EM_AARCH64:
|
||||
return is_le ? ARM64LE::name : ARM64BE::name;
|
||||
case EM_RISCV:
|
||||
@@ -160,8 +159,6 @@ static std::string_view get_elf_type(u8 *buf) {
|
||||
return is_le ? SH4LE::name : SH4BE::name;
|
||||
case EM_LOONGARCH:
|
||||
return is_64 ? LOONGARCH64::name : LOONGARCH32::name;
|
||||
case EM_ARC_COMPACT2:
|
||||
return ARC::name;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
+4
-2
@@ -629,8 +629,10 @@ static std::vector<Compunit> read_compunits(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (chunk->is_compressed)
|
||||
return chunk->uncompressed_data;
|
||||
if (chunk->is_compressed) {
|
||||
CompressedSection<E> &sec = *(CompressedSection<E> *)chunk;
|
||||
return {sec.uncompressed_data.get(), (size_t)sec.chdr.ch_size};
|
||||
}
|
||||
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
|
||||
}
|
||||
|
||||
|
||||
+61
-23
@@ -112,7 +112,7 @@ std::string_view InputFile<E>::get_source_name() const {
|
||||
|
||||
template <typename E>
|
||||
static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
|
||||
return !(shdr.sh_flags & SHF_ALLOC) && name.starts_with(".debug");
|
||||
return !(shdr.sh_flags & SHF_ALLOC) && name.starts_with(".debug_");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -317,10 +317,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (is_arc<E>)
|
||||
if (shdr.sh_type == SHT_ARC_ATTRIBUTES)
|
||||
continue;
|
||||
|
||||
switch (shdr.sh_type) {
|
||||
case SHT_GROUP: {
|
||||
// Get the signature of this section group.
|
||||
@@ -429,6 +425,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
|
||||
InputSection<E> *isec = this->sections[i].get();
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
@@ -450,19 +447,17 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
this->has_ctors = true;
|
||||
|
||||
if (name == ".eh_frame")
|
||||
eh_frame_sections.push_back(this->sections[i].get());
|
||||
eh_frame_sections.push_back(isec);
|
||||
|
||||
if (name == ".debug_info" && !(shdr.sh_flags & SHF_ALLOC))
|
||||
debug_info = isec;
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
extra.got2 = this->sections[i].get();
|
||||
extra.got2 = isec;
|
||||
|
||||
// Save debug sections for --gdb-index.
|
||||
if (ctx.arg.gdb_index) {
|
||||
InputSection<E> *isec = this->sections[i].get();
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = isec;
|
||||
|
||||
// If --gdb-index is given, contents of .debug_gnu_pubnames and
|
||||
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
|
||||
// in an output file is just a waste of space.
|
||||
@@ -1003,6 +998,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, esym, !this->is_reachable) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec);
|
||||
@@ -1010,6 +1006,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = esym.is_weak();
|
||||
sym.is_versioned_default = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1209,7 +1206,7 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
if (ctx.symtab_shndx)
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + idx;
|
||||
|
||||
symtab_base[idx] = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
symtab_base[idx] = *to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab_base + strtab_off, sym.name());
|
||||
};
|
||||
|
||||
@@ -1277,23 +1274,48 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
if (vers.empty() || esyms[i].is_undef())
|
||||
ver = VER_NDX_GLOBAL;
|
||||
else
|
||||
ver = (vers[i] & ~VERSYM_HIDDEN);
|
||||
ver = vers[i] & ~VERSYM_HIDDEN;
|
||||
|
||||
if (ver == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_default = vers.empty() || !(vers[i] & VERSYM_HIDDEN);
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
this->versyms.push_back(ver);
|
||||
|
||||
if (is_default) {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
std::string_view mangled_name = save_string(
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
|
||||
auto get_versioned_sym = [&] {
|
||||
std::string_view key = save_string(
|
||||
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
|
||||
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
|
||||
return get_symbol(ctx, key, name);
|
||||
};
|
||||
|
||||
// Symbol resolution involving symbol versioning is tricky because one
|
||||
// symbol can be resolved with two different identifiers. Among
|
||||
// symbols with the same name but different versions, one of them is
|
||||
// always marked as the "default" one. This symbol is often denoted
|
||||
// with two atsigns as `foo@@VERSION` and can be referred to either
|
||||
// as `foo` or `foo@VERSION`. No other symbols have two names like that.
|
||||
//
|
||||
// On contrary, a versioned non-default symbol can be referred only
|
||||
// with an explicit version suffix, e.g., `foo@VERSION`.
|
||||
//
|
||||
// Here is how we resolve versioned default symbols. We resolve `foo`
|
||||
// and `foo@VERSION` as usual, but with information to forward
|
||||
// references to `foo@VERSION` to `foo`. After name resolution, we
|
||||
// visit all symbol references to redirect `foo@VERSION` to `foo`.
|
||||
if (vers.empty() || ver == VER_NDX_GLOBAL) {
|
||||
// Unversioned symbol
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else if (vers[i] & VERSYM_HIDDEN) {
|
||||
// Versioned non-default symbol
|
||||
this->symbols.push_back(get_versioned_sym());
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else {
|
||||
// Versioned default symbol
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
this->symbols2.push_back(get_versioned_sym());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1400,6 +1422,22 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = versyms[i];
|
||||
sym.is_weak = true;
|
||||
sym.is_versioned_default = false;
|
||||
}
|
||||
|
||||
// A symbol with the default version is a special case because, unlike
|
||||
// other symbols, the symbol can be referred by two names, `foo` and
|
||||
// `foo@VERSION`. Here, we resolve `foo@VERSOIN` as a proxy of `foo`.
|
||||
Symbol<E> *sym2 = this->symbols2[i];
|
||||
if (sym2 && sym2 != &sym) {
|
||||
std::scoped_lock lock2(sym2->mu);
|
||||
|
||||
if (get_rank(this, esym, false) < get_rank(*sym2)) {
|
||||
sym2->file = this;
|
||||
sym2->origin = (uintptr_t)&sym;
|
||||
sym2->sym_idx = i;
|
||||
sym2->is_versioned_default = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1518,7 +1556,7 @@ void SharedFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) +
|
||||
this->global_symtab_idx + i;
|
||||
|
||||
*symtab++ = to_output_esym(ctx, *sym, strtab_off, xindex);
|
||||
*symtab++ = *to_output_esym(ctx, *sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab + strtab_off, sym->name());
|
||||
i++;
|
||||
}
|
||||
|
||||
+38
-24
@@ -63,16 +63,16 @@ void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
copy_contents(ctx, buf);
|
||||
copy_contents_to(ctx, buf, sh_size);
|
||||
contents = std::string_view((char *)buf, sh_size);
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
|
||||
void InputSection<E>::copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
memcpy(buf, contents.data(), sz);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,16 +84,35 @@ void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
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);
|
||||
z_stream s = {};
|
||||
inflateInit(&s);
|
||||
s.next_in = (u8 *)data.data();
|
||||
s.avail_in = data.size();
|
||||
s.next_out = buf;
|
||||
s.avail_out = sz;
|
||||
|
||||
int r;
|
||||
while (s.total_out < sz && (r = inflate(&s, Z_NO_FLUSH)) == Z_OK);
|
||||
if (s.total_out < sz && r != Z_STREAM_END)
|
||||
Fatal(ctx) << *this << ": uncompress failed: " << s.msg;
|
||||
inflateEnd(&s);
|
||||
break;
|
||||
}
|
||||
case ELFCOMPRESS_ZSTD:
|
||||
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
|
||||
Fatal(ctx) << *this << ": ZSTD_decompress failed";
|
||||
case ELFCOMPRESS_ZSTD: {
|
||||
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||
ZSTD_inBuffer in = { data.data(), data.size() };
|
||||
ZSTD_outBuffer out = { buf, (size_t)sz };
|
||||
|
||||
while (out.pos < out.size) {
|
||||
size_t r = ZSTD_decompressStream(dctx, &out, &in);
|
||||
if (ZSTD_isError(r))
|
||||
Fatal(ctx) << *this << ": uncompress failed: " << ZSTD_getErrorName(r);
|
||||
if (r == 0 && out.pos < out.size)
|
||||
Fatal(ctx) << *this << ": uncompress failed: premature end of input";
|
||||
}
|
||||
ZSTD_freeDCtx(dctx);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
@@ -114,12 +133,6 @@ static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
|
||||
<< sym << "' can not be used; recompile with -fPIC";
|
||||
break;
|
||||
case COPYREL:
|
||||
// Create a copy relocation
|
||||
if (!sym.file->is_dso) {
|
||||
assert(sym.esym().is_undef_weak());
|
||||
Error(ctx) << isec << ": cannot create a copy relocation for "
|
||||
<< sym <<"; recompile with -fPIE or -fPIC";
|
||||
}
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
break;
|
||||
case PLT:
|
||||
@@ -234,7 +247,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
if (deltas.empty()) {
|
||||
// If a section is not relaxed, we can copy it as a one big chunk.
|
||||
copy_contents(ctx, buf);
|
||||
copy_contents_to(ctx, buf, sh_size);
|
||||
} else {
|
||||
// A relaxed section is copied piece-wise.
|
||||
memcpy(buf, contents.data(), deltas[0].offset);
|
||||
@@ -250,7 +263,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copy_contents(ctx, buf);
|
||||
copy_contents_to(ctx, buf, sh_size);
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
@@ -343,8 +356,8 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
|
||||
template <typename E>
|
||||
MergeableSection<E>::MergeableSection(Context<E> &ctx, MergedSection<E> &parent,
|
||||
std::unique_ptr<InputSection<E>> &isec)
|
||||
: parent(parent), p2align(isec->p2align), section(std::move(isec)) {
|
||||
section->uncompress(ctx);
|
||||
: parent(parent), p2align(isec->p2align), input_section(std::move(isec)) {
|
||||
input_section->uncompress(ctx);
|
||||
|
||||
std::scoped_lock lock(parent.mu);
|
||||
parent.members.push_back(this);
|
||||
@@ -380,9 +393,9 @@ static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
|
||||
// We do not support mergeable sections that have relocations.
|
||||
template <typename E>
|
||||
void MergeableSection<E>::split_contents(Context<E> &ctx) {
|
||||
std::string_view data = section->contents;
|
||||
std::string_view data = input_section->contents;
|
||||
if (data.size() > UINT32_MAX)
|
||||
Fatal(ctx) << *section << ": mergeable section too large";
|
||||
Fatal(ctx) << *input_section << ": mergeable section too large";
|
||||
|
||||
i64 entsize = parent.shdr.sh_entsize;
|
||||
|
||||
@@ -392,12 +405,13 @@ void MergeableSection<E>::split_contents(Context<E> &ctx) {
|
||||
frag_offsets.push_back(pos);
|
||||
size_t end = find_null(data, pos, entsize);
|
||||
if (end == data.npos)
|
||||
Fatal(ctx) << *section << ": string is not null terminated";
|
||||
Fatal(ctx) << *input_section << ": string is not null terminated";
|
||||
pos = end + entsize;
|
||||
}
|
||||
} else {
|
||||
if (data.size() % entsize)
|
||||
Fatal(ctx) << *section << ": section size is not multiple of sh_entsize";
|
||||
Fatal(ctx) << *input_section
|
||||
<< ": section size is not multiple of sh_entsize";
|
||||
frag_offsets.reserve(data.size() / entsize);
|
||||
|
||||
for (i64 pos = 0; pos < data.size(); pos += entsize)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// mold processes to just 1 for each user. It is intended to be used as
|
||||
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
|
||||
|
||||
#include "common.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
File renamed without changes.
+11
-9
@@ -29,8 +29,8 @@
|
||||
//
|
||||
// 4. Continue the rest of the linking process as usual.
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// backend.
|
||||
//
|
||||
// The linker plugin API is documented at
|
||||
@@ -425,7 +425,7 @@ get_api_version(const char *plugin_identifier,
|
||||
if (LAPI_V1 < minimal_api_supported)
|
||||
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
|
||||
|
||||
std::string version = get_mold_version() + "\0"s;
|
||||
std::string version = mold_version + "\0"s;
|
||||
|
||||
*linker_identifier = "mold";
|
||||
*linker_version = version.data();
|
||||
@@ -600,10 +600,10 @@ create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
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.";
|
||||
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);
|
||||
|
||||
@@ -670,8 +670,10 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
// 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)
|
||||
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, psym.comdat_key);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
obj->symbol_strtab = save_string(ctx, strtab);
|
||||
|
||||
+29
-29
@@ -1,11 +1,9 @@
|
||||
#include "mold.h"
|
||||
#include "../lib/archive-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -15,13 +13,6 @@
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
int main(int argc, char **argv) {
|
||||
mold::set_mimalloc_options();
|
||||
return mold::mold_main<mold::X86_64>(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
@@ -44,8 +35,8 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
|
||||
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, archive_name);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_reachable =
|
||||
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
@@ -65,8 +56,8 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_reachable =
|
||||
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
if (ctx.arg.trace)
|
||||
Out(ctx) << "trace: " << *file;
|
||||
@@ -81,7 +72,7 @@ new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
SharedFile<E> *file = new SharedFile<E>(ctx, mf);
|
||||
ctx.dso_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_reachable = !rctx.as_needed;
|
||||
file->as_needed = rctx.as_needed;
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
@@ -306,8 +297,8 @@ int mold_main(int argc, char **argv) {
|
||||
|
||||
acquire_global_lock();
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
@@ -496,7 +487,8 @@ int mold_main(int argc, char **argv) {
|
||||
// create as few segments as possible.
|
||||
sort_output_sections(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
// Handle --separate-debug-file.
|
||||
if (ctx.gnu_debuglink)
|
||||
separate_debug_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
@@ -514,6 +506,10 @@ int mold_main(int argc, char **argv) {
|
||||
// be added to .dynsym.
|
||||
sort_dynsyms(ctx);
|
||||
|
||||
// Sort .debug_info contents so that DWARF32 debug info precedes that of
|
||||
// DWARF64. This is to mitigate the possibility of a relocation overflow.
|
||||
sort_debug_info_sections(ctx);
|
||||
|
||||
// Print reports about undefined symbols, if needed.
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
report_undef_errors(ctx);
|
||||
@@ -536,13 +532,6 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// We've created range extension thunks with a pessimistive assumption
|
||||
// that all out-of-section references are out of range. Now that we are
|
||||
// able to assign addresses to all SHF_ALLOC output sections, we can
|
||||
// eliminate excessive thunks.
|
||||
if constexpr (needs_thunk<E>)
|
||||
remove_redundant_thunks(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
if (!ctx.arg.strip_all)
|
||||
create_output_symtab(ctx);
|
||||
@@ -562,6 +551,14 @@ int mold_main(int argc, char **argv) {
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// We've created range extension thunks with a pessimistive assumption
|
||||
// that all out-of-section references are out of range. Now that we know
|
||||
// the addresses of all sections,, we can eliminate excessive thunks.
|
||||
if constexpr (needs_thunk<E>) {
|
||||
remove_redundant_thunks(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
if constexpr (is_arm32<E>) {
|
||||
if (ctx.extra.exidx) {
|
||||
ctx.extra.exidx->remove_duplicate_entries(ctx);
|
||||
@@ -578,8 +575,8 @@ int mold_main(int argc, char **argv) {
|
||||
// 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) {
|
||||
// using zlib or zstd.
|
||||
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE) {
|
||||
compress_debug_sections(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
@@ -601,6 +598,9 @@ int mold_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
if constexpr (is_arm32be<E>)
|
||||
arm32be_swap_bytes(ctx);
|
||||
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.z_rewrite_endbr)
|
||||
rewrite_endbr(ctx);
|
||||
@@ -612,7 +612,7 @@ int mold_main(int argc, char **argv) {
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gdb_index && !ctx.gnu_debuglink)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
@@ -621,7 +621,7 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.buildid)
|
||||
write_build_id(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
write_gnu_debuglink(ctx);
|
||||
|
||||
t_copy.stop();
|
||||
@@ -642,7 +642,7 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
write_separate_debug_file(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
|
||||
+6
-8
@@ -19,14 +19,12 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
+308
-48
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../lib/common.h"
|
||||
#include "../lib/lib.h"
|
||||
#include "elf.h"
|
||||
|
||||
#include <cassert>
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <string_view>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/task_group.h>
|
||||
#include <type_traits>
|
||||
@@ -49,7 +50,207 @@ struct ReaderContext;
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
|
||||
|
||||
std::string get_mold_version();
|
||||
extern std::string mold_version;
|
||||
|
||||
//
|
||||
// error.cc
|
||||
//
|
||||
|
||||
// Some C++ stdlibs don't support std::osyncstream even though
|
||||
// it's is in the C++20 standard. So we implement it ourselves.
|
||||
class SyncStream {
|
||||
public:
|
||||
SyncStream(std::ostream &out) : out(out) {}
|
||||
~SyncStream() { emit(); }
|
||||
|
||||
void emit() {
|
||||
if (!emitted) {
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << '\n';
|
||||
emitted = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> SyncStream &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream &out;
|
||||
std::stringstream ss;
|
||||
bool emitted = false;
|
||||
static inline std::mutex mu;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class Out {
|
||||
public:
|
||||
Out(Context<E> &ctx) {}
|
||||
|
||||
template <typename T> Out &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cout};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(Context<E> &ctx);
|
||||
[[noreturn]] ~Fatal();
|
||||
|
||||
template <typename T> Fatal &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cerr};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class Error {
|
||||
public:
|
||||
Error(Context<E> &ctx);
|
||||
|
||||
template <typename T> Error &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cerr};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(Context<E> &ctx);
|
||||
|
||||
template <typename T> Warn &operator<<(T &&val) {
|
||||
if (out)
|
||||
*out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<SyncStream> out;
|
||||
};
|
||||
|
||||
//
|
||||
// signal-unix.cc
|
||||
//
|
||||
|
||||
inline char *output_tmpfile = nullptr;
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
std::string errno_string();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
|
||||
//
|
||||
// mapped-file-unix.cc
|
||||
//
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
class MappedFile {
|
||||
public:
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
void close_fd();
|
||||
void reopen_fd(const std::string &path);
|
||||
|
||||
template <typename E>
|
||||
MappedFile *slice(Context<E> &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = name;
|
||||
mf->data = data + start;
|
||||
mf->size = size;
|
||||
mf->parent = this;
|
||||
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
return mf;
|
||||
}
|
||||
|
||||
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;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
|
||||
// For --dependency-file
|
||||
bool is_dependency = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE fd = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
int fd = -1;
|
||||
#endif
|
||||
};
|
||||
|
||||
MappedFile *open_file_impl(const std::string &path, std::string &error);
|
||||
|
||||
template <typename E>
|
||||
MappedFile *open_file(Context<E> &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
std::string error;
|
||||
MappedFile *mf = open_file_impl(path, error);
|
||||
if (!error.empty())
|
||||
Fatal(ctx) << error;
|
||||
|
||||
if (mf)
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile *must_open_file(Context<E> &ctx, std::string path) {
|
||||
MappedFile *mf = open_file(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
return mf;
|
||||
}
|
||||
|
||||
//
|
||||
// jobs-unix.cc
|
||||
//
|
||||
|
||||
void acquire_global_lock();
|
||||
void release_global_lock();
|
||||
|
||||
//
|
||||
// Mergeable section fragments
|
||||
@@ -65,9 +266,13 @@ struct __attribute__((aligned(4))) SectionFragment {
|
||||
}
|
||||
|
||||
MergedSection<E> &output_section;
|
||||
u32 offset = -1;
|
||||
i64 offset = -1;
|
||||
Atomic<u8> p2align = 0;
|
||||
Atomic<bool> is_alive = false;
|
||||
|
||||
// True if this fragment must be placed within 2^32 bytes from the
|
||||
// start of the output section.
|
||||
Atomic<bool> is_32bit = false;
|
||||
};
|
||||
|
||||
// Additional class members for dynamic symbols. Because most symbols
|
||||
@@ -262,9 +467,9 @@ struct FdeRecord {
|
||||
template <typename E>
|
||||
struct InputSectionExtras {};
|
||||
|
||||
template <>
|
||||
struct InputSectionExtras<ARM32> {
|
||||
InputSection<ARM32> *exidx = nullptr;
|
||||
template <is_arm32 E>
|
||||
struct InputSectionExtras<E> {
|
||||
InputSection<E> *exidx = nullptr;
|
||||
};
|
||||
|
||||
struct RelocDelta {
|
||||
@@ -294,7 +499,7 @@ public:
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx);
|
||||
|
||||
void uncompress(Context<E> &ctx);
|
||||
void copy_contents(Context<E> &ctx, u8 *buf);
|
||||
void copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz);
|
||||
void scan_relocations(Context<E> &ctx);
|
||||
void write_to(Context<E> &ctx, u8 *buf);
|
||||
void apply_reloc_alloc(Context<E> &ctx, u8 *base);
|
||||
@@ -401,6 +606,22 @@ template <typename E>
|
||||
std::string_view
|
||||
get_machine_type(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf);
|
||||
|
||||
//
|
||||
// compress.cc
|
||||
//
|
||||
|
||||
class Compressor {
|
||||
public:
|
||||
Compressor(i64 format, u8 *buf, i64 size);
|
||||
void write_to(u8 *buf);
|
||||
~Compressor();
|
||||
|
||||
i64 compressed_size = 0;
|
||||
|
||||
protected:
|
||||
std::vector<std::span<u8>> shards;
|
||||
};
|
||||
|
||||
//
|
||||
// output-chunks.cc
|
||||
//
|
||||
@@ -449,14 +670,13 @@ public:
|
||||
|
||||
// For --gdb-index
|
||||
bool is_compressed = false;
|
||||
std::vector<u8> uncompressed_data;
|
||||
|
||||
// Some synethetic sections add local symbols to the output.
|
||||
// For example, range extension thunks adds function_name@thunk
|
||||
// symbol for each thunk entry. The following members are used
|
||||
// for such synthesizing symbols.
|
||||
virtual void compute_symtab_size(Context<E> &ctx) {};
|
||||
virtual void populate_symtab(Context<E> &ctx) {};
|
||||
virtual void compute_symtab_size(Context<E> &ctx) {}
|
||||
virtual void populate_symtab(Context<E> &ctx) {}
|
||||
|
||||
i64 local_symtab_idx = 0;
|
||||
i64 num_local_symtab = 0;
|
||||
@@ -570,7 +790,7 @@ public:
|
||||
void populate_symtab(Context<E> &ctx) override;
|
||||
|
||||
void scan_abs_relocations(Context<E> &ctx);
|
||||
void create_range_extension_thunks(Context<E> &ctx, bool first_pass);
|
||||
void create_range_extension_thunks(Context<E> &ctx);
|
||||
|
||||
std::vector<InputSection<E> *> members;
|
||||
std::vector<std::unique_ptr<Thunk<E>>> thunks;
|
||||
@@ -797,8 +1017,8 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
U32<E> *shndx);
|
||||
std::optional<ElfSym<E>>
|
||||
to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name, U32<E> *shndx);
|
||||
|
||||
template <typename E>
|
||||
class SymtabSection : public Chunk<E> {
|
||||
@@ -1127,9 +1347,11 @@ public:
|
||||
CompressedSection(Context<E> &ctx, Chunk<E> &chunk);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<u8[]> uncompressed_data;
|
||||
ElfChdr<E> chdr = {};
|
||||
std::unique_ptr<Compressor> compressor;
|
||||
|
||||
private:
|
||||
std::optional<Compressor> compressor;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -1301,11 +1523,11 @@ public:
|
||||
std::string_view get_contents(i64 idx);
|
||||
|
||||
MergedSection<E> &parent;
|
||||
std::vector<SectionFragment<E> *> fragments;
|
||||
u8 p2align = 0;
|
||||
std::unique_ptr<InputSection<E>> input_section;
|
||||
std::vector<SectionFragment<E> *> fragments;
|
||||
|
||||
private:
|
||||
std::unique_ptr<InputSection<E>> section;
|
||||
std::vector<u32> frag_offsets;
|
||||
std::vector<u32> hashes;
|
||||
};
|
||||
@@ -1337,6 +1559,7 @@ public:
|
||||
mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(InputFile<E> *)> feeder) = 0;
|
||||
|
||||
std::span<Symbol<E> *> get_local_syms();
|
||||
std::span<Symbol<E> *> get_global_syms();
|
||||
std::string_view get_source_name() const;
|
||||
|
||||
@@ -1353,6 +1576,7 @@ public:
|
||||
std::string_view shstrtab;
|
||||
std::string_view symbol_strtab;
|
||||
|
||||
bool as_needed = false;
|
||||
bool has_init_array = false;
|
||||
bool has_ctors = false;
|
||||
|
||||
@@ -1429,6 +1653,7 @@ public:
|
||||
bool is_lto_obj = false;
|
||||
bool is_gcc_offload_obj = false;
|
||||
bool is_rust_obj = false;
|
||||
bool is_dwarf32 = false;
|
||||
|
||||
i64 fde_idx = 0;
|
||||
i64 fde_offset = 0;
|
||||
@@ -1487,6 +1712,7 @@ public:
|
||||
|
||||
std::string soname;
|
||||
std::vector<std::string_view> version_strings;
|
||||
std::vector<Symbol<E> *> symbols2;
|
||||
std::vector<ElfSym<E>> elf_syms2;
|
||||
|
||||
private:
|
||||
@@ -1567,6 +1793,22 @@ template <typename E>
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path);
|
||||
|
||||
//
|
||||
// archive-file.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context<E> &ctx, MappedFile *mf);
|
||||
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_fat_archive_members(Context<E> &ctx, MappedFile *mf);
|
||||
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_archive_members(Context<E> &ctx, MappedFile *mf);
|
||||
|
||||
//
|
||||
// lto.cc
|
||||
//
|
||||
@@ -1687,6 +1929,7 @@ template <typename E> void scan_relocations(Context<E> &);
|
||||
template <typename E> void compute_imported_symbol_weakness(Context<E> &);
|
||||
template <typename E> void construct_relr(Context<E> &);
|
||||
template <typename E> void sort_dynsyms(Context<E> &);
|
||||
template <typename E> void sort_debug_info_sections(Context<E> &);
|
||||
template <typename E> void create_output_symtab(Context<E> &);
|
||||
template <typename E> void report_undef_errors(Context<E> &);
|
||||
template <typename E> void create_reloc_sections(Context<E> &);
|
||||
@@ -1717,28 +1960,34 @@ void rewrite_endbr(Context<X86_64> &ctx);
|
||||
// arch-arm32.cc
|
||||
//
|
||||
|
||||
class Arm32ExidxSection : public Chunk<ARM32> {
|
||||
template <is_arm32 E>
|
||||
class Arm32ExidxSection : public Chunk<E> {
|
||||
public:
|
||||
Arm32ExidxSection(OutputSection<ARM32> &osec) : output_section(osec) {
|
||||
Arm32ExidxSection(OutputSection<E> &osec) : output_section(osec) {
|
||||
this->name = ".ARM.exidx";
|
||||
this->shdr.sh_type = SHT_ARM_EXIDX;
|
||||
this->shdr.sh_flags = SHF_ALLOC;
|
||||
this->shdr.sh_addralign = 4;
|
||||
}
|
||||
|
||||
void compute_section_size(Context<ARM32> &ctx) override;
|
||||
void update_shdr(Context<ARM32> &ctx) override;
|
||||
void remove_duplicate_entries(Context<ARM32> &ctx);
|
||||
void copy_buf(Context<ARM32> &ctx) override;
|
||||
void compute_section_size(Context<E> &ctx) override;
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void remove_duplicate_entries(Context<E> &ctx);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
private:
|
||||
std::vector<u8> get_contents(Context<ARM32> &ctx);
|
||||
std::vector<u8> get_contents(Context<E> &ctx);
|
||||
|
||||
OutputSection<ARM32> &output_section;
|
||||
OutputSection<E> &output_section;
|
||||
};
|
||||
|
||||
template <> u64 get_eflags(Context<ARM32> &ctx);
|
||||
void create_arm_exidx_section(Context<ARM32> &ctx);
|
||||
template <is_arm32 E>
|
||||
u64 get_eflags(Context<E> &ctx);
|
||||
|
||||
template <is_arm32 E>
|
||||
void create_arm_exidx_section(Context<E> &ctx);
|
||||
|
||||
void arm32be_swap_bytes(Context<ARM32BE> &ctx);
|
||||
|
||||
//
|
||||
// arch-riscv.cc
|
||||
@@ -1808,12 +2057,6 @@ public:
|
||||
|
||||
template <> u64 get_eflags(Context<PPC64V2> &ctx);
|
||||
|
||||
//
|
||||
// arch-arc.cc
|
||||
//
|
||||
|
||||
template <> u64 get_eflags(Context<ARC> &ctx);
|
||||
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
@@ -1837,8 +2080,6 @@ struct BuildId {
|
||||
i64 hash_size = 0;
|
||||
};
|
||||
|
||||
typedef enum { COMPRESS_NONE, COMPRESS_ZLIB, COMPRESS_ZSTD } CompressKind;
|
||||
|
||||
typedef enum {
|
||||
UNRESOLVED_ERROR,
|
||||
UNRESOLVED_WARN,
|
||||
@@ -1894,9 +2135,9 @@ struct ContextExtras<E> {
|
||||
NotePropertySection<E> *note_property = nullptr;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ContextExtras<ARM32> {
|
||||
Arm32ExidxSection *exidx = nullptr;
|
||||
template <is_arm32 E>
|
||||
struct ContextExtras<E> {
|
||||
Arm32ExidxSection<E> *exidx = nullptr;
|
||||
};
|
||||
|
||||
template <is_riscv E>
|
||||
@@ -1934,7 +2175,7 @@ struct ContextExtras<SPARC64> {
|
||||
template <typename E>
|
||||
struct Context {
|
||||
Context() {
|
||||
arg.entry = get_symbol(*this, is_arc<E> ? "__start" : "_start");
|
||||
arg.entry = get_symbol(*this, "_start");
|
||||
arg.fini = get_symbol(*this, "_fini");
|
||||
arg.init = get_symbol(*this, "_init");
|
||||
|
||||
@@ -1956,8 +2197,8 @@ struct Context {
|
||||
BsymbolicKind Bsymbolic = BSYMBOLIC_NONE;
|
||||
BuildId build_id;
|
||||
CetReportKind z_cet_report = CET_REPORT_NONE;
|
||||
CompressKind compress_debug_sections = COMPRESS_NONE;
|
||||
MultiGlob undefined_glob;
|
||||
Glob undefined_glob;
|
||||
Glob unique;
|
||||
SeparateCodeKind z_separate_code = NOSEPARATE_CODE;
|
||||
ShuffleSectionsKind shuffle_sections = SHUFFLE_SECTIONS_NONE;
|
||||
Symbol<E> *entry = nullptr;
|
||||
@@ -1967,6 +2208,7 @@ struct Context {
|
||||
bool allow_multiple_definition = false;
|
||||
bool allow_shlib_undefined = true;
|
||||
bool apply_dynamic_relocs = true;
|
||||
bool be8 = false;
|
||||
bool color_diagnostics = false;
|
||||
bool default_symver = false;
|
||||
bool demangle = true;
|
||||
@@ -2040,12 +2282,12 @@ struct Context {
|
||||
bool z_shstk = false;
|
||||
bool z_start_stop_visibility_protected = false;
|
||||
bool z_text = false;
|
||||
i64 compress_debug_sections = ELFCOMPRESS_NONE;
|
||||
i64 filler = -1;
|
||||
i64 spare_dynamic_tags = 5;
|
||||
i64 spare_program_headers = 0;
|
||||
i64 thread_count = 0;
|
||||
i64 z_stack_size = 0;
|
||||
std::optional<Glob> unique;
|
||||
std::optional<u64> physical_image_base;
|
||||
std::optional<std::vector<Symbol<E> *>> retain_symbols_file;
|
||||
std::string Map;
|
||||
@@ -2084,6 +2326,7 @@ struct Context {
|
||||
u64 shuffle_sections_seed = 0;
|
||||
} arg;
|
||||
|
||||
std::optional<tbb::global_control> global_limit;
|
||||
std::vector<VersionPattern> version_patterns;
|
||||
std::vector<DynamicPattern> dynamic_list_patterns;
|
||||
i64 default_version = VER_NDX_UNSPECIFIED;
|
||||
@@ -2392,6 +2635,9 @@ public:
|
||||
bool is_traced : 1 = false; // for --trace-symbol
|
||||
bool is_wrapped : 1 = false; // for --wrap
|
||||
|
||||
// For symbols with default symbol version, e.g. foo@@VERSION.
|
||||
bool is_versioned_default : 1 = false;
|
||||
|
||||
// If a symbol can be resolved to a symbol in a different ELF file at
|
||||
// runtime, `is_imported` is true. If a symbol is a dynamic symbol and
|
||||
// can be used by other ELF file at runtime, `is_exported` is true.
|
||||
@@ -2651,7 +2897,7 @@ InputSection<E>::get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag) {
|
||||
return {};
|
||||
|
||||
std::string_view str = name();
|
||||
if (!str.starts_with(".debug"))
|
||||
if (!str.starts_with(".debug_"))
|
||||
return {};
|
||||
|
||||
// If the section was dead due to ICF, we don't want to emit debug
|
||||
@@ -2695,8 +2941,8 @@ template <typename E>
|
||||
std::string_view MergeableSection<E>::get_contents(i64 i) {
|
||||
i64 cur = frag_offsets[i];
|
||||
if (i == frag_offsets.size() - 1)
|
||||
return section->contents.substr(cur);
|
||||
return section->contents.substr(cur, frag_offsets[i + 1] - cur);
|
||||
return input_section->contents.substr(cur);
|
||||
return input_section->contents.substr(cur, frag_offsets[i + 1] - cur);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -2734,6 +2980,11 @@ inline std::string_view InputFile<E>::get_string(Context<E> &ctx, i64 idx) {
|
||||
return this->get_string(ctx, elf_sections[idx]);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::span<Symbol<E> *> InputFile<E>::get_local_syms() {
|
||||
return std::span<Symbol<E> *>(this->symbols).subspan(0, this->first_global);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::span<Symbol<E> *> InputFile<E>::get_global_syms() {
|
||||
return std::span<Symbol<E> *>(this->symbols).subspan(this->first_global);
|
||||
@@ -3021,12 +3272,16 @@ template <typename E>
|
||||
u64
|
||||
Symbol<E>::get_thunk_addr(Context<E> &ctx, u64 P) const requires needs_thunk<E> {
|
||||
std::span<u64> vec = ctx.symbol_aux[aux_idx].thunk_addrs;
|
||||
|
||||
u64 lo = (P < branch_distance<E>) ? 0 : P - branch_distance<E>;
|
||||
u64 val = *ranges::lower_bound(vec, lo);
|
||||
i64 disp = val - P;
|
||||
auto it = ranges::lower_bound(vec, lo);
|
||||
if (it == vec.end())
|
||||
Fatal(ctx) << "range extension thunk out of range: " << *this;
|
||||
|
||||
i64 disp = *it - P;
|
||||
if (disp < -branch_distance<E> || branch_distance<E> <= disp)
|
||||
Fatal(ctx) << "range extension thunk out of range: " << *this;
|
||||
return val;
|
||||
return *it;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -3036,6 +3291,11 @@ inline bool Symbol<E>::has_plt(Context<E> &ctx) const {
|
||||
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_absolute() const {
|
||||
// An unresolved weak symbol acts as if it were an absolute address
|
||||
// at address 0
|
||||
if (is_remaining_undef_weak())
|
||||
return true;
|
||||
|
||||
return !is_imported && !get_frag() && !get_input_section() &&
|
||||
!get_output_section();
|
||||
}
|
||||
|
||||
+97
-75
@@ -834,20 +834,6 @@ void DynamicSection<E>::copy_buf(Context<E> &ctx) {
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::vector<std::span<T>> split(std::vector<T> &input, i64 unit) {
|
||||
std::span<T> span(input);
|
||||
std::vector<std::span<T>> vec;
|
||||
|
||||
while (span.size() >= unit) {
|
||||
vec.push_back(span.subspan(0, unit));
|
||||
span = span.subspan(unit);
|
||||
}
|
||||
if (!span.empty())
|
||||
vec.push_back(span);
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Assign offsets to OutputSection members
|
||||
template <typename E>
|
||||
void OutputSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
@@ -1176,6 +1162,9 @@ void OutputSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
u8 *strtab = strtab_base + this->strtab_offset;
|
||||
|
||||
memset(esym, 0, this->num_local_symtab * sizeof(ElfSym<E>));
|
||||
memset(strtab, 0, this->strtab_size);
|
||||
|
||||
auto write_esym = [&](u64 addr, i64 st_name) {
|
||||
memset(esym, 0, sizeof(*esym));
|
||||
esym->st_name = st_name;
|
||||
@@ -1315,18 +1304,16 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
if (sym->is_imported) {
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
add({idx, 0, E::R_GLOB_DAT, sym});
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we know an address at link-time, fill that GOT entry now.
|
||||
// It may need a base relocation, though.
|
||||
if (ctx.arg.pic && sym->is_relative())
|
||||
} else if (ctx.arg.pic && sym->is_relative()) {
|
||||
// We know the symbol's address, but it needs a base relocation.
|
||||
add({idx, sym->get_addr(ctx, NO_PLT), E::R_RELATIVE});
|
||||
else
|
||||
} else {
|
||||
// We know the symbol's exact run-time address at link-time.
|
||||
add({idx, sym->get_addr(ctx, NO_PLT)});
|
||||
}
|
||||
}
|
||||
|
||||
// Create GOT entries for TLVs.
|
||||
@@ -1738,8 +1725,8 @@ static u64 get_symbol_size(Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
U32<E> *shn_xindex) {
|
||||
std::optional<ElfSym<E>>
|
||||
to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name, U32<E> *shn_xindex) {
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
|
||||
@@ -1848,7 +1835,8 @@ ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
if (0 <= shndx && shndx < SHN_LORESERVE) {
|
||||
esym.st_shndx = shndx;
|
||||
} else if (SHN_LORESERVE <= shndx) {
|
||||
assert(shn_xindex);
|
||||
if (!shn_xindex)
|
||||
return {};
|
||||
esym.st_shndx = SHN_XINDEX;
|
||||
*shn_xindex = shndx;
|
||||
}
|
||||
@@ -1882,7 +1870,17 @@ void DynsymSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 1; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
buf[sym.get_dynsym_idx(ctx)] = to_output_esym(ctx, sym, offset, nullptr);
|
||||
|
||||
std::optional<ElfSym<E>> esym = to_output_esym(ctx, sym, offset, nullptr);
|
||||
if (!esym) {
|
||||
Error(ctx) << ctx.arg.output
|
||||
<< ": .dynsym: too many output sections: "
|
||||
<< (ctx.shdr->shdr.sh_size / sizeof(ElfShdr<E>))
|
||||
<< " requested, but ELF allows at most 65279";
|
||||
return;
|
||||
}
|
||||
|
||||
buf[sym.get_dynsym_idx(ctx)] = *esym;
|
||||
offset += sym.name().size() + 1;
|
||||
}
|
||||
}
|
||||
@@ -1994,7 +1992,7 @@ get_merged_output_name(Context<E> &ctx, std::string_view name, u64 flags,
|
||||
i64 entsize, i64 addralign) {
|
||||
if (ctx.arg.relocatable && !ctx.arg.relocatable_merge_sections)
|
||||
return name;
|
||||
if (ctx.arg.unique && ctx.arg.unique->match(name))
|
||||
if (!ctx.arg.unique.empty() && ctx.arg.unique.find(name) != -1)
|
||||
return name;
|
||||
|
||||
// GCC seems to create sections named ".rodata.strN.<mangled-symbol-name>.M"
|
||||
@@ -2106,7 +2104,7 @@ static void add_comment_strings(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
// Add an identification string to .comment.
|
||||
add(get_mold_version());
|
||||
add(mold_version);
|
||||
|
||||
// Embed command line arguments for debugging.
|
||||
char *env = getenv("MOLD_DEBUG");
|
||||
@@ -2144,43 +2142,54 @@ void MergedSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
if (!resolved)
|
||||
resolve(ctx);
|
||||
|
||||
std::vector<i64> sizes(map.NUM_SHARDS);
|
||||
std::vector<i64> sizes(map.NUM_SHARDS * 2);
|
||||
|
||||
tbb::parallel_for((i64)0, map.NUM_SHARDS, [&](i64 i) {
|
||||
using Entry = typename decltype(map)::Entry;
|
||||
std::vector<Entry *> entries = map.get_sorted_entries(i);
|
||||
i64 offset = 0;
|
||||
|
||||
i64 off1 = 0;
|
||||
i64 off2 = 0;
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
SectionFragment<E> &frag = ent->value;
|
||||
if (frag.is_alive) {
|
||||
offset = align_to(offset, 1 << frag.p2align);
|
||||
frag.offset = offset;
|
||||
offset += ent->keylen;
|
||||
if (frag.is_32bit) {
|
||||
off1 = align_to(off1, 1 << frag.p2align);
|
||||
frag.offset = off1;
|
||||
off1 += ent->keylen;
|
||||
} else {
|
||||
off2 = align_to(off2, 1 << frag.p2align);
|
||||
frag.offset = off2;
|
||||
off2 += ent->keylen;
|
||||
}
|
||||
}
|
||||
}
|
||||
sizes[i] = offset;
|
||||
|
||||
sizes[i] = off1;
|
||||
sizes[i + map.NUM_SHARDS] = off2;
|
||||
});
|
||||
|
||||
i64 shard_size = map.nbuckets / map.NUM_SHARDS;
|
||||
shard_offsets.resize(map.NUM_SHARDS + 1);
|
||||
shard_offsets.resize(sizes.size() + 1);
|
||||
|
||||
for (i64 i = 1; i < map.NUM_SHARDS + 1; i++)
|
||||
for (i64 i = 1; i < sizes.size() + 1; i++)
|
||||
shard_offsets[i] =
|
||||
align_to(shard_offsets[i - 1] + sizes[i - 1], this->shdr.sh_addralign);
|
||||
|
||||
this->shdr.sh_size = shard_offsets.back();
|
||||
|
||||
tbb::parallel_for((i64)1, map.NUM_SHARDS, [&](i64 i) {
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
|
||||
SectionFragment<E> &frag = map.entries[j].value;
|
||||
if (frag.is_alive)
|
||||
frag.offset += shard_offsets[i];
|
||||
if (frag.is_alive) {
|
||||
if (frag.is_32bit)
|
||||
frag.offset += shard_offsets[i];
|
||||
else
|
||||
frag.offset += shard_offsets[i + map.NUM_SHARDS];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this->shdr.sh_size = shard_offsets[map.NUM_SHARDS];
|
||||
|
||||
if (this->shdr.sh_size > UINT32_MAX)
|
||||
Fatal(ctx) << this->name << ": output section too large";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -2196,8 +2205,11 @@ void MergedSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
// There might be gaps between strings to satisfy alignment requirements.
|
||||
// If that's the case, we need to zero-clear them.
|
||||
if (this->shdr.sh_addralign > 1 &&
|
||||
this->shdr.sh_addralign != this->shdr.sh_entsize)
|
||||
this->shdr.sh_addralign != this->shdr.sh_entsize) {
|
||||
memset(buf + shard_offsets[i], 0, shard_offsets[i + 1] - shard_offsets[i]);
|
||||
i64 j = map.NUM_SHARDS + i;
|
||||
memset(buf + shard_offsets[j], 0, shard_offsets[j + 1] - shard_offsets[j]);
|
||||
}
|
||||
|
||||
// Copy strings
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
|
||||
@@ -2447,21 +2459,30 @@ void EhFrameRelocSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void CopyrelSection<E>::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
assert(!ctx.arg.shared);
|
||||
if (sym->has_copyrel)
|
||||
return;
|
||||
|
||||
assert(!ctx.arg.shared);
|
||||
assert(sym->file->is_dso);
|
||||
if (!sym->file->is_dso) {
|
||||
assert(sym->esym().is_undef_weak());
|
||||
Error(ctx) << *sym->file << ": cannot create a copy relocation for "
|
||||
<< *sym <<"; recompile with -fPIE or -fPIC";
|
||||
return;
|
||||
}
|
||||
|
||||
if (sym->esym().st_visibility == STV_PROTECTED)
|
||||
if (sym->esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << *sym->file
|
||||
<< ": cannot create a copy relocation for protected symbol '"
|
||||
<< *sym << "'; recompile with -fPIC";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.arg.z_copyreloc)
|
||||
if (!ctx.arg.z_copyreloc) {
|
||||
Error(ctx) << "-z nocopyreloc: " << *sym->file
|
||||
<< ": cannot create a copy relocation for symbol '" << *sym
|
||||
<< "'; recompile with -fPIC";
|
||||
return;
|
||||
}
|
||||
|
||||
symbols.push_back(sym);
|
||||
|
||||
@@ -2634,6 +2655,14 @@ void VerdefSection<E>::construct(Context<E> &ctx) {
|
||||
if (ctx.arg.version_definitions.empty())
|
||||
return;
|
||||
|
||||
// Handle --default-symver
|
||||
if (ctx.arg.default_symver)
|
||||
for (Symbol<E> *sym : ctx.dynsym->symbols)
|
||||
if (sym && !sym->file->is_dso)
|
||||
if (u16 ver = sym->ver_idx;
|
||||
ver == VER_NDX_GLOBAL || ver == VER_NDX_UNSPECIFIED)
|
||||
sym->ver_idx = VER_NDX_LAST_RESERVED + 1;
|
||||
|
||||
// Resize .gnu.version and write to it
|
||||
ctx.versym->contents.resize(ctx.dynsym->symbols.size(), VER_NDX_GLOBAL);
|
||||
ctx.versym->contents[0] = VER_NDX_LOCAL;
|
||||
@@ -2809,42 +2838,33 @@ void NotePropertySection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
CompressedSection<E>::CompressedSection(Context<E> &ctx, Chunk<E> &chunk) {
|
||||
assert(chunk.name.starts_with(".debug"));
|
||||
this->name = chunk.name;
|
||||
this->is_compressed = true;
|
||||
// Allocate a temporary buffer to write uncompressed contents. Note
|
||||
// that we use u8[] instead of std::vector<u8> to avoid the cost of
|
||||
// zero-initialization, as sh_size can be very large.
|
||||
std::unique_ptr<u8[]> buf(new u8[chunk.shdr.sh_size]);
|
||||
|
||||
this->uncompressed_data.resize(chunk.shdr.sh_size);
|
||||
u8 *buf = this->uncompressed_data.data();
|
||||
|
||||
chunk.write_to(ctx, buf);
|
||||
|
||||
switch (ctx.arg.compress_debug_sections) {
|
||||
case COMPRESS_ZLIB:
|
||||
chdr.ch_type = ELFCOMPRESS_ZLIB;
|
||||
compressor.reset(new ZlibCompressor(buf, chunk.shdr.sh_size));
|
||||
break;
|
||||
case COMPRESS_ZSTD:
|
||||
chdr.ch_type = ELFCOMPRESS_ZSTD;
|
||||
compressor.reset(new ZstdCompressor(buf, chunk.shdr.sh_size));
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
// Write uncompressed contents and then compress them
|
||||
chunk.write_to(ctx, buf.get());
|
||||
compressor.emplace(ctx.arg.compress_debug_sections, buf.get(),
|
||||
chunk.shdr.sh_size);
|
||||
|
||||
// Compute header field values
|
||||
chdr.ch_type = ctx.arg.compress_debug_sections;
|
||||
chdr.ch_size = chunk.shdr.sh_size;
|
||||
chdr.ch_addralign = chunk.shdr.sh_addralign;
|
||||
|
||||
this->name = chunk.name;
|
||||
this->shndx = chunk.shndx;
|
||||
this->is_compressed = true;
|
||||
|
||||
this->shdr = chunk.shdr;
|
||||
this->shdr.sh_flags |= SHF_COMPRESSED;
|
||||
this->shdr.sh_addralign = 1;
|
||||
this->shdr.sh_size = sizeof(chdr) + compressor->compressed_size;
|
||||
this->shndx = chunk.shndx;
|
||||
|
||||
// We don't need to keep the original data unless --gdb-index is given.
|
||||
if (!ctx.arg.gdb_index) {
|
||||
this->uncompressed_data.clear();
|
||||
this->uncompressed_data.shrink_to_fit();
|
||||
}
|
||||
// We can discard the uncompressed contents unless --gdb-index is given
|
||||
if (ctx.arg.gdb_index)
|
||||
this->uncompressed_data = std::move(buf);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -3027,6 +3047,8 @@ template class GnuDebuglinkSection<E>;
|
||||
template Chunk<E> *find_chunk(Context<E> &, u32);
|
||||
template Chunk<E> *find_chunk(Context<E> &, std::string_view);
|
||||
template i64 to_phdr_flags(Context<E> &ctx, Chunk<E> *chunk);
|
||||
template ElfSym<E> to_output_esym(Context<E> &, Symbol<E> &, u32, U32<E> *);
|
||||
|
||||
template std::optional<ElfSym<E>>
|
||||
to_output_esym(Context<E> &, Symbol<E> &, u32, U32<E> *);
|
||||
|
||||
} // namespace mold
|
||||
+318
-166
@@ -1,6 +1,7 @@
|
||||
#include "config.h"
|
||||
#include "mold.h"
|
||||
#include "blake3.h"
|
||||
|
||||
#include <blake3.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
@@ -26,8 +27,10 @@ int redo_main(Context<E> &ctx, int argc, char **argv) {
|
||||
return mold_main<ARM64LE>(argc, argv);
|
||||
if (target == ARM64BE::name)
|
||||
return mold_main<ARM64BE>(argc, argv);
|
||||
if (target == ARM32::name)
|
||||
return mold_main<ARM32>(argc, argv);
|
||||
if (target == ARM32LE::name)
|
||||
return mold_main<ARM32LE>(argc, argv);
|
||||
if (target == ARM32BE::name)
|
||||
return mold_main<ARM32BE>(argc, argv);
|
||||
if (target == RV64LE::name)
|
||||
return mold_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::name)
|
||||
@@ -56,8 +59,6 @@ int redo_main(Context<E> &ctx, int argc, char **argv) {
|
||||
return mold_main<LOONGARCH32>(argc, argv);
|
||||
if (target == LOONGARCH64::name)
|
||||
return mold_main<LOONGARCH64>(argc, argv);
|
||||
if (target == ARC::name)
|
||||
return mold_main<ARC>(argc, argv);
|
||||
abort();
|
||||
}
|
||||
|
||||
@@ -206,7 +207,8 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (!file->is_reachable) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file == file && ctx.arg.undefined_glob.find(sym->name())) {
|
||||
if (sym->file == file &&
|
||||
ctx.arg.undefined_glob.find(sym->name()) != -1) {
|
||||
file->is_reachable = true;
|
||||
sym->gc_root = true;
|
||||
break;
|
||||
@@ -217,14 +219,14 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
std::vector<InputFile<E> *> roots;
|
||||
append(roots, ctx.objs);
|
||||
append(roots, ctx.dsos);
|
||||
|
||||
for (InputFile<E> *file : ctx.objs)
|
||||
if (file->is_reachable)
|
||||
roots.push_back(file);
|
||||
for (InputFile<E> *file : roots)
|
||||
if (!file->as_needed)
|
||||
file->is_reachable = true;
|
||||
|
||||
for (InputFile<E> *file : ctx.dsos)
|
||||
if (file->is_reachable)
|
||||
roots.push_back(file);
|
||||
std::erase_if(roots, [](InputFile<E> *file) { return !file->is_reachable; });
|
||||
|
||||
tbb::parallel_for_each(roots, [&](InputFile<E> *file,
|
||||
tbb::feeder<InputFile<E> *> &feeder) {
|
||||
@@ -249,25 +251,15 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
// `VER1`. If it does, that's the symbol we are looking for.
|
||||
template <typename E>
|
||||
static void resolve_default_symver(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
std::vector<InputFile<E> *> files;
|
||||
append(files, ctx.objs);
|
||||
append(files, ctx.dsos);
|
||||
|
||||
if (!sym.file && esym.is_undef() &&
|
||||
file->has_symver[i - file->first_global]) {
|
||||
std::string_view str = file->symbol_strtab.data() + esym.st_name;
|
||||
i64 pos = str.find('@');
|
||||
assert(pos != str.npos);
|
||||
|
||||
std::string_view name = str.substr(0, pos);
|
||||
std::string_view ver = str.substr(pos + 1);
|
||||
|
||||
Symbol<E> *sym2 = get_symbol(ctx, name);
|
||||
if (sym2->file && sym2->file->is_dso && sym2->get_version() == ver)
|
||||
file->symbols[i] = sym2;
|
||||
}
|
||||
}
|
||||
tbb::parallel_for_each(files, [](InputFile<E> *file) {
|
||||
std::span<Symbol<E> *> syms = file->get_global_syms();
|
||||
for (i64 i = 0; i < syms.size(); i++)
|
||||
if (syms[i]->is_versioned_default)
|
||||
syms[i] = (Symbol<E> *)syms[i]->origin;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -312,69 +304,68 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
resolve_default_symver(ctx);
|
||||
mark_live_objects(ctx);
|
||||
|
||||
// Now that we know the exact set of input files that are to be
|
||||
// included in the output file, we want to redo symbol resolution.
|
||||
// This is because symbols defined by object files in archive files
|
||||
// may have risen as a result of mark_live_objects().
|
||||
//
|
||||
// To redo symbol resolution, we want to clear the state first.
|
||||
clear_symbols(ctx);
|
||||
|
||||
// COMDAT elimination needs to happen exactly here.
|
||||
//
|
||||
// It needs to be after archive extraction, otherwise we might
|
||||
// assign COMDAT leader to an archive member that is not supposed to
|
||||
// be extracted.
|
||||
//
|
||||
// It needs to happen before the final symbol resolution, otherwise
|
||||
// we could eliminate a symbol that is already resolved to and cause
|
||||
// dangling references.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
update_minimum(ref.group->owner, file->priority);
|
||||
for (ComdatGroup *g : file->lto_comdat_groups)
|
||||
if (g)
|
||||
update_minimum(g->owner, file->priority);
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
if (ref.group->owner != file->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (InputSection<E> *isec = file->sections[i].get())
|
||||
isec->is_alive = false;
|
||||
});
|
||||
|
||||
// Redo symbol resolution
|
||||
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
file->resolve_symbols(ctx);
|
||||
});
|
||||
|
||||
// Symbols with hidden visibility need to be resolved within the
|
||||
// output file. If a hidden symbol was resolved to a DSO, we'll redo
|
||||
// symbol resolution from scratch with the flag to skip that symbol
|
||||
// next time. This should be rare.
|
||||
std::atomic_bool flag = false;
|
||||
|
||||
std::atomic_bool redo = false;
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file && sym->visibility == STV_HIDDEN) {
|
||||
sym->skip_dso = true;
|
||||
flag = true;
|
||||
redo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
return;
|
||||
if (!redo)
|
||||
break;
|
||||
clear_symbols(ctx);
|
||||
}
|
||||
|
||||
// Now that we know the exact set of input files that are to be
|
||||
// included in the output file, we want to redo symbol resolution.
|
||||
// This is because symbols defined by object files in archive files
|
||||
// may have risen as a result of mark_live_objects().
|
||||
//
|
||||
// To redo symbol resolution, we want to clear the state first.
|
||||
clear_symbols(ctx);
|
||||
|
||||
// COMDAT elimination needs to happen exactly here.
|
||||
//
|
||||
// It needs to be after archive extraction, otherwise we might
|
||||
// assign COMDAT leader to an archive member that is not supposed to
|
||||
// be extracted.
|
||||
//
|
||||
// It needs to happen before the final symbol resolution, otherwise
|
||||
// we could eliminate a symbol that is already resolved to and cause
|
||||
// dangling references.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
update_minimum(ref.group->owner, file->priority);
|
||||
for (ComdatGroup *g : file->lto_comdat_groups)
|
||||
if (g)
|
||||
update_minimum(g->owner, file->priority);
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
if (ref.group->owner != file->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (InputSection<E> *isec = file->sections[i].get())
|
||||
isec->is_alive = false;
|
||||
});
|
||||
|
||||
// Redo symbol resolution
|
||||
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
file->resolve_symbols(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// Do link-time optimization. We pass all IR object files to the compiler
|
||||
@@ -498,7 +489,7 @@ static std::string_view
|
||||
get_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
|
||||
if (ctx.arg.relocatable && !ctx.arg.relocatable_merge_sections)
|
||||
return name;
|
||||
if (ctx.arg.unique && ctx.arg.unique->match(name))
|
||||
if (!ctx.arg.unique.empty() && ctx.arg.unique.find(name) != -1)
|
||||
return name;
|
||||
if (flags & SHF_MERGE)
|
||||
return name;
|
||||
@@ -1051,7 +1042,7 @@ void write_repro_file(Context<E> &ctx) {
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
|
||||
tar->append("response.txt", create_response_file(ctx));
|
||||
tar->append("version.txt", get_mold_version() + "\n");
|
||||
tar->append("version.txt", mold_version + "\n");
|
||||
|
||||
std::unordered_set<std::string_view> seen;
|
||||
|
||||
@@ -1127,35 +1118,41 @@ void check_shlib_undefined(Context<E> &ctx) {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Obtain a list of known shared library names.
|
||||
std::unordered_set<std::string_view> sonames;
|
||||
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
|
||||
sonames.insert(file->soname);
|
||||
// Skip test if we don't have a complete set of shared object files
|
||||
// for the program, because if there's a missing .so, an undefined
|
||||
// symbol might be defined by that library.
|
||||
auto do_test = [&] {
|
||||
std::unordered_set<std::string_view> sonames;
|
||||
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
|
||||
sonames.insert(file->soname);
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
// Skip the file if it depends on a file that we know nothing about.
|
||||
// This is because missing symbols may be provided by that unknown file.
|
||||
for (std::string_view needed : file->get_dt_needed(ctx))
|
||||
if (!sonames.contains(needed))
|
||||
return;
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
for (std::string_view soname : file->get_dt_needed(ctx))
|
||||
if (!sonames.contains(soname))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Check if all undefined symbols have been resolved.
|
||||
for (i64 i = 0; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (esym.is_undef() && !esym.is_weak() && !sym.file &&
|
||||
!is_sparc_register(esym))
|
||||
Error(ctx) << *file << ": --no-allow-shlib-undefined: undefined symbol: "
|
||||
<< sym;
|
||||
}
|
||||
});
|
||||
if (do_test()) {
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
// Check if all undefined symbols have been resolved.
|
||||
for (i64 i = 0; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (esym.is_undef() && !esym.is_weak() && !sym.file &&
|
||||
!is_sparc_register(esym))
|
||||
Error(ctx) << *file << ": --no-allow-shlib-undefined: undefined symbol: "
|
||||
<< sym;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Beyond this point, DSOs that are not referenced directly by any
|
||||
// object file are not needed. They were kept by
|
||||
// SharedFile<E>::mark_live_objects just for this pass. Therefore,
|
||||
// remove unneeded DSOs from the list now.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
file->is_reachable = false;
|
||||
file->is_reachable = !file->as_needed;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms())
|
||||
@@ -1171,10 +1168,6 @@ template <typename E>
|
||||
void check_symbol_types(Context<E> &ctx) {
|
||||
Timer t(ctx, "check_symbol_types");
|
||||
|
||||
std::vector<InputFile<E> *> files;
|
||||
append(files, ctx.objs);
|
||||
append(files, ctx.dsos);
|
||||
|
||||
auto canonicalize = [](u32 ty) -> u32 {
|
||||
if (ty == STT_GNU_IFUNC)
|
||||
return STT_FUNC;
|
||||
@@ -1183,23 +1176,35 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
return ty;
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<E> *file) {
|
||||
auto check = [&](InputFile<E> &file, Symbol<E> &sym,
|
||||
const ElfSym<E> &esym1, const ElfSym<E> &esym2) {
|
||||
if (sym.file && sym.file != &file &&
|
||||
esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
|
||||
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
<< ">>> defined in " << *sym.file << " as "
|
||||
<< stt_to_string<E>(esym1.st_type) << '\n'
|
||||
<< ">>> defined in " << file << " as "
|
||||
<< stt_to_string<E>(esym2.st_type);
|
||||
}
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++)
|
||||
if (Symbol<E> *sym = file->symbols[i];
|
||||
sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (!sym.file || sym.file == file)
|
||||
continue;
|
||||
if (Symbol<E> *sym = file->symbols[i];
|
||||
sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
|
||||
const ElfSym<E> &esym1 = sym.esym();
|
||||
const ElfSym<E> &esym2 = file->elf_syms[i];
|
||||
|
||||
if (esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
|
||||
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
<< ">>> defined in " << *sym.file << " as "
|
||||
<< stt_to_string<E>(esym1.st_type) << '\n'
|
||||
<< ">>> defined in " << *file << " as "
|
||||
<< stt_to_string<E>(esym2.st_type);
|
||||
}
|
||||
if (Symbol<E> *sym = file->symbols2[i])
|
||||
if (sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1297,6 +1302,142 @@ void sort_ctor_dtor(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if a given section contains a DWARF32 debug record.
|
||||
// `isec` must be a .debug_info section.
|
||||
template <typename E>
|
||||
static bool is_dwarf32(Context<E> &ctx, InputSection<E> *isec) {
|
||||
if (!isec)
|
||||
return true;
|
||||
|
||||
if (isec->sh_size < 12) {
|
||||
// The section is too short. This is a user error, but instead of
|
||||
// being nitpicky about it, we simply handle it on a garbage-in,
|
||||
// garbage-out basis.
|
||||
return true;
|
||||
}
|
||||
|
||||
// A .debug_info section contains compilation units (CUs). A 32-bit CU
|
||||
// starts with a 32-bit size field, while a 64-bit CU starts with a
|
||||
// magic number 0xffff'ffff followed by a 64-bit size field.
|
||||
//
|
||||
// Note that size doesn't take the size field itself into account, so
|
||||
// the actual size of a 64-bit CU including the size field is 12 bytes
|
||||
// larger than the value in the size field.
|
||||
u8 buf[12];
|
||||
isec->copy_contents_to(ctx, buf, 12);
|
||||
if (*(U32<E> *)buf != 0xffff'ffff)
|
||||
return true;
|
||||
if (*(U64<E> *)(buf + 4) + 12 == isec->sh_size)
|
||||
return false;
|
||||
|
||||
// An input .debug_info section usually contains a single CU. However,
|
||||
// if the linker combines multiple object files using `-r`, the
|
||||
// resulting object file may have a .debug_info section with as many CUs
|
||||
// as there are in the input files. Therefore, if the first CU doesn't
|
||||
// cover the entire .debug_info section, we need to keep reading until
|
||||
// the end of the section.
|
||||
//
|
||||
// An input .debug_info section may be compressed using zlib or zstd, so
|
||||
// we need to uncompress it before accessing `isec->contents`.
|
||||
isec->uncompress(ctx);
|
||||
|
||||
u8 *p = (u8 *)isec->contents.data() + *(U64<E> *)(buf + 4) + 12;
|
||||
u8 *end = (u8 *)isec->contents.data() + isec->sh_size;
|
||||
|
||||
while (end - p >= 12) {
|
||||
if (*(U32<E> *)p != 0xffff'ffff)
|
||||
return true;
|
||||
p += *(U64<E> *)(p + 4) + 12;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debug sections in an input object file refer to other debug sections in
|
||||
// the same file using section offsets. The offsets are 64 bits in DWARF64
|
||||
// and 32 bits in DWARF32.
|
||||
//
|
||||
// GCC and Clang emit DWARF32 debug info by default even for 64-bit code.
|
||||
// That is, 64-bit values are used for addresses, and 32-bit values for
|
||||
// references between debug sections. This makes sense for ordinary programs
|
||||
// because it reduces the size of the debug info sections.
|
||||
//
|
||||
// You can change the format to DWARF64 by passing `-gdwarf64`. Therefore,
|
||||
// the "right" approach to build an extremely large program in debug mode is
|
||||
// to recompile everything with `-gdwarf64`. However, that’s often not
|
||||
// feasiable for various reasons.
|
||||
//
|
||||
// If we don't do anything about it, a relocation overflow could occur if
|
||||
// any output debug section exceeds 4 GiB in size, making it almost
|
||||
// impossible for users to link an object file compiled without `-gdwarf64`
|
||||
// to an extremely large program.
|
||||
//
|
||||
// This function works around the issue by sorting output debug section
|
||||
// contents so that DWARF32 input sections are at the start of the output
|
||||
// section followed by DWARF64 input sections. By doing this, we can avoid
|
||||
// relocation overflow until the total size of DWARF32 input sections alone
|
||||
// exceeds 4 GiB.
|
||||
template <typename E>
|
||||
void sort_debug_info_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_debug_info_sections");
|
||||
|
||||
// True if mold is running under ctest
|
||||
bool is_in_test = false;
|
||||
if (char *env = getenv("MOLD_DEBUG"); env && env[0])
|
||||
is_in_test = true;
|
||||
|
||||
// Get lists of output debug sections that need sorting
|
||||
std::vector<OutputSection<E> *> vec1;
|
||||
std::vector<MergedSection<E> *> vec2;
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (!(osec->shdr.sh_flags & SHF_ALLOC) && osec->name.starts_with(".debug_"))
|
||||
if (osec->shdr.sh_size >= UINT32_MAX || is_in_test)
|
||||
vec1.push_back(osec);
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
|
||||
if (!(osec->shdr.sh_flags & SHF_ALLOC) && osec->name.starts_with(".debug_"))
|
||||
if (osec->shdr.sh_size >= UINT32_MAX || is_in_test)
|
||||
vec2.push_back(osec.get());
|
||||
|
||||
if (vec1.empty() && vec2.empty())
|
||||
return;
|
||||
|
||||
// Read each input file's .debug_info to record whether the file contains
|
||||
// DWARF32 or DWARF64
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->is_dwarf32 = is_dwarf32(ctx, file->debug_info);
|
||||
});
|
||||
|
||||
// Unless we have a mix of DWARF32 and DWARF64, it doesn't make sense to
|
||||
// sort sections.
|
||||
if (ranges::all_of(ctx.objs, &ObjectFile<E>::is_dwarf32) ||
|
||||
ranges::none_of(ctx.objs, &ObjectFile<E>::is_dwarf32))
|
||||
return;
|
||||
|
||||
// Reorder input sections in the output section so that DWARF32
|
||||
// precededs DWARF64
|
||||
tbb::parallel_for_each(vec1, [&](OutputSection<E> *osec) {
|
||||
ranges::stable_partition(osec->members, [](InputSection<E> *isec) {
|
||||
return isec->file.is_dwarf32;
|
||||
});
|
||||
osec->compute_section_size(ctx);
|
||||
});
|
||||
|
||||
// Reorder strings in .debug_str and the like
|
||||
tbb::parallel_for_each(vec2, [&](MergedSection<E> *osec) {
|
||||
tbb::parallel_for_each(osec->members, [&](MergeableSection<E> *m) {
|
||||
if (m->input_section->file.is_dwarf32)
|
||||
for (SectionFragment<E> *frag : m->fragments)
|
||||
frag->is_32bit = true;
|
||||
});
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(vec2, [&](MergedSection<E> *osec) {
|
||||
osec->compute_section_size(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// .ctors/.dtors serves the same purpose as .init_array/.fini_array,
|
||||
// albeit with very subtly differences. Both contain pointers to
|
||||
// initializer/finalizer functions. The runtime executes them one by one
|
||||
@@ -1371,7 +1512,8 @@ void shuffle_sections(Context<E> &ctx) {
|
||||
auto is_eligible = [](OutputSection<E> *osec) {
|
||||
if (osec) {
|
||||
std::string_view name = osec->name;
|
||||
return name != ".init" && name != ".fini" &&
|
||||
return (osec->shdr.sh_flags & SHF_ALLOC) &&
|
||||
name != ".init" && name != ".fini" &&
|
||||
name != ".ctors" && name != ".dtors" &&
|
||||
name != ".init_array" && name != ".preinit_array" &&
|
||||
name != ".fini_array";
|
||||
@@ -1443,7 +1585,7 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
|
||||
// create_range_extension_thunks is not thread-safe
|
||||
for (Chunk<E> *chunk : std::span(vec.begin(), tail.begin()))
|
||||
chunk->to_osec()->create_range_extension_thunks(ctx, true);
|
||||
chunk->to_osec()->create_range_extension_thunks(ctx);
|
||||
|
||||
tbb::parallel_for_each(tail, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_section_size(ctx);
|
||||
@@ -1618,7 +1760,8 @@ void scan_relocations(Context<E> &ctx) {
|
||||
ctx.got->add_tlsdesc_symbol(ctx, sym);
|
||||
|
||||
if (sym->flags & NEEDS_COPYREL) {
|
||||
if (ctx.arg.z_relro && ((SharedFile<E> *)sym->file)->is_readonly(sym))
|
||||
if (ctx.arg.z_relro && sym->file->is_dso &&
|
||||
((SharedFile<E> *)sym->file)->is_readonly(sym))
|
||||
ctx.copyrel_relro->add_symbol(ctx, sym);
|
||||
else
|
||||
ctx.copyrel->add_symbol(ctx, sym);
|
||||
@@ -1863,8 +2006,8 @@ void apply_version_script(Context<E> &ctx) {
|
||||
|
||||
// Assign versions to symbols specified with `extern "C++"` or
|
||||
// wildcard patterns first.
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
Glob matcher;
|
||||
Glob cpp_matcher;
|
||||
|
||||
// The "local:" label has a special meaning in the version script.
|
||||
// It can appear in any VERSION clause, and it hides matched symbols
|
||||
@@ -1901,18 +2044,14 @@ void apply_version_script(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
std::string_view name = sym->name();
|
||||
i64 match = -1;
|
||||
|
||||
if (std::optional<i64> idx = matcher.find(name))
|
||||
match = std::max(match, *idx);
|
||||
i64 match = matcher.find(name);
|
||||
|
||||
// Match non-mangled symbols against the C++ pattern as well.
|
||||
// Weird, but required to match other linkers' behavior.
|
||||
if (!cpp_matcher.empty()) {
|
||||
if (std::optional<std::string_view> s = demangle_cpp(name))
|
||||
name = *s;
|
||||
if (std::optional<i64> idx = cpp_matcher.find(name))
|
||||
match = std::max(match, *idx);
|
||||
match = std::max(match, cpp_matcher.find(name));
|
||||
}
|
||||
|
||||
if (match != -1)
|
||||
@@ -2090,8 +2229,8 @@ void compute_import_export(Context<E> &ctx) {
|
||||
// exported so that they are interposable. In other words, symbols
|
||||
// that did not match will be bound locally within the output file,
|
||||
// effectively turning them into protected symbols.
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
Glob matcher;
|
||||
Glob cpp_matcher;
|
||||
|
||||
auto handle_match = [&](Symbol<E> *sym) {
|
||||
if (ctx.arg.shared) {
|
||||
@@ -2131,12 +2270,12 @@ void compute_import_export(Context<E> &ctx) {
|
||||
|
||||
std::string_view name = sym->name();
|
||||
|
||||
if (matcher.find(name)) {
|
||||
if (matcher.find(name) != -1) {
|
||||
handle_match(sym);
|
||||
} else if (!cpp_matcher.empty()) {
|
||||
if (std::optional<std::string_view> s = demangle_cpp(name))
|
||||
name = *s;
|
||||
if (cpp_matcher.find(name))
|
||||
if (cpp_matcher.find(name) != -1)
|
||||
handle_match(sym);
|
||||
}
|
||||
}
|
||||
@@ -2414,6 +2553,15 @@ void sort_output_sections(Context<E> &ctx) {
|
||||
sort_output_sections_by_order(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_tls_segment_alignment(Context<E> &ctx) {
|
||||
i64 val = 1;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_flags & SHF_TLS)
|
||||
val = std::max<i64>(val, chunk->shdr.sh_addralign);
|
||||
return val;
|
||||
}
|
||||
|
||||
// This function assigns virtual addresses to output sections. Assigning
|
||||
// addresses is a bit tricky because we want to pack sections as tightly
|
||||
// as possible while not violating the constraints imposed by the hardware
|
||||
@@ -2458,8 +2606,12 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
|
||||
std::vector<Chunk<E> *> &chunks = ctx.chunks;
|
||||
u64 addr = ctx.arg.image_base;
|
||||
|
||||
auto is_tls = [](Chunk<E> *chunk) {
|
||||
return chunk->shdr.sh_flags & SHF_TLS;
|
||||
};
|
||||
|
||||
auto is_tbss = [](Chunk<E> *chunk) {
|
||||
return (chunk->shdr.sh_type == SHT_NOBITS) && (chunk->shdr.sh_flags & SHF_TLS);
|
||||
return (chunk->shdr.sh_flags & SHF_TLS) && (chunk->shdr.sh_type == SHT_NOBITS);
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < chunks.size(); i++) {
|
||||
@@ -2516,6 +2668,12 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// TLS sections are included only in PT_LOAD but also in PT_TLS.
|
||||
// We align the first TLS section so that the PT_TLS segment starts
|
||||
// at an address that meets the segment's alignment requirement.
|
||||
if (is_tls(chunks[i]) && (i == 0 || !is_tls(chunks[i - 1])))
|
||||
addr = align_to(addr, get_tls_segment_alignment(ctx));
|
||||
|
||||
// TLS BSS sections are laid out so that they overlap with the
|
||||
// subsequent non-tbss sections. Overlapping is fine because a STT_TLS
|
||||
// segment contains an initialization image for newly-created threads,
|
||||
@@ -2766,7 +2924,7 @@ i64 set_osec_offsets(Context<E> &ctx) {
|
||||
if (ctx.phdr) {
|
||||
i64 sz = ctx.phdr->shdr.sh_size;
|
||||
ctx.phdr->update_shdr(ctx);
|
||||
if (sz != ctx.phdr->shdr.sh_size)
|
||||
if (sz < ctx.phdr->shdr.sh_size)
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2801,10 +2959,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
}
|
||||
};
|
||||
|
||||
auto stop = [](Symbol<E> *sym, auto &chunk) {
|
||||
auto stop = [](Symbol<E> *sym, auto &chunk, i64 bias = 0) {
|
||||
if (sym && chunk) {
|
||||
sym->set_output_section(chunk);
|
||||
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size;
|
||||
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size + bias;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2841,10 +2999,9 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
// such executable lacks the .dynamic section and thus there's no way
|
||||
// to find ifunc relocations other than these symbols.
|
||||
if (ctx.reldyn && ctx.arg.static_ && !ctx.arg.pie) {
|
||||
stop(ctx.__rel_iplt_start, ctx.reldyn);
|
||||
stop(ctx.__rel_iplt_start, ctx.reldyn,
|
||||
-get_num_irelative_relocs(ctx) * sizeof(ElfRel<E>));
|
||||
stop(ctx.__rel_iplt_end, ctx.reldyn);
|
||||
ctx.__rel_iplt_start->value -=
|
||||
get_num_irelative_relocs(ctx) * sizeof(ElfRel<E>);
|
||||
} else {
|
||||
// If the symbols are not ncessary, we turn them to absolute
|
||||
// symbols at address 0.
|
||||
@@ -2946,17 +3103,13 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// PPC64's _{save,rest}gpr{0,1}_{14,15,16,...,31} symbols
|
||||
if constexpr (is_ppc64v2<E>) {
|
||||
i64 offset = 0;
|
||||
for (std::pair<std::string_view, u32> p : ppc64_save_restore_insns) {
|
||||
std::string_view label = p.first;
|
||||
if (!label.empty())
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
for (i64 i = 0; i < ppc64_save_restore_insns.size(); i++)
|
||||
if (std::string_view label = ppc64_save_restore_insns[i].first;
|
||||
!label.empty())
|
||||
if (Symbol<E> *sym = get_symbol(ctx, label);
|
||||
sym->file == ctx.internal_obj)
|
||||
start(sym, ctx.extra.save_restore, offset);
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
start(sym, ctx.extra.save_restore, i * 4);
|
||||
|
||||
// __start_ and __stop_ symbols
|
||||
for (Chunk<E> *chunk : sections) {
|
||||
@@ -3004,25 +3157,22 @@ template <typename E>
|
||||
void compress_debug_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "compress_debug_sections");
|
||||
|
||||
// Since this pass is embarassingly parallel, we want to use all
|
||||
// available cores.
|
||||
ctx.global_limit.reset();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.chunks.size(), [&](i64 i) {
|
||||
Chunk<E> &chunk = *ctx.chunks[i];
|
||||
|
||||
if ((chunk.shdr.sh_flags & SHF_ALLOC) || chunk.shdr.sh_size == 0 ||
|
||||
!chunk.name.starts_with(".debug"))
|
||||
return;
|
||||
|
||||
Chunk<E> *comp = new CompressedSection<E>(ctx, chunk);
|
||||
ctx.chunk_pool.emplace_back(comp);
|
||||
ctx.chunks[i] = comp;
|
||||
if (!(chunk.shdr.sh_flags & SHF_ALLOC) && chunk.shdr.sh_size &&
|
||||
chunk.name.starts_with(".debug_")) {
|
||||
Chunk<E> *comp = new CompressedSection<E>(ctx, chunk);
|
||||
ctx.chunk_pool.emplace_back(comp);
|
||||
ctx.chunks[i] = comp;
|
||||
}
|
||||
});
|
||||
|
||||
if (ctx.shstrtab)
|
||||
ctx.shstrtab->update_shdr(ctx);
|
||||
|
||||
if (ctx.ehdr)
|
||||
ctx.ehdr->update_shdr(ctx);
|
||||
if (ctx.shdr)
|
||||
ctx.shdr->update_shdr(ctx);
|
||||
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
}
|
||||
|
||||
// BLAKE3 is a cryptographic hash function just like SHA256.
|
||||
@@ -3211,9 +3361,10 @@ void write_separate_debug_file(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
append(ctx.chunks, ctx.debug_chunks);
|
||||
sort_debug_info_sections(ctx);
|
||||
|
||||
// Handle --compress-debug-info
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
|
||||
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE)
|
||||
compress_debug_sections(ctx);
|
||||
|
||||
// Write to the debug info file as if it were a regular output file.
|
||||
@@ -3390,6 +3541,7 @@ template void create_reloc_sections(Context<E> &);
|
||||
template void copy_chunks(Context<E> &);
|
||||
template void construct_relr(Context<E> &);
|
||||
template void sort_dynsyms(Context<E> &);
|
||||
template void sort_debug_info_sections(Context<E> &);
|
||||
template void create_output_symtab(Context<E> &);
|
||||
template void apply_version_script(Context<E> &);
|
||||
template void parse_symbol_version(Context<E> &);
|
||||
|
||||
@@ -122,10 +122,6 @@ i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
if (sym.is_absolute())
|
||||
return INT64_MAX;
|
||||
|
||||
// Likewise, relocations against weak undefined symbols won't be relaxed.
|
||||
if (sym.esym().is_undef_weak())
|
||||
return INT64_MAX;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <tbb/version.h>
|
||||
@@ -28,8 +28,7 @@ void cleanup() {
|
||||
// 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
|
||||
// region, the failure of the write is reported as a SIGBUS. 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.
|
||||
@@ -74,7 +73,7 @@ void install_signal_handler() {
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
|
||||
// OneTBB 2021.9.0 has the interface version 12090.
|
||||
if (TBB_runtime_interface_version() < 12090) {
|
||||
if (TBB_runtime_interface_version() <= 12090) {
|
||||
sigabrt_msg = "mold: aborted\n"
|
||||
"mold: mold with libtbb version 2021.9.0 or older is known to be unstable "
|
||||
"under heavy load. Your libtbb version is " +
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "config.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <csignal>
|
||||
@@ -104,7 +105,7 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
setenv("LD_PRELOAD", dso_path.c_str(), 1);
|
||||
setenv("MOLD_PATH", self.c_str(), 1);
|
||||
|
||||
// If ld, ld.lld or ld.gold is specified, run mold itself
|
||||
// If ld, ld.lld or ld.gold is specified, run mold instead
|
||||
if (std::string cmd = path_filename(argv[2]);
|
||||
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
|
||||
std::vector<char *> args;
|
||||
|
||||
+117
-68
@@ -20,8 +20,8 @@
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64LE || MOLD_ARM64BE || MOLD_PPC32 || \
|
||||
MOLD_PPC64V1 || MOLD_PPC64V2
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE || MOLD_ARM64LE || MOLD_ARM64BE || \
|
||||
MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -45,68 +45,67 @@ static constexpr i64 max_thunk_size = 1024 * 1024;
|
||||
static constexpr i64 thunk_align = 16;
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, bool first_pass, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
// If the target section is in the same output section but
|
||||
// hasn't got any address yet, that's unreacahble.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (isec2 && isec.output_section == isec2->output_section &&
|
||||
isec2->offset == -1)
|
||||
static bool
|
||||
requires_thunk(Context<E> &ctx, InputSection<E> &isec, const ElfRel<E> &rel,
|
||||
bool first_pass) {
|
||||
if (!is_func_call_rel(rel))
|
||||
return false;
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
|
||||
// We don't know about the final file layout on the first pass, so
|
||||
// we assume pessimistically that all out-of-section relocations are
|
||||
// out-of-range. Excessive thunks will be removed on the second pass.
|
||||
if (first_pass) {
|
||||
// On the first pass, we pessimistically assume that all out-of-section
|
||||
// relocations are out of range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
// 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 true;
|
||||
|
||||
// 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;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 reachable.
|
||||
if constexpr (is_arm32<E>)
|
||||
if (bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_PLT32 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_THM_JUMP24 && !is_thumb))
|
||||
return true;
|
||||
|
||||
// On PowerPC, all PLT calls go through range extension thunks.
|
||||
if constexpr (is_ppc<E>)
|
||||
if (sym.has_plt(ctx))
|
||||
return true;
|
||||
|
||||
// PowerPC before Power9 lacks PC-relative load/store instructions.
|
||||
// Functions compiled for Power9 or earlier assume that r2 points to
|
||||
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
|
||||
// We need a thunk to recompute r2 for interworking.
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
if ((rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
|
||||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc()))
|
||||
return true;
|
||||
|
||||
// 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 = get_addend(isec, rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -branch_distance<E> <= val && val < branch_distance<E>;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool needs_shim(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
// 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 (is_arm32<E>) {
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
return (rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_PLT32 && is_thumb);
|
||||
}
|
||||
|
||||
// On PowerPC, all PLT calls go through range extension thunks.
|
||||
if constexpr (is_ppc32<E> || is_ppc64v1<E>)
|
||||
return sym.has_plt(ctx);
|
||||
|
||||
// PowerPC before Power9 lacks PC-relative load/store instructions.
|
||||
// Functions compiled for Power9 or earlier assume that r2 points to
|
||||
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
|
||||
// We need to a thunk to recompute r2 for interworking.
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
return sym.has_plt(ctx) ||
|
||||
(rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
|
||||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc());
|
||||
return false;
|
||||
return val < -branch_distance<E> || branch_distance<E> <= val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
bool first_pass) {
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
std::span<InputSection<E> *> m = members;
|
||||
if (m.empty())
|
||||
return;
|
||||
@@ -122,7 +121,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
//
|
||||
// Input sections between B and C are in the current batch.
|
||||
// Input sections between B and C are the current batch.
|
||||
//
|
||||
// A is the input section with the smallest address than can reach
|
||||
// from the current batch.
|
||||
@@ -181,33 +180,21 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, thunk_align);
|
||||
thunks.emplace_back(std::make_unique<Thunk<E>>(*this, offset));
|
||||
|
||||
Thunk<E> &thunk = *thunks.back();
|
||||
std::mutex mu;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
// entries in the new thunk.
|
||||
std::mutex mu;
|
||||
|
||||
tbb::parallel_for(b, c, [&](i64 i) {
|
||||
InputSection<E> &isec = *m[i];
|
||||
|
||||
for (const ElfRel<E> &rel : isec.get_rels(ctx)) {
|
||||
if (!is_func_call_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 we can directly branch to the destination.
|
||||
if (is_reachable(ctx, first_pass, isec, sym, rel) &&
|
||||
!needs_shim(ctx, sym, rel))
|
||||
continue;
|
||||
|
||||
// Add the symbol to the current thunk if it's not added already.
|
||||
if (!sym.flags.test_and_set()) {
|
||||
std::scoped_lock lock(mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
if (requires_thunk(ctx, isec, rel, true)) {
|
||||
if (Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
!sym.flags.test_and_set()) {
|
||||
std::scoped_lock lock(mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -226,6 +213,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
b = c;
|
||||
}
|
||||
|
||||
// Reset flags for future use
|
||||
for (; t < thunks.size(); t++)
|
||||
for (Symbol<E> *sym : thunks[t]->symbols)
|
||||
sym->flags = 0;
|
||||
@@ -233,15 +221,75 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
this->shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
// create_range_extension_thunks() creates thunks with a pessimistic
|
||||
// assumption that all out-of-section references are out of range.
|
||||
// After computing output section addresses, we revisit all thunks to
|
||||
// remove unneeded entries from them.
|
||||
//
|
||||
// We create more thunks than necessary and then eliminate some of
|
||||
// them later, instead of just creating thunks at this stage. This is
|
||||
// because we can safely shrink sections after assigning addresses to
|
||||
// them without worrying about making existing references to thunks go
|
||||
// out of range. On the other hand, if we insert thunks after
|
||||
// assigning addresses to sections, references to thunks could become
|
||||
// out of range due to the new extra gaps for thunks. Thus, the
|
||||
// creation of thunks is a two-pass process.
|
||||
template <>
|
||||
void remove_redundant_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "remove_redundant_thunks");
|
||||
set_osec_offsets(ctx);
|
||||
|
||||
// Gather output executable sections
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
osec->create_range_extension_thunks(ctx, false);
|
||||
sections.push_back(osec);
|
||||
|
||||
// Mark all symbols that actually need range extension thunks
|
||||
for (OutputSection<E> *osec : sections) {
|
||||
tbb::parallel_for_each(osec->members, [&](InputSection<E> *isec) {
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx))
|
||||
if (requires_thunk(ctx, *isec, rel, false))
|
||||
isec->file.symbols[rel.r_sym]->flags.test_and_set();
|
||||
});
|
||||
}
|
||||
|
||||
// Remove symbols from thunks if they don't actually need range
|
||||
// extension thunks
|
||||
std::vector<Symbol<E> *> syms;
|
||||
|
||||
for (OutputSection<E> *osec : sections) {
|
||||
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks) {
|
||||
append(syms, thunk->symbols);
|
||||
std::erase_if(thunk->symbols, [&](Symbol<E> *sym) { return !sym->flags; });
|
||||
}
|
||||
}
|
||||
|
||||
// Reset flags for future use
|
||||
for (Symbol<E> *sym : syms)
|
||||
sym->flags = 0;
|
||||
|
||||
// Recompute section sizes
|
||||
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
|
||||
std::span<InputSection<E> *> m = osec->members;
|
||||
std::span<std::unique_ptr<Thunk<E>>> t = osec->thunks;
|
||||
i64 offset = 0;
|
||||
|
||||
while (!m.empty() || !t.empty()) {
|
||||
if (!m.empty() && (t.empty() || m[0]->offset < t[0]->offset)) {
|
||||
offset = align_to(offset, 1 << m[0]->p2align);
|
||||
m[0]->offset = offset;
|
||||
offset += m[0]->sh_size;
|
||||
m = m.subspan(1);
|
||||
} else {
|
||||
offset = align_to(offset, thunk_align);
|
||||
t[0]->offset = offset;
|
||||
offset += t[0]->size();
|
||||
t = t.subspan(1);
|
||||
}
|
||||
}
|
||||
osec->shdr.sh_size = offset;
|
||||
});
|
||||
}
|
||||
|
||||
// When applying relocations, we want to know the address in a reachable
|
||||
@@ -259,7 +307,8 @@ void gather_thunk_addresses(Context<E> &ctx) {
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
sections.push_back(osec);
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
sections.push_back(osec);
|
||||
|
||||
ranges::stable_sort(sections, {}, [](OutputSection<E> *x) {
|
||||
return x->shdr.sh_addr;
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ u64 get_tp_addr(const ElfPhdr<E> &phdr) {
|
||||
// and %a0/%a1 on s390x) refers to past the end of the TLS block for
|
||||
// historical reasons. TLVs are accessed with negative offsets from TP.
|
||||
return align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
|
||||
} else if constexpr (is_arm<E> || is_sh4<E> || is_arc<E>) {
|
||||
} else if constexpr (is_arm<E> || is_sh4<E>) {
|
||||
// On ARM and SH4, the runtime appends two words at the beginning
|
||||
// of TLV template image when copying TLVs to the TLS block, so we need
|
||||
// to offset it.
|
||||
|
||||
+8
-8
@@ -30,8 +30,8 @@ endif()
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS)
|
||||
list(APPEND QEMU_ARCHS
|
||||
x86_64 i386 arm aarch64 aarch64_be ppc ppc64 ppc64le sparc64 sh4 sh4eb
|
||||
s390x riscv64 riscv32 m68k loongarch64 arc)
|
||||
x86_64 i386 arm armeb aarch64 aarch64_be ppc ppc64 ppc64le sparc64
|
||||
sh4 sh4eb s390x riscv64 riscv32 m68k loongarch64)
|
||||
|
||||
LIST(APPEND TRIPLES
|
||||
x86_64-linux-gnu
|
||||
@@ -39,6 +39,7 @@ if(MOLD_ENABLE_QEMU_TESTS)
|
||||
aarch64-linux-gnu
|
||||
aarch64_be-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
armeb-linux-gnueabihf
|
||||
riscv64-linux-gnu
|
||||
powerpc-linux-gnu
|
||||
powerpc64-linux-gnu
|
||||
@@ -49,8 +50,7 @@ if(MOLD_ENABLE_QEMU_TESTS)
|
||||
sh4aeb-linux-gnu
|
||||
riscv32-linux-gnu
|
||||
m68k-linux-gnu
|
||||
loongarch64-linux-gnu
|
||||
arc-linux-gnu)
|
||||
loongarch64-linux-gnu)
|
||||
|
||||
foreach(ARCH IN LISTS QEMU_ARCHS)
|
||||
find_program(HAS_qemu-${ARCH} qemu-${ARCH})
|
||||
@@ -122,6 +122,10 @@ if(${MACHINE} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
|
||||
add_target(arm arm-linux-gnueabihf)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "armeb" OR (HAS_qemu-armeb AND HAS_armeb-linux-gnueabihf-gcc))
|
||||
add_target(armeb armeb-linux-gnueabihf)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "riscv64" OR (HAS_qemu-riscv64 AND HAS_riscv64-linux-gnu-gcc))
|
||||
add_target(riscv64 riscv64-linux-gnu)
|
||||
endif()
|
||||
@@ -185,7 +189,3 @@ endif()
|
||||
if(${MACHINE} STREQUAL "loongarch64" OR (HAS_qemu-loongarch64 AND HAS_loongarch64-linux-gnu-gcc))
|
||||
add_target(loongarch64 loongarch64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "arc" OR (HAS_qemu-loongarch64 AND HAS_arc-linux-gnu-gcc))
|
||||
add_target(arc arc-linux-gnu)
|
||||
endif()
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
not $CC -B. -o $t/exe $t/a.o -mbe32 |& grep 'be32 is not supported'
|
||||
+18
-23
@@ -1,33 +1,28 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
int foo() {
|
||||
return 0;
|
||||
}
|
||||
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
|
||||
int fn1() { return 42; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
__attribute__((weak)) int foo();
|
||||
|
||||
int bar() {
|
||||
if (foo) return foo();
|
||||
return 0;
|
||||
}
|
||||
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc - -L$t -lfoo
|
||||
int fn1();
|
||||
int fn2() { return fn1(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -xc -c -o $t/c.o -
|
||||
int bar();
|
||||
|
||||
int main() {
|
||||
return bar();
|
||||
}
|
||||
cat <<EOF | $CC -o $t/libbaz.so -shared -fPIC -Wl,-soname,libbaz.so -xc - -L$t -lbar
|
||||
int fn2();
|
||||
int fn3() { return fn2(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/libfoo.so $t/a.o
|
||||
$CC -B. -shared -o $t/libbar.so $t/b.o
|
||||
$CC -B. -o $t/exe $t/c.o -L$t -Wl,--as-needed -lfoo -lbar
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int fn3();
|
||||
int main() { fn3(); }
|
||||
EOF
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
not grep libfoo.so $t/log
|
||||
grep libbar.so $t/log
|
||||
$CC -B. -o $t/exe $t/a.o -L$t -Wl,--as-needed -lbaz -lbar
|
||||
readelf -W --dynamic $t/exe > $t/log2
|
||||
|
||||
not grep libfoo $t/log2
|
||||
not grep libbar $t/log2
|
||||
grep libbaz $t/log2
|
||||
+3
-2
@@ -7,6 +7,7 @@ canonical_name() {
|
||||
case $1 in
|
||||
i?86) echo i686 ;;
|
||||
amd64) echo x86_64 ;;
|
||||
armeb*) echo armeb ;;
|
||||
arm*) echo arm ;;
|
||||
powerpc) echo ppc ;;
|
||||
powerpc64) echo ppc64 ;;
|
||||
@@ -64,7 +65,7 @@ t=$TESTDIR/$testname
|
||||
mkdir -p $t
|
||||
|
||||
case $MACHINE in
|
||||
x86_64 | i686 | arm)
|
||||
x86_64 | i686 | arm | armeb)
|
||||
tlsdesc_opt=-mtls-dialect=gnu2 ;;
|
||||
aarch64 | riscv* | loongarch*)
|
||||
tlsdesc_opt=-mtls-dialect=desc ;;
|
||||
@@ -101,7 +102,7 @@ supports_ifunc() {
|
||||
|
||||
supports_tlsdesc() {
|
||||
# musl's tlsdesc on arm32 seems to be broken
|
||||
[ $MACHINE = arm ] && is_musl && return 1
|
||||
[[ $MACHINE = arm* ]] && is_musl && return 1
|
||||
|
||||
# FreeBSD's loader doesn't seem to support TLSDESC relocs in an executable
|
||||
[ "$(uname)" = FreeBSD ] && return 1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# arm-linux-gnueabihf-objcopy crashes on x86-64
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
[ $MACHINE = riscv32 ] && skip
|
||||
|
||||
command -v zstdcat >& /dev/null || skip
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Currently, CREL is not supported on REL-type targets
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
|
||||
[ "$CC" = cc ] || skip
|
||||
|
||||
@@ -6,16 +6,16 @@ cat <<EOF > $t/a.h
|
||||
#define B 99
|
||||
EOF
|
||||
|
||||
cat <<EOF | $GCC -o $t/b.o -c -xc - -I$t -g3
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -I$t -g3
|
||||
#include "a.h"
|
||||
extern int z();
|
||||
int main () { return z() - 122; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $GCC -o $t/c.o -c -xc - -I$t -g3
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc - -I$t -g3
|
||||
#include "a.h"
|
||||
int z() { return A + B; }
|
||||
EOF
|
||||
|
||||
$GCC -B. -o $t/exe $t/b.o $t/c.o
|
||||
$CC -B. -o $t/exe $t/b.o $t/c.o
|
||||
$OBJDUMP --dwarf=macro $t/exe | not grep 'DW_MACRO_import -.* 0x0$'
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
echo '{ global: foo; local: *; };' > $t/b.ver
|
||||
|
||||
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--default-symver -Wl,--version-script=$t/b.ver
|
||||
readelf --dyn-syms $t/c.so | grep -F ' foo@@c.so'
|
||||
+2
-2
@@ -22,8 +22,8 @@ void bar();
|
||||
int main() { bar(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so -Wl,--allow-shlib-undefined
|
||||
readelf --dynamic $t/exe1 | grep 'Dependency audit library:..*/a.so'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo
|
||||
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo -Wl,--allow-shlib-undefined
|
||||
readelf --dynamic $t/exe2 | grep 'Dependency audit library:..*foo:.*/a.so'
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
# musl libc does not support init/fini on ARM
|
||||
# https://github.com/rui314/mold/issues/951
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
|
||||
[[ $MACHINE = arm* -o $MACHINE = aarch64 ]] && is_musl && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
@@ -6,8 +6,11 @@ cat <<EOF | $CC -c -o $t/a.o -x assembler -
|
||||
_start:
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o
|
||||
readelf --sections $t/exe | not grep -F .interp
|
||||
./mold -o $t/exe1 $t/a.o
|
||||
readelf -WS $t/exe1 | not grep -F .interp
|
||||
|
||||
./mold -o $t/exe $t/a.o --dynamic-linker=/foo/bar
|
||||
readelf --sections $t/exe | grep -F .interp
|
||||
./mold -o $t/exe2 $t/a.o --dynamic-linker=/foo/bar
|
||||
readelf -WS $t/exe2 | grep -F .interp
|
||||
|
||||
./mold -o $t/exe3 $t/a.o --dynamic-linker=/foo/bar -static
|
||||
readelf -WS $t/exe3 | not grep -F .interp
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -
|
||||
#include <cstdio>
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $GCC -c -o $t/a.o -xc -
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((section(".gnu.warning.foo")))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# ARM32's strip command crashes on the output of this test for some reason.
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
|
||||
strip=$STRIP
|
||||
command -v $strip >& /dev/null || skip
|
||||
|
||||
@@ -9,4 +9,4 @@ echo 'int main() {}' | clang -c -o $t/a.o -xc -
|
||||
echo 'void foo() {}' | clang -c -o $t/b.o -xc - -flto
|
||||
|
||||
not ./mold -o /dev/null $t/a.o $t/b.o |&
|
||||
grep "b.o: don't know how to handle this LTO object file"
|
||||
grep "b.o: unable to handle this LTO object file"
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' |
|
||||
seq 1 100000 | sed 's/.*/.section .data.&,"aw"\n.word 0\n/g' |
|
||||
$CC -c -xassembler -o $t/a.o -
|
||||
|
||||
cat <<'EOF' | $CC -c -xc -o $t/b.o -
|
||||
File renamed without changes.
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
seq 1 100000 | sed 's/.*/.globl foo&\n.section .foo&,"aw"\nfoo&:.word 0\n/g' |
|
||||
$CC -c -xassembler -o $t/a.o -
|
||||
|
||||
cat <<'EOF' | $CC -c -xc -o $t/b.o - -fPIC
|
||||
extern int foo100000;
|
||||
int bar() { return foo100000; }
|
||||
EOF
|
||||
|
||||
not $CC -B. -shared -o $t/c.so $t/a.o $t/b.o |& grep -F 'too many output sections'
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ "$(uname)" = FreeBSD ] && skip
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
|
||||
void foo();
|
||||
void bar() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc - -L$t -lbar
|
||||
void baz();
|
||||
void foo() { baz(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -c -xc -
|
||||
int bar();
|
||||
int main() { bar(); }
|
||||
EOF
|
||||
|
||||
not $CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lfoo -lbar |&
|
||||
grep 'libfoo.so: --no-allow-shlib-undefined: undefined symbol: baz'
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ "$(uname)" = FreeBSD ] && skip
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ "$(uname)" = FreeBSD ] && skip
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ "$(uname)" = FreeBSD ] && skip
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
|
||||
void foo();
|
||||
void bar() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbaz.so -xc - -L$t -lbar
|
||||
void bar();
|
||||
void baz() { bar(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -c -xc -
|
||||
int baz();
|
||||
int main() { baz(); }
|
||||
EOF
|
||||
|
||||
mv $t/libfoo.so $t/libfoo.so.bak
|
||||
echo | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lbar -lbaz
|
||||
|
||||
not $CC -B. -o $t/exe2 $t/a.o -Wl,--no-allow-shlib-undefined \
|
||||
-L$t -lfoo -lbar -lbaz |& grep 'no-allow-shlib-undefined: undefined symbol: foo'
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ "$(uname)" = FreeBSD ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
void f1() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
|
||||
void f1();
|
||||
void f2() { f1(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
|
||||
void f1();
|
||||
void f3() { f1(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/libfoo.so -shared $t/a.o
|
||||
$CC -B. -o $t/libbar.so -shared $t/b.o -L$t -lfoo
|
||||
$CC -B. -o $t/libbaz.so -shared $t/c.o
|
||||
|
||||
|
||||
cat <<EOF | $CC -o $t/d.o -c -xc - -fPIC
|
||||
void f2();
|
||||
void f3();
|
||||
int main() { f2(); f3(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -L$t -lbar -lbaz
|
||||
+1
-1
@@ -3,10 +3,10 @@
|
||||
|
||||
[ $MACHINE = i686 ] && skip
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = arm ] && skip
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -shared -o $t/a.so -xc -
|
||||
|
||||
Binary file not shown.
@@ -6,12 +6,12 @@ nm mold | grep '__tsan_init' && skip
|
||||
|
||||
test_cflags -g3 -gz || skip
|
||||
|
||||
cat <<EOF | $GCC -c -o $t/a.o -xc - -g3 -gz
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc - -g3 -gz
|
||||
#include <stdio.h>
|
||||
void hello() { printf("Hello world\n"); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $GCC -c -o $t/b.o -xc - -g3 -gz
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc - -g3 -gz
|
||||
void hello();
|
||||
int main() { hello(); }
|
||||
EOF
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep '__tsan_init' && skip
|
||||
|
||||
seq 1 80000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' |
|
||||
seq 1 80000 | sed 's/.*/.section .data.&,"aw"\n.word 0\n/g' |
|
||||
$CC -c -xassembler -o $t/a.o -
|
||||
|
||||
cat <<'EOF' | $CC -c -xc -o $t/b.o -
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
# ARM assembler has a differnet grammar than the others.
|
||||
# Concretely speaking, ARM as uses "@" as a start of a comment.
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
|
||||
# All data symbols need to be aligned to 2 byte boundaries on s390x,
|
||||
# so rodata.str1.1 in this file is invalid on s390x.
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
nm mold | grep '__tsan_init' && skip
|
||||
command -v flock >& /dev/null || skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
int x = 1;
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
$CC -o $t/a.o -c -g -gdwarf32 $t/a.c || skip
|
||||
|
||||
cat <<EOF > $t/b.c
|
||||
int y = 1;
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CC -o $t/b.o -c -g -gdwarf64 $t/b.c
|
||||
|
||||
MOLD_DEBUG=1 $CC -B. -o $t/exe $t/a.o $t/b.o -g -Wl,--separate-debug-file
|
||||
flock $t/exe.dbg true
|
||||
readelf -p .debug_line_str $t/exe.dbg > $t/str
|
||||
|
||||
if grep -Fw a.c $t/str; then
|
||||
grep -A10 -Fw a.c $t/str | grep -Fw b.c
|
||||
fi
|
||||
@@ -17,7 +17,7 @@ $CC -c -o $t/a.o $t/a.c -g
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,--separate-debug-file
|
||||
readelf -SW $t/exe1 | grep -F .gnu_debuglink
|
||||
|
||||
flock $t/exe1 true
|
||||
flock $t/exe1.dbg true
|
||||
gdb $t/exe1 -ex 'list main' -ex 'quit' | grep -F printf
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ $CC -c -o $t/a.o $t/a.c -g
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,--separate-debug-file,--no-build-id
|
||||
readelf -SW $t/exe2 | grep -F .gnu_debuglink
|
||||
|
||||
flock $t/exe2 true
|
||||
flock $t/exe2.dbg true
|
||||
gdb $t/exe2 -ex 'list main' -ex 'quit' | grep -F printf
|
||||
|
||||
|
||||
@@ -33,6 +33,6 @@ $CC -c -o $t/a.o $t/a.c -g
|
||||
$CC -B. -o $t/exe3 $t/a.o -Wl,--separate-debug-file,--compress-debug-sections=zlib
|
||||
readelf -SW $t/exe3 | grep -F .gnu_debuglink
|
||||
|
||||
flock $t/exe3 true
|
||||
flock $t/exe3.dbg true
|
||||
readelf -W --sections $t/exe3.dbg | grep '\.debug_info .*C'
|
||||
gdb $t/exe3 -ex 'list main' -ex 'quit' | grep -F printf
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -g -gdwarf32 -Wl,--compress-debug-sections=zlib || skip
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -g -gdwarf64 -Wl,--compress-debug-sections=zstd || skip
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
# Test if DWARF32 precedes DWARF64 in the output .debug_info
|
||||
MOLD_DEBUG=1 $CC -B. -o $t/exe1 $t/a.o $t/b.o -g -Wl,-Map=$t/map1
|
||||
grep -A10 -F '/a.o:(.debug_info)' $t/map1 | grep -F '/b.o:(.debug_info)'
|
||||
|
||||
MOLD_DEBUG=1 $CC -B. -o $t/exe2 $t/b.o $t/a.o -g -Wl,-Map=$t/map2
|
||||
grep -A10 -F '/a.o:(.debug_info)' $t/map2 | grep -F '/b.o:(.debug_info)'
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
nm mold | grep '__tsan_init' && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -g -gdwarf64 || skip
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -g -gdwarf32
|
||||
void bar() {}
|
||||
EOF
|
||||
|
||||
./mold -r -o $t/c.o $t/a.o $t/b.o
|
||||
|
||||
cat <<EOF | $CC -o $t/d.o -c -xc - -g -gdwarf64
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
MOLD_DEBUG=1 $CC -B. -o $t/exe1 $t/c.o $t/d.o -g -Wl,-Map=$t/map1
|
||||
grep -A10 -F '/c.o:(.debug_info)' $t/map1 | grep -F '/d.o:(.debug_info)'
|
||||
|
||||
MOLD_DEBUG=1 $CC -B. -o $t/exe2 $t/d.o $t/c.o -g -Wl,-Map=$t/map2
|
||||
grep -A10 -F '/c.o:(.debug_info)' $t/map2 | grep -F '/d.o:(.debug_info)'
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
int x = 1;
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
$CC -o $t/a.o -c -g -gdwarf32 $t/a.c || skip
|
||||
|
||||
cat <<EOF > $t/b.c
|
||||
int y = 1;
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CC -o $t/b.o -c -g -gdwarf64 $t/b.c
|
||||
|
||||
# Test if DWARF32 precedes DWARF64 in the output .debug_info
|
||||
|
||||
MOLD_DEBUG=1 $CC -B. -o $t/exe1 $t/a.o $t/b.o -g -Wl,-Map=$t/map1
|
||||
grep -A10 -F '/a.o:(.debug_info)' $t/map1 | grep -F '/b.o:(.debug_info)'
|
||||
readelf -p .debug_line_str $t/exe1 > $t/str1
|
||||
|
||||
if grep -Fw a.c $t/str1; then
|
||||
grep -A10 -Fw a.c $t/str1 | grep -Fw b.c
|
||||
fi
|
||||
|
||||
|
||||
MOLD_DEBUG=1 $CC -B. -o $t/exe2 $t/b.o $t/a.o -g -Wl,-Map=$t/map2
|
||||
grep -A10 -F '/a.o:(.debug_info)' $t/map2 | grep -F '/b.o:(.debug_info)'
|
||||
readelf -p .debug_line_str $t/exe2 > $t/str2
|
||||
|
||||
if grep -Fw a.c $t/str2; then
|
||||
grep -A10 -Fw a.c $t/str2 | grep -Fw b.c
|
||||
fi
|
||||
|
||||
|
||||
if [ -z "$QEMU" ] && command -v gdb; then
|
||||
gdb $t/exe1 -ex 'list main' -ex 'quit' | grep 'int y = 1'
|
||||
gdb $t/exe2 -ex 'list main' -ex 'quit' | grep 'int y = 1'
|
||||
fi
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
|
||||
__asm__(".symver foo_,foo@@VERSION");
|
||||
void foo_() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/c.version
|
||||
VERSION { global: foo; local: *; };
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/libfoo.so $t/a.o
|
||||
$CC -B. -shared -o $t/libbar.so $t/b.o -Wl,--version-script=$t/c.version
|
||||
|
||||
cat <<EOF | $CC -o $t/d.o -c -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
__asm__(".symver foo,foo@VERSION");
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -L$t -lfoo -lbar
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = armeb ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = armeb ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = armeb ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
mkdir -p $t/bin $t/sysroot/foo
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $GCC -o $t/a.o -c -xassembler -
|
||||
cat <<EOF | $CC -o $t/a.o -c -xassembler - || skip
|
||||
.globl foo
|
||||
.tls_common foo,4,4
|
||||
EOF
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc -
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
|
||||
static _Thread_local int foo[10000] = { 3, [9999] = 5 };
|
||||
|
||||
int get_foo(int idx) { return foo[idx]; }
|
||||
@@ -9,7 +9,7 @@ EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
cat <<EOF | $GCC -fPIC -c -o $t/c.o -xc -
|
||||
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
|
||||
Loaded 100 of 391 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user