mirror of
https://github.com/rui314/mold.git
synced 2026-11-12 14:59:43 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec04bfda9d | ||
|
|
8cd85aaa29 | ||
|
|
01bc0b522d | ||
|
|
48e777ad7b | ||
|
|
0955c30852 | ||
|
|
02ec7156c1 | ||
|
|
c7c8583946 | ||
|
|
08b0a1629d | ||
|
|
02b439af48 | ||
|
|
a8cd2e84e0 | ||
|
|
a25226eb13 | ||
|
|
91f9c4d167 | ||
|
|
416b0d336f | ||
|
|
ca4738d5e2 | ||
|
|
4281f45f06 | ||
|
|
08be7ff2d2 | ||
|
|
47030cf6ca | ||
|
|
a5e5ad75fe | ||
|
|
0ce1c78012 | ||
|
|
a250aea2f9 | ||
|
|
1fa4e34cec | ||
|
|
1290a02a4e | ||
|
|
10b69883bd | ||
|
|
fa8e95a289 | ||
|
|
6373eb05a7 | ||
|
|
3ec4b1787e | ||
|
|
74129bfc86 | ||
|
|
595f98da78 | ||
|
|
2649b69e95 | ||
|
|
d20dc21e2a | ||
|
|
7ac0816aa9 | ||
|
|
154b6b047d | ||
|
|
9a233df7e2 | ||
|
|
7b2cb47727 | ||
|
|
d683e8aa0b | ||
|
|
5ec3d2dcc2 | ||
|
|
edcf252603 | ||
|
|
024b03791a | ||
|
|
230ef819c2 | ||
|
|
5bdf48695a | ||
|
|
78fea38b12 | ||
|
|
4510f4af81 | ||
|
|
a3480e8a2b | ||
|
|
5396667ad5 | ||
|
|
f0f61cc7ad | ||
|
|
b237fd1ae9 | ||
|
|
2b67afb8a3 | ||
|
|
20fa8d56f5 | ||
|
|
ed1e3c06a2 | ||
|
|
fdbe7eecf9 | ||
|
|
16ad212919 | ||
|
|
5c0cce3d6c | ||
|
|
ec0a9d09dd | ||
|
|
ea054ccced | ||
|
|
cbbe23aa46 | ||
|
|
990b0daae2 | ||
|
|
a16316e916 | ||
|
|
14952546a4 | ||
|
|
020b1a78d9 | ||
|
|
6b70e02bf5 | ||
|
|
31969a2756 | ||
|
|
53ebcd80d8 | ||
|
|
d714301180 | ||
|
|
002d619b11 | ||
|
|
55bb6bc318 | ||
|
|
05066e5279 | ||
|
|
131993634f | ||
|
|
40f6b17eda | ||
|
|
7a8ebc4dc7 | ||
|
|
a9346f5c82 | ||
|
|
3104ce1dd2 | ||
|
|
d213f2b25d | ||
|
|
813089047f | ||
|
|
0ed5a9842a | ||
|
|
f037cd0740 | ||
|
|
c9faf3d345 | ||
|
|
db8758060a | ||
|
|
c8ac0919a9 | ||
|
|
a2432e125a | ||
|
|
e498e2fd83 | ||
|
|
c65121a370 | ||
|
|
fdc935718d | ||
|
|
18d3b64bfb | ||
|
|
ddc63ba9b2 | ||
|
|
5576b88604 | ||
|
|
852be4a465 | ||
|
|
fb3c6029d0 | ||
|
|
9acd9ae894 | ||
|
|
d478addd2b | ||
|
|
6463a7c48a | ||
|
|
7434465334 | ||
|
|
f7b705959e | ||
|
|
1648f716f4 | ||
|
|
108b08fadb | ||
|
|
204566cd14 | ||
|
|
a3b4192e15 | ||
|
|
dfa26d0882 | ||
|
|
2683a07955 | ||
|
|
62bfc5fd8c | ||
|
|
f666282b4e | ||
|
|
77aefdf6a6 | ||
|
|
60ce1aaf01 | ||
|
|
496e2f3eb3 | ||
|
|
93bae76f49 | ||
|
|
7d17aa83eb | ||
|
|
7835e45ded | ||
|
|
0cf209002c | ||
|
|
57f2260301 | ||
|
|
76445ce73c | ||
|
|
99a5b1541e | ||
|
|
d432e987a0 | ||
|
|
71457375e4 | ||
|
|
8dc311c2d2 | ||
|
|
faa5e57913 | ||
|
|
4396784dbd | ||
|
|
81b714c4f3 | ||
|
|
cb739c62a9 | ||
|
|
58cfc6ba39 | ||
|
|
fafc315dac | ||
|
|
ffcd64792d | ||
|
|
8c24b22b2d | ||
|
|
6fe9bdc3db | ||
|
|
9d8b243e39 | ||
|
|
9884e19b7f | ||
|
|
5304a343e4 | ||
|
|
38e8f06def | ||
|
|
b93b85bcd5 | ||
|
|
587360662c | ||
|
|
95431aa7cf | ||
|
|
7de092df28 | ||
|
|
03b57c2655 | ||
|
|
a1c835b993 | ||
|
|
4ada2f52da | ||
|
|
9bb1a8207a | ||
|
|
a9e76295ff | ||
|
|
4e3364ddd3 | ||
|
|
6e5ae45c1a | ||
|
|
11c9376a50 | ||
|
|
b5f2a7b1ce | ||
|
|
3df7c8e89c | ||
|
|
0edbe29d38 | ||
|
|
8df5a76203 | ||
|
|
32c4a09deb | ||
|
|
ee6907f678 | ||
|
|
03640029db | ||
|
|
7a65ed9082 | ||
|
|
b10f818106 | ||
|
|
caad70dd6f | ||
|
|
6fe4f23074 | ||
|
|
fd8fb73492 | ||
|
|
7c8ed4d2e1 | ||
|
|
08fada7de0 | ||
|
|
163975d82a | ||
|
|
dda521e2bc | ||
|
|
e7dc90dadc | ||
|
|
8375741489 | ||
|
|
042b14653e | ||
|
|
c7f6a91da5 | ||
|
|
c26260e38d | ||
|
|
ac04d79ad2 | ||
|
|
052b89e8d6 | ||
|
|
c395da1c54 | ||
|
|
16b0564d49 | ||
|
|
d21207cc79 | ||
|
|
c4b69a1077 | ||
|
|
c85dd8881b | ||
|
|
8eae0a33b3 | ||
|
|
6356fa09e9 | ||
|
|
641e7c2b05 | ||
|
|
c60d1d0877 | ||
|
|
c45dd19d47 | ||
|
|
51f871f43b | ||
|
|
89a4720575 | ||
|
|
081e447a1b | ||
|
|
da0ee683a3 | ||
|
|
c6e5e2c0a0 | ||
|
|
fb3c166f80 | ||
|
|
39643aaf94 | ||
|
|
50bdf39ba5 | ||
|
|
1e413d4121 | ||
|
|
fcda712238 | ||
|
|
d4ff48a07e | ||
|
|
21f25545a8 | ||
|
|
37f975bf82 | ||
|
|
58558e319c | ||
|
|
48f6a08352 | ||
|
|
01e9f42c10 | ||
|
|
8c3967886c | ||
|
|
8a4fb3b445 | ||
|
|
04c9b62660 | ||
|
|
4790349f79 | ||
|
|
3e6005a1e7 | ||
|
|
9e3565027a | ||
|
|
6d8e6afbf2 | ||
|
|
5b5f431717 | ||
|
|
796e2556b6 | ||
|
|
8090737d56 | ||
|
|
d39a0c7035 | ||
|
|
de696ba5a4 | ||
|
|
cefa2d9100 | ||
|
|
9f07fd035c | ||
|
|
7ead7231c9 | ||
|
|
b0a3a5ce62 | ||
|
|
64c2084c8a | ||
|
|
4c355239ad | ||
|
|
79169ea315 | ||
|
|
4db866d5c3 | ||
|
|
83a5bfbfb1 | ||
|
|
c9651d9b2b | ||
|
|
8c99dd0f2c | ||
|
|
25351379ab | ||
|
|
ba67c5d977 | ||
|
|
7f8d77d84e | ||
|
|
91a88ee80f | ||
|
|
206fe8527f | ||
|
|
bed5b1731b | ||
|
|
c36b83fdea | ||
|
|
fede4a8102 | ||
|
|
29c0baa151 | ||
|
|
5d04d9c6cd | ||
|
|
2415b3e0bb | ||
|
|
d832547ba2 | ||
|
|
fe118f6347 | ||
|
|
9554b92c86 | ||
|
|
669ada024e | ||
|
|
e1e16bf85a | ||
|
|
35516a6fc2 | ||
|
|
51ecb10b01 | ||
|
|
10ce09fe0c | ||
|
|
0702d4185a | ||
|
|
afe57e99d1 | ||
|
|
e921486008 | ||
|
|
70d59ca1cd | ||
|
|
2ccaa81aab | ||
|
|
4db2eb91e9 | ||
|
|
a4daff2d83 | ||
|
|
cc884d57ca | ||
|
|
0c8a872c37 | ||
|
|
663ac74610 | ||
|
|
f04aebf1e0 | ||
|
|
bcf3b48e98 | ||
|
|
e7cc9455c9 | ||
|
|
e909535684 | ||
|
|
cea54cb865 | ||
|
|
3bc2e9bdb1 | ||
|
|
159960c72e | ||
|
|
9d35710419 | ||
|
|
bdcf448605 | ||
|
|
72eb180028 | ||
|
|
fe427bfe7a | ||
|
|
348f5c7086 | ||
|
|
e09b877146 | ||
|
|
aeb7ba4731 | ||
|
|
421cd1c3ca | ||
|
|
5e8729bd6f | ||
|
|
76d057a418 | ||
|
|
ea9864bbd5 | ||
|
|
3d9b4d5c05 | ||
|
|
d06bfe0ac3 | ||
|
|
6540294a81 | ||
|
|
02a7640047 | ||
|
|
f4c5a8a42e | ||
|
|
0fdbace9b2 | ||
|
|
cab6014154 | ||
|
|
000ce0ee52 | ||
|
|
d720a2011c | ||
|
|
c91055271b | ||
|
|
1478acd551 | ||
|
|
b3404c07d7 | ||
|
|
94767dc0e7 | ||
|
|
f957acfe11 | ||
|
|
7fc43ccc3a | ||
|
|
dc5b9508d9 | ||
|
|
ef056d8ed1 | ||
|
|
6a63671ef5 | ||
|
|
d3867766af | ||
|
|
1ae4b9571b | ||
|
|
3fd1cec20e | ||
|
|
28b16eea19 | ||
|
|
37f32a9008 | ||
|
|
8415252322 | ||
|
|
b3984c9580 | ||
|
|
6616995804 | ||
|
|
de7d37e1fc | ||
|
|
3cb8a528a5 | ||
|
|
8bb01e1a0e | ||
|
|
0416d7ed80 | ||
|
|
c02c04f08a | ||
|
|
efb952ef12 | ||
|
|
04b145d795 | ||
|
|
1830fa1d84 | ||
|
|
a2b300b863 | ||
|
|
2d8297a562 | ||
|
|
deb7728b46 | ||
|
|
bbd6d86af4 | ||
|
|
b6361b8cad | ||
|
|
04fdae124f | ||
|
|
29df7093a8 | ||
|
|
18212a21ab | ||
|
|
e13d28bfe6 | ||
|
|
667204ac7c | ||
|
|
76ea83e791 | ||
|
|
55daf7385d | ||
|
|
0e66e4b7e9 | ||
|
|
fe485db9b4 | ||
|
|
b3dc4879fd | ||
|
|
25ce3738b9 | ||
|
|
22b8ac8470 | ||
|
|
6a4e128ef8 | ||
|
|
66d68c8a98 | ||
|
|
76867af090 | ||
|
|
5d02248a0b | ||
|
|
eb6c213f2a | ||
|
|
efdac9afdd | ||
|
|
c6bfff2a04 | ||
|
|
49066ea329 | ||
|
|
25ceab07f7 | ||
|
|
6e97393740 | ||
|
|
ff3d54d26f | ||
|
|
25c9cf3976 | ||
|
|
5f2fb08cfd | ||
|
|
60bc56848f | ||
|
|
600ca381fd | ||
|
|
da3f5dd4ec | ||
|
|
b59911b0c1 | ||
|
|
8131528305 | ||
|
|
6a71e7dd4b | ||
|
|
a63b2f70b2 | ||
|
|
3f88964527 | ||
|
|
e18dc70af0 | ||
|
|
0c6daad215 | ||
|
|
8c35c80bb7 | ||
|
|
d42ba3bae4 | ||
|
|
8c337b25df | ||
|
|
8d1c08bd03 | ||
|
|
5599a6f24f | ||
|
|
ef9a610dd1 | ||
|
|
31c3b5397b | ||
|
|
e11d64b5c7 | ||
|
|
9d91a2bc23 | ||
|
|
8c41f21d3c | ||
|
|
5c7a75b3e4 | ||
|
|
0804fbd095 | ||
|
|
0f78fa9c43 | ||
|
|
d30e970eb8 |
No files matched your search
@@ -0,0 +1,45 @@
|
||||
name: Build all tarballs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
build-tarballs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ x86_64, aarch64, arm, riscv64, ppc64le, s390x ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tarballs
|
||||
path: mold-*.tar.gz
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Build x86 tarball
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build-tarball:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tarball
|
||||
path: mold-*.tar.gz
|
||||
+34
-35
@@ -20,10 +20,10 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: install-build-deps
|
||||
run: sudo ./install-build-deps.sh update
|
||||
run: sudo ./install-build-deps.sh
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
sudo apt-get install -y clang++-12
|
||||
@@ -31,15 +31,18 @@ jobs:
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++-12 ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: test
|
||||
run: |
|
||||
cd build
|
||||
ctest -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -47,68 +50,64 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: install-build-deps
|
||||
shell: bash
|
||||
run: |
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump xz-utils {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
|
||||
|
||||
for i in {i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu arm-linux-gnueabihf; do
|
||||
ln -sf /usr/bin/$i-gcc-10 /usr/bin/$i-gcc
|
||||
ln -sf /usr/bin/$i-g++-10 /usr/bin/$i-g++
|
||||
done
|
||||
./install-build-deps.sh
|
||||
./install-cross-tools.sh
|
||||
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
mkdir /usr/local/rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/riscv32-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
mkdir /rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/riscv32-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar -C /rv32 --strip-components=1 -xzf -
|
||||
|
||||
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
|
||||
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
|
||||
ln -sf /rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
|
||||
done
|
||||
|
||||
# Install a LoongArch toolchain
|
||||
mkdir /usr/local/larch
|
||||
wget -O- -q https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz | tar -C /usr/local/larch --strip-components=1 --xz -xf -
|
||||
mkdir /larch
|
||||
wget -O- -q https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /usr/local/larch/target /usr/loongarch64-linux-gnu
|
||||
cp -r /usr/local/larch/loongarch64-unknown-linux-gnu/lib/* /usr/loongarch64-linux-gnu/lib64/
|
||||
ln -sf /larch/target /usr/loongarch64-linux-gnu
|
||||
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /usr/loongarch64-linux-gnu/lib64/
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /usr/local/larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
|
||||
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
|
||||
done
|
||||
|
||||
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-gcc -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-gcc
|
||||
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-g++ -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-g++
|
||||
echo '/larch/bin/loongarch64-unknown-linux-gnu-gcc -L/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-gcc
|
||||
echo '/larch/bin/loongarch64-unknown-linux-gnu-g++ -L/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-g++
|
||||
chmod 755 /usr/bin/loongarch64-linux-gnu-{gcc,g++}
|
||||
|
||||
wget -O/usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2023.08.08/qemu-loongarch64
|
||||
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2023.08.08/qemu-loongarch64
|
||||
chmod 755 /usr/local/bin/qemu-loongarch64
|
||||
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On -DMOLD_ENABLE_QEMU_TESTS_LOONGARCH=On ..
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: test
|
||||
run: |
|
||||
cd build
|
||||
ctest -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-gcc
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-11
|
||||
@@ -122,7 +121,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
name: linux-packages
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: build
|
||||
run: ./dist.sh x86_64
|
||||
- name: upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: mold-*-linux.tar.gz
|
||||
@@ -0,0 +1,34 @@
|
||||
# This file is generated by ChatGPT
|
||||
|
||||
name: Update manpage
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/mold.md'
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
update-manpage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install ronn
|
||||
run: sudo apt-get install -y ronn
|
||||
|
||||
- name: Generate mold.1 from mold.md
|
||||
run: ronn --roff docs/mold.md
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name 'Rui Ueyama'
|
||||
git config --global user.email '[email protected]'
|
||||
|
||||
- name: Commit and push if mold.1 is updated
|
||||
run: |
|
||||
git add docs/mold.1
|
||||
git diff --staged --quiet || (git commit -m "Update mold.1 (automated commit)" && git push)
|
||||
@@ -7,3 +7,4 @@ core
|
||||
gentoo
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
/mold
|
||||
+48
-89
@@ -1,31 +1,31 @@
|
||||
# You can customize a build by specifying CMake options. An option may be
|
||||
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
|
||||
# means true while `OFF` or `0` means false.
|
||||
# means true, while `OFF` or `0` means false.
|
||||
#
|
||||
# Here are a couple of common cmake options:
|
||||
#
|
||||
# -DCMAKE_C_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies a C compiler name to use. The default value is `cc`.
|
||||
# Specifies the C compiler name to use. The default value is `cc`.
|
||||
#
|
||||
# -DCMAKE_CXX_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies a C++ compiler name to use. The default value is `c++`.
|
||||
# Specifies the C++ compiler name to use. The default value is `c++`.
|
||||
#
|
||||
# -DCMAKE_INSTALL_PREFIX=<directory>
|
||||
#
|
||||
# Specifies an install target directory. The default value is `/usr/local`.
|
||||
# Specifies the install target directory. The default value is `/usr/local`.
|
||||
#
|
||||
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
|
||||
#
|
||||
# Specifies a build type. The default is `Release` which is the right
|
||||
# Specifies the build type. The default is `Release`, which is the right
|
||||
# option unless you are debugging mold.
|
||||
#
|
||||
# An example of a cmake command line is shown below:
|
||||
#
|
||||
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
|
||||
#
|
||||
# where `..` refers this directory.
|
||||
# where `..` refers to this directory.
|
||||
#
|
||||
# With cmake, you may run `cmake --install .` instead of `make install` to
|
||||
# install build artifacts to system directories. If you want to install
|
||||
@@ -39,14 +39,15 @@
|
||||
# how to build mold. However, as a policy, we do not provide a way to
|
||||
# enable/disable any individual mold's feature. In other words, we do not
|
||||
# provide options like `--enable-foo` or `--disable-foo`. The motivation
|
||||
# behind it is build reproducibility. We want to guarantees that all builds
|
||||
# of the mold linker of the same version will have the exactly same set of
|
||||
# behind this is build reproducibility. We want to guarantee that all builds
|
||||
# of the mold linker of the same version will have the exact same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.3.2)
|
||||
project(mold VERSION 2.32.0)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
@@ -106,7 +107,7 @@ endif()
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
# need nor want to set this to ON.
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
@@ -117,6 +118,7 @@ find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
set(ZLIB_BUILD_EXAMPLES OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
target_include_directories(zlibstatic INTERFACE third-party/zlib
|
||||
$<TARGET_PROPERTY:zlibstatic,BINARY_DIR>)
|
||||
@@ -129,12 +131,17 @@ find_package(BLAKE3 QUIET)
|
||||
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE BLAKE3::blake3)
|
||||
else()
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
function(mold_add_blake3)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
endfunction()
|
||||
|
||||
mold_add_blake3()
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. If libzstd.so is not found, we compile a
|
||||
# Find zstd compression library. If zstd.h is not found, we compile a
|
||||
# bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
@@ -212,11 +219,6 @@ else()
|
||||
mold_add_tbb()
|
||||
endif()
|
||||
|
||||
# Check if this is a commercial version of mold (a.k.a. "sold")
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.md")
|
||||
set(MOLD_IS_SOLD ON)
|
||||
endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
@@ -270,6 +272,8 @@ if(NOT APPLE AND NOT MSVC)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
|
||||
|
||||
# Create a .cc file containing the current git hash for `mold --version`.
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
@@ -307,9 +311,7 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
elf/jobs.cc
|
||||
elf/linker-script.cc
|
||||
elf/lto.cc
|
||||
elf/main.cc
|
||||
elf/mapfile.cc
|
||||
elf/output-chunks.cc
|
||||
@@ -320,19 +322,11 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/tls.cc
|
||||
)
|
||||
|
||||
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
|
||||
|
||||
list(APPEND MOLD_MACHO_TEMPLATE_FILES
|
||||
macho/cmdline.cc
|
||||
macho/dead-strip.cc
|
||||
macho/input-files.cc
|
||||
macho/input-sections.cc
|
||||
macho/lto.cc
|
||||
macho/main.cc
|
||||
macho/mapfile.cc
|
||||
macho/output-chunks.cc
|
||||
macho/tapi.cc
|
||||
)
|
||||
if(WIN32)
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES elf/lto-win32.cc)
|
||||
else()
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES elf/lto-unix.cc)
|
||||
endif()
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
@@ -351,14 +345,6 @@ foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
foreach (SOURCE IN LISTS MOLD_MACHO_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_MACHO_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
endforeach()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
common/compress.cc
|
||||
@@ -366,11 +352,11 @@ target_sources(mold PRIVATE
|
||||
common/filepath.cc
|
||||
common/glob.cc
|
||||
common/hyperloglog.cc
|
||||
common/main.cc
|
||||
common/malloc.cc
|
||||
common/multi-glob.cc
|
||||
common/perf.cc
|
||||
common/random.cc
|
||||
common/tar.cc
|
||||
common/uuid.cc
|
||||
elf/arch-alpha.cc
|
||||
elf/arch-arm32.cc
|
||||
elf/arch-arm64.cc
|
||||
@@ -383,40 +369,40 @@ target_sources(mold PRIVATE
|
||||
elf/arch-sh4.cc
|
||||
elf/arch-sparc64.cc
|
||||
elf/arch-x86-64.cc
|
||||
elf/config.cc
|
||||
elf/elf.cc
|
||||
git-hash.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
if(WIN32)
|
||||
target_sources(mold PRIVATE
|
||||
macho/arch-arm64.cc
|
||||
macho/arch-x86-64.cc
|
||||
macho/yaml.cc
|
||||
common/jobs-win32.cc
|
||||
common/mapped-file-win32.cc
|
||||
common/signal-win32.cc
|
||||
)
|
||||
else()
|
||||
target_sources(mold PRIVATE
|
||||
common/jobs-unix.cc
|
||||
common/mapped-file-unix.cc
|
||||
common/signal-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add frequently included header files for pre-compiling.
|
||||
# target_precompile_headers is supported by CMake 3.16.0 or newer.
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
|
||||
if(MOLD_IS_SOLD)
|
||||
target_precompile_headers(mold PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/macho/mold.h>")
|
||||
else()
|
||||
target_precompile_headers(mold PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>")
|
||||
endif()
|
||||
|
||||
# ccache needs this flag along with `sloppiness = pch_defines,time_macros`
|
||||
# to enable caching
|
||||
target_compile_options(mold PRIVATE -fpch-preprocess)
|
||||
if(NOT MSVC)
|
||||
target_compile_options(mold PRIVATE -fpch-preprocess)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Create the ld and ld64 symlinks required for testing
|
||||
# Create the ld symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
@@ -424,22 +410,9 @@ if(BUILD_TESTING)
|
||||
BYPRODUCTS ld
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
|
||||
BYPRODUCTS ld64
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(${APPLE})
|
||||
if(MOLD_IS_SOLD)
|
||||
add_subdirectory(test/macho)
|
||||
endif()
|
||||
elseif(${UNIX})
|
||||
if(${UNIX})
|
||||
add_subdirectory(test/elf)
|
||||
endif()
|
||||
endif()
|
||||
@@ -447,13 +420,8 @@ endif()
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE")
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.third-party")
|
||||
install(FILES "LICENSE.third-party" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
|
||||
endif()
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install(FILES "LICENSE.third-party" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
@@ -473,13 +441,4 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld64.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld.sold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld64.sold)
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,30 +1,25 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
<i>This repository contains a free version of the mold linker.
|
||||
If you are looking for a commercial version that supports macOS
|
||||
please visit the
|
||||
[repository of the sold linker](https://github.com/bluewhalesystems/sold).</i>
|
||||
|
||||
mold is a faster drop-in replacement for existing Unix linkers. It is several
|
||||
times quicker than the LLVM lld linker, the second-fastest open-source linker,
|
||||
which I initially developed a few years ago. mold aims to enhance developer
|
||||
productivity by minimizing build time, particularly in rapid
|
||||
debug-edit-rebuild cycles.
|
||||
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold when linking
|
||||
final debuginfo-enabled executables for major large programs on a simulated
|
||||
8-core, 16-thread machine.
|
||||
Here is a performance comparison of GNU ld, GNU gold, LLVM lld, and
|
||||
mold when linking final debuginfo-enabled executables for major large
|
||||
programs on a simulated 16-core, 32-thread machine.
|
||||
|
||||

|
||||

|
||||
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|----------|----------|--------
|
||||
| Chrome 96 (1.89 GiB) | 53.86s | 11.74s | 2.21s
|
||||
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
|
||||
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
|
||||
| Program (linker output size) | GNU ld | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|--------|----------|----------|------
|
||||
| MySQL 8.3 (0.47 GiB) | 10.84s | 7.47s | 1.64s | 0.46s
|
||||
| Clang 19 (1.56 GiB) | 42.07s | 33.13s | 5.20s | 1.35s
|
||||
| Chromium 124 (1.35 GiB) | N/A | 27.40s | 6.10s | 1.52s
|
||||
|
||||
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
|
||||
machine. If you find that mold is not faster than other linkers, please feel
|
||||
machine. If you find that mold is not faster than other linkers, feel
|
||||
free to [file a bug report](https://github.com/rui314/mold/issues).
|
||||
|
||||
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
|
||||
@@ -32,9 +27,6 @@ RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
|
||||
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, SH-4, and DEC
|
||||
Alpha.
|
||||
|
||||
mold/macOS is commercial software. For mold/macOS, please visit
|
||||
https://github.com/bluewhalesystems/sold.
|
||||
|
||||
## Why does linking speed matter?
|
||||
|
||||
If you are using a compiled language such as C, C++, or Rust, a build consists
|
||||
@@ -68,14 +60,12 @@ necessary packages. You may need to run it as root.
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v2.3.2
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
sudo cmake --build . --target install
|
||||
git clone --branch stable https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
./install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
|
||||
cmake --build build -j$(nproc)
|
||||
sudo cmake --build build --target install
|
||||
```
|
||||
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
@@ -100,7 +90,7 @@ use the mold executable within it.
|
||||
|
||||
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
|
||||
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
|
||||
indirectly invoked by `make` or another build system command.
|
||||
indirectly invoked by `make` or other build system commands.
|
||||
|
||||
If you can specify an additional command line option for your compiler driver
|
||||
by modifying the build system's config files, add one of the following flags
|
||||
@@ -173,18 +163,18 @@ If you want to use mold for all projects, add the above snippet to
|
||||
|
||||
It is sometimes very hard to pass an appropriate command line option to `cc`
|
||||
to specify an alternative linker. To address this situation, mold has a
|
||||
feature to intercept all invocations of `ld`, `ld.lld`, or `ld.gold` and
|
||||
redirect them to itself. To use this feature, run `make` (or another build
|
||||
feature to intercept all invocations of `ld`, `ld.bfd`, `ld.lld`, or `ld.gold`
|
||||
and redirect them to itself. To use this feature, run `make` (or another build
|
||||
command) as a subcommand of mold as follows:
|
||||
|
||||
```shell
|
||||
mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
Internally, mold invokes the given command with the `LD_PRELOAD` environment
|
||||
Internally, mold invokes a given command with the `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object file
|
||||
intercepts all function calls to `exec(3)`-family functions to replace
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.gold`, or `ld.lld`.
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -192,7 +182,7 @@ intercepts all function calls to `exec(3)`-family functions to replace
|
||||
|
||||
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
|
||||
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
|
||||
run on a two-core machine, mold is still significantly faster than the default
|
||||
run on a 4 core machine, mold is still significantly faster than the default
|
||||
GNU linker, especially when linking large programs.
|
||||
|
||||
</details>
|
||||
@@ -218,7 +208,7 @@ If `mold` is present in the `.comment` section, the file was created by mold.
|
||||
|
||||
Since mold is a drop-in replacement, you should be able to use it without
|
||||
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
|
||||
is available. You can read the same manual by running `man mold`.
|
||||
is available online. You can read the same manual by running `man mold`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -242,10 +232,19 @@ For details, please see the [design notes](docs/design.md).
|
||||
|
||||
## Sponsors
|
||||
|
||||
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
|
||||
and [OpenCollective](https://opencollective.com/mold-linker). We thank
|
||||
everyone who sponsors our project. In particular, we'd like to acknowledge the
|
||||
following people and organizations who have sponsored $128/month or more:
|
||||
It is taken for granted nowadays that compiler toolchains can be easily
|
||||
installed and used for free, and people may not think too much about the
|
||||
individuals behind these "free tools". mold supports many projects, but it
|
||||
is essentially a one-person project. This situation is similar to the one
|
||||
depicted in the following xkcd illustration.
|
||||
|
||||
[](https://xkcd.com/2347)
|
||||
|
||||
If you think that the "Nebraska guy" should be rewarded, please consider
|
||||
becoming our [GitHub sponsor](https://github.com/sponsors/rui314)!
|
||||
|
||||
We thank everyone who sponsors our project. In particular, we'd like to acknowledge
|
||||
the following people and organizations who have sponsored $128/month or more:
|
||||
|
||||
### Corporate sponsors
|
||||
|
||||
@@ -255,13 +254,14 @@ following people and organizations who have sponsored $128/month or more:
|
||||
|
||||
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
|
||||
|
||||
- [Uber](https://uber.com)
|
||||
- [G-Research](https://www.gresearch.co.uk)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
- [GlareDB](https://github.com/GlareDB)
|
||||
|
||||
### Individual sponsors
|
||||
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Johan Andersson](https://github.com/repi)
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
- [kyle-elliott](https://github.com/kyle-elliott)
|
||||
- [Bryant Biggs](https://github.com/bryantbiggs)
|
||||
- [kraptor23](https://github.com/kraptor23)
|
||||
- [Jinkyu Yi](https://github.com/jincreator)
|
||||
@@ -117,7 +117,7 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile::must_open(ctx, path));
|
||||
vec.push_back(must_open_file(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(Context &ctx, std::string_view path, i64 depth) {
|
||||
if (depth > 10)
|
||||
Fatal(ctx) << path << ": response file nesting too deep";
|
||||
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
while (!data.empty()) {
|
||||
if (isspace(data[0])) {
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto read_quoted = [&]() {
|
||||
char quote = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string buf;
|
||||
while (!data.empty() && data[0] != quote) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
} else {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
}
|
||||
}
|
||||
if (data.empty())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
data = data.substr(1);
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
auto read_unquoted = [&] {
|
||||
std::string buf;
|
||||
while (!data.empty()) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[0])) {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
std::string_view tok;
|
||||
if (data[0] == '\'' || data[0] == '\"')
|
||||
tok = read_quoted();
|
||||
else
|
||||
tok = read_unquoted();
|
||||
|
||||
if (tok.starts_with('@'))
|
||||
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
|
||||
else
|
||||
vec.push_back(tok);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
template <typename Context>
|
||||
std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1, 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static inline std::string_view string_trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return "";
|
||||
str = str.substr(pos);
|
||||
|
||||
pos = str.find_last_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return str;
|
||||
return str.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+192
-227
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "integers.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <bitset>
|
||||
@@ -42,23 +41,6 @@
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
// __builtin_assume() is supported only by clang, and [[assume]] is
|
||||
// available only in C++23, so we use this macro when giving a hint to
|
||||
// the compiler's optimizer what's true.
|
||||
#define ASSUME(x) do { if (!(x)) __builtin_unreachable(); } while (0)
|
||||
|
||||
// This is an assert() that is enabled even in the release build.
|
||||
#define ASSERT(x) \
|
||||
do { \
|
||||
if (!(x)) { \
|
||||
std::cerr << "Assertion failed: (" << #x \
|
||||
<< "), function " << __FUNCTION__ \
|
||||
<< ", file " << __FILE__ \
|
||||
<< ", line " << __LINE__ << ".\n"; \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
inline uint64_t hash_string(std::string_view str) {
|
||||
return XXH3_64bits(str.data(), str.size());
|
||||
}
|
||||
@@ -82,20 +64,16 @@ using namespace std::literals::string_view_literals;
|
||||
template <typename Context> class OutputFile;
|
||||
|
||||
inline char *output_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
inline std::string mold_version;
|
||||
extern std::string mold_version_string;
|
||||
extern std::string mold_git_hash;
|
||||
|
||||
std::string errno_string();
|
||||
std::string get_self_path();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
i64 get_default_thread_count();
|
||||
|
||||
static u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
@@ -105,103 +83,126 @@ static u64 combine_hash(u64 a, u64 b) {
|
||||
// Error output
|
||||
//
|
||||
|
||||
template <typename Context>
|
||||
class SyncOut {
|
||||
// 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:
|
||||
SyncOut(Context &ctx, std::ostream *out = &std::cout) : out(out) {
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
SyncStream(std::ostream &out) : out(out) {}
|
||||
|
||||
~SyncStream() {
|
||||
emit();
|
||||
}
|
||||
|
||||
~SyncOut() {
|
||||
if (out) {
|
||||
std::scoped_lock lock(mu);
|
||||
*out << ss.str() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> SyncOut &operator<<(T &&val) {
|
||||
if (out)
|
||||
ss << std::forward<T>(val);
|
||||
template <typename T> SyncStream &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static inline std::mutex mu;
|
||||
void emit() {
|
||||
if (emitted)
|
||||
return;
|
||||
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << '\n';
|
||||
emitted = true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream *out;
|
||||
std::ostream &out;
|
||||
std::stringstream ss;
|
||||
bool emitted = false;
|
||||
static inline std::mutex mu;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
static std::string add_color(Context &ctx, std::string msg) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
|
||||
return "mold: " + msg + ": ";
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
class Fatal {
|
||||
class Out {
|
||||
public:
|
||||
Fatal(Context &ctx) : out(ctx, &std::cerr) {
|
||||
out << add_color(ctx, "fatal");
|
||||
}
|
||||
Out(Context &ctx) {}
|
||||
|
||||
[[noreturn]] ~Fatal() {
|
||||
out.~SyncOut();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <class T> Fatal &operator<<(T &&val) {
|
||||
template <typename T> Out &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<Context> out;
|
||||
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) : out(ctx, &std::cerr) {
|
||||
Error(Context &ctx) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << add_color(ctx, "warning");
|
||||
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
} else {
|
||||
out << add_color(ctx, "error");
|
||||
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> Error &operator<<(T &&val) {
|
||||
template <typename T> Error &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<Context> out;
|
||||
SyncStream out{std::cerr};
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(Context &ctx)
|
||||
: out(ctx, ctx.arg.suppress_warnings ? nullptr : &std::cerr) {
|
||||
Warn(Context &ctx) {
|
||||
if (ctx.arg.suppress_warnings)
|
||||
return;
|
||||
|
||||
out.emplace(std::cerr);
|
||||
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
out << add_color(ctx, "error");
|
||||
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
} else {
|
||||
out << add_color(ctx, "warning");
|
||||
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> Warn &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
template <typename T> Warn &operator<<(T &&val) {
|
||||
if (out)
|
||||
*out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<Context> out;
|
||||
std::optional<SyncStream> out;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -216,7 +217,7 @@ struct Atomic : std::atomic<T> {
|
||||
|
||||
using std::atomic<T>::atomic;
|
||||
|
||||
Atomic(const Atomic<T> &other) { store(other.load()); }
|
||||
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
|
||||
|
||||
Atomic<T> &operator=(const Atomic<T> &other) {
|
||||
store(other.load());
|
||||
@@ -421,13 +422,11 @@ inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void sort(T &vec) {
|
||||
inline void sort(auto &vec) {
|
||||
std::stable_sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void sort(T &vec, U less) {
|
||||
inline void sort(auto &vec, auto less) {
|
||||
std::stable_sort(vec.begin(), vec.end(), less);
|
||||
}
|
||||
|
||||
@@ -512,9 +511,6 @@ inline u64 read_uleb(std::string_view str) {
|
||||
}
|
||||
|
||||
inline i64 uleb_size(u64 val) {
|
||||
#if __GNUC__
|
||||
#pragma GCC unroll 8
|
||||
#endif
|
||||
for (int i = 1; i < 9; i++)
|
||||
if (val < (1LL << (7 * i)))
|
||||
return i;
|
||||
@@ -546,6 +542,16 @@ inline bool remove_prefix(std::string_view &s, std::string_view prefix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void pause() {
|
||||
#if defined(__x86_64__)
|
||||
asm volatile("pause");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_8A__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Concurrent Map
|
||||
//
|
||||
@@ -555,17 +561,26 @@ inline bool remove_prefix(std::string_view &s, std::string_view prefix) {
|
||||
// So you need to give a correct estimation of the final size before
|
||||
// using it. We use this hash map to uniquify pieces of data in
|
||||
// mergeable sections.
|
||||
//
|
||||
// We've implemented this ourselves because the performance of
|
||||
// conrurent hash map is critical for our linker.
|
||||
template <typename T>
|
||||
class ConcurrentMap {
|
||||
public:
|
||||
ConcurrentMap() {}
|
||||
ConcurrentMap() = default;
|
||||
|
||||
ConcurrentMap(i64 nbuckets) {
|
||||
resize(nbuckets);
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
free(entries);
|
||||
if (entries) {
|
||||
#ifdef _WIN32
|
||||
_aligned_free(entries);
|
||||
#else
|
||||
munmap(entries, sizeof(Entry) * nbuckets);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// In order to avoid unnecessary cache-line false sharing, we want
|
||||
@@ -573,40 +588,51 @@ public:
|
||||
// power-of-two address.
|
||||
struct alignas(32) Entry {
|
||||
Atomic<const char *> key;
|
||||
T value;
|
||||
u32 keylen;
|
||||
T value;
|
||||
};
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
assert(!entries);
|
||||
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
i64 bufsize = sizeof(Entry) * this->nbuckets;
|
||||
|
||||
i64 sz = sizeof(Entry) * this->nbuckets;
|
||||
free(entries);
|
||||
|
||||
#if _WIN32
|
||||
// Even though std::aligned_alloc is defined in C++17, MSVC doesn't
|
||||
// seem to provide that function.
|
||||
entries = (Entry *)_aligned_malloc(sz, alignof(Entry));
|
||||
// Allocate a zero-initialized buffer. We use mmap() if available
|
||||
// because it's faster than malloc() and memset().
|
||||
#ifdef _WIN32
|
||||
entries = (Entry *)_aligned_malloc(bufsize, alignof(Entry));
|
||||
memset((void *)entries, 0, bufsize);
|
||||
#else
|
||||
entries = (Entry *)std::aligned_alloc(alignof(Entry), sz);
|
||||
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
#endif
|
||||
|
||||
memset(entries, 0, sz);
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
std::pair<T *, bool> insert(std::string_view key, u32 hash, const T &val) {
|
||||
assert(has_single_bit(nbuckets));
|
||||
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
i64 begin = hash & (nbuckets - 1);
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
|
||||
while (retry < MAX_RETRY) {
|
||||
for (i64 i = 0; i < MAX_RETRY; i++) {
|
||||
i64 idx = (begin & ~mask) | ((begin + i) & mask);
|
||||
Entry &ent = entries[idx];
|
||||
const char *ptr = nullptr;
|
||||
bool claimed = ent.key.compare_exchange_weak(ptr, (char *)-1,
|
||||
std::memory_order_acquire);
|
||||
|
||||
// If we successfully claimed the ownership of an unused slot,
|
||||
// It seems avoiding compare-and-swap is faster overall at least
|
||||
// on my Zen4 machine, so do it.
|
||||
if (const char *ptr = ent.key.load(std::memory_order_acquire);
|
||||
ptr != nullptr && ptr != (char *)-1) {
|
||||
if (key == std::string_view(ptr, ent.keylen))
|
||||
return {&ent.value, false};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, use CAS to atomically claim the ownership of the slot.
|
||||
const char *ptr = nullptr;
|
||||
bool claimed = ent.key.compare_exchange_strong(ptr, (char *)-1,
|
||||
std::memory_order_acquire);
|
||||
|
||||
// If we successfully claimed the ownership of the slot,
|
||||
// copy values to it.
|
||||
if (claimed) {
|
||||
new (&ent.value) T(val);
|
||||
@@ -615,10 +641,6 @@ public:
|
||||
return {&ent.value, true};
|
||||
}
|
||||
|
||||
// Loop on a spurious failure.
|
||||
if (ptr == nullptr)
|
||||
continue;
|
||||
|
||||
// If someone is copying values to the slot, do busy wait.
|
||||
while (ptr == (char *)-1) {
|
||||
pause();
|
||||
@@ -629,11 +651,6 @@ public:
|
||||
// looking for.
|
||||
if (key == std::string_view(ptr, ent.keylen))
|
||||
return {&ent.value, false};
|
||||
|
||||
// Otherwise, move on to the next slot.
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
idx = (idx & ~mask) | ((idx + 1) & mask);
|
||||
retry++;
|
||||
}
|
||||
|
||||
assert(false && "ConcurrentMap is full");
|
||||
@@ -701,17 +718,14 @@ public:
|
||||
|
||||
Entry *entries = nullptr;
|
||||
i64 nbuckets = 0;
|
||||
|
||||
private:
|
||||
static void pause() {
|
||||
#if defined(__x86_64__)
|
||||
asm volatile("pause");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// random.cc
|
||||
//
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size);
|
||||
|
||||
//
|
||||
// output-file.h
|
||||
//
|
||||
@@ -742,14 +756,9 @@ template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
~MallocOutputFile() {
|
||||
free(this->buf);
|
||||
: OutputFile<Context>(path, filesize, false), ptr(new u8[filesize]),
|
||||
perm(perm) {
|
||||
this->buf = ptr.get();
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
@@ -759,10 +768,19 @@ public:
|
||||
if (this->path == "-") {
|
||||
fp = stdout;
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
int pmode = (perm & 0200) ? (_S_IREAD | _S_IWRITE) : _S_IREAD;
|
||||
i64 fd = _open(this->path.c_str(), _O_RDWR | _O_CREAT | _O_BINARY, pmode);
|
||||
#else
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
#endif
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
#ifdef _WIN32
|
||||
fp = _fdopen(fd, "wb");
|
||||
#else
|
||||
fp = fdopen(fd, "w");
|
||||
#endif
|
||||
}
|
||||
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
@@ -772,6 +790,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<u8[]> ptr;
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
@@ -781,9 +800,7 @@ private:
|
||||
|
||||
class HyperLogLog {
|
||||
public:
|
||||
HyperLogLog() : buckets(NBUCKETS) {}
|
||||
|
||||
void insert(u32 hash) {
|
||||
void insert(u64 hash) {
|
||||
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
|
||||
}
|
||||
|
||||
@@ -798,7 +815,7 @@ private:
|
||||
static constexpr i64 NBUCKETS = 2048;
|
||||
static constexpr double ALPHA = 0.79402;
|
||||
|
||||
std::vector<std::atomic_uint8_t> buckets;
|
||||
Atomic<u8> buckets[NBUCKETS];
|
||||
};
|
||||
|
||||
//
|
||||
@@ -832,13 +849,13 @@ private:
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, u32 val);
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return strings.empty(); }
|
||||
std::optional<u32> find(std::string_view str);
|
||||
std::optional<i64> find(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
u32 value = -1;
|
||||
i64 value = -1;
|
||||
TrieNode *suffix_link = nullptr;
|
||||
std::unique_ptr<TrieNode> children[256];
|
||||
};
|
||||
@@ -846,26 +863,21 @@ private:
|
||||
void compile();
|
||||
void fix_suffix_links(TrieNode &node);
|
||||
void fix_values();
|
||||
i64 find_aho_corasick(std::string_view str);
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::unique_ptr<TrieNode> root;
|
||||
std::vector<std::pair<Glob, u32>> globs;
|
||||
std::vector<std::pair<Glob, i64>> globs;
|
||||
std::once_flag once;
|
||||
bool is_compiled = false;
|
||||
bool prefix_match = false;
|
||||
};
|
||||
|
||||
//
|
||||
// uuid.cc
|
||||
//
|
||||
|
||||
std::array<u8, 16> get_uuid_v4();
|
||||
|
||||
//
|
||||
// filepath.cc
|
||||
//
|
||||
|
||||
template <typename T>
|
||||
std::filesystem::path filepath(const T &path) {
|
||||
std::filesystem::path filepath(const auto &path) {
|
||||
return {path, std::filesystem::path::format::generic_format};
|
||||
}
|
||||
|
||||
@@ -877,8 +889,15 @@ std::filesystem::path to_abs_path(std::filesystem::path path);
|
||||
// demangle.cc
|
||||
//
|
||||
|
||||
std::string_view demangle(std::string_view name);
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name);
|
||||
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();
|
||||
|
||||
//
|
||||
// compress.cc
|
||||
@@ -928,8 +947,6 @@ public:
|
||||
void append(std::string path, std::string_view data);
|
||||
|
||||
private:
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
|
||||
|
||||
FILE *out = nullptr;
|
||||
@@ -942,16 +959,22 @@ private:
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
template <typename Context>
|
||||
class MappedFile {
|
||||
public:
|
||||
static MappedFile *open(Context &ctx, std::string path);
|
||||
static MappedFile *must_open(Context &ctx, std::string path);
|
||||
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
|
||||
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size);
|
||||
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.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
return mf;
|
||||
}
|
||||
|
||||
std::string_view get_contents() {
|
||||
return std::string_view((char *)data, size);
|
||||
@@ -984,98 +1007,40 @@ public:
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
int fd = -1;
|
||||
|
||||
// For --dependency-file
|
||||
bool is_dependency = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE file_handle = INVALID_HANDLE_VALUE;
|
||||
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<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
MappedFile *open_file(Context &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
#else
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
std::string error;
|
||||
MappedFile *mf = open_file_impl(path, error);
|
||||
if (!error.empty())
|
||||
Fatal(ctx) << error;
|
||||
|
||||
if (fd == -1) {
|
||||
if (errno != ENOENT)
|
||||
Fatal(ctx) << "opening " << path << " failed: " << errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1)
|
||||
Fatal(ctx) << path << ": fstat failed: " << errno_string();
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
if (st.st_size > 0) {
|
||||
#ifdef _WIN32
|
||||
HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
|
||||
nullptr, PAGE_READWRITE, 0,
|
||||
st.st_size, nullptr);
|
||||
if (!handle)
|
||||
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
|
||||
mf->file_handle = handle;
|
||||
mf->data = (u8 *)MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, st.st_size);
|
||||
if (!mf->data)
|
||||
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
|
||||
#else
|
||||
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE, fd, 0);
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
#endif
|
||||
}
|
||||
|
||||
close(fd);
|
||||
if (mf)
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
MappedFile<Context> *
|
||||
MappedFile<Context>::must_open(Context &ctx, std::string path) {
|
||||
if (MappedFile *mf = MappedFile::open(ctx, path))
|
||||
return mf;
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
MappedFile<Context> *
|
||||
MappedFile<Context>::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.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
void MappedFile<Context>::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
UnmapViewOfFile(data);
|
||||
if (file_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(file_handle);
|
||||
#else
|
||||
munmap(data, size);
|
||||
#endif
|
||||
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+4
-4
@@ -132,7 +132,7 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
@@ -146,10 +146,10 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
|
||||
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
|
||||
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);
|
||||
LEVEL);
|
||||
assert(!ZSTD_isError(sz));
|
||||
buf.resize(sz);
|
||||
buf.shrink_to_fit();
|
||||
@@ -178,7 +178,7 @@ void ZstdCompressor::write_to(u8 *buf) {
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
#cmakedefine01 MOLD_IS_SOLD
|
||||
|
||||
#cmakedefine HAVE_MADVISE 1
|
||||
+10
-19
@@ -10,25 +10,7 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string_view demangle(std::string_view name) {
|
||||
static thread_local char *p;
|
||||
if (p)
|
||||
free(p);
|
||||
|
||||
// Try to demangle as a Rust symbol. Since legacy-style Rust symbols
|
||||
// are also valid as a C++ mangled name, we need to call this before
|
||||
// cpp_demangle.
|
||||
p = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (p)
|
||||
return p;
|
||||
|
||||
// Try to demangle as a C++ symbol.
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
return *s;
|
||||
return name;
|
||||
}
|
||||
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name) {
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
@@ -48,4 +30,13 @@ std::optional<std::string_view> cpp_demangle(std::string_view name) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
free(buf);
|
||||
buf = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (buf)
|
||||
return buf;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -3,6 +3,14 @@
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string get_realpath(std::string_view path) {
|
||||
@@ -26,4 +34,35 @@ std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
return (std::filesystem::current_path() / path).lexically_normal();
|
||||
}
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#if __APPLE__
|
||||
char path[8192];
|
||||
u32 size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size)) {
|
||||
std::cerr << "_NSGetExecutablePath failed\n";
|
||||
exit(1);
|
||||
}
|
||||
return path;
|
||||
#elif __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+7
-3
@@ -25,9 +25,13 @@ enum class FileType {
|
||||
|
||||
template <typename MappedFile>
|
||||
bool is_text_file(MappedFile *mf) {
|
||||
auto istext = [](char c) {
|
||||
return isprint(c) || c == '\n' || c == '\t';
|
||||
};
|
||||
|
||||
u8 *data = mf->data;
|
||||
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
return mf->size >= 4 && istext(data[0]) && istext(data[1]) &&
|
||||
istext(data[2]) && istext(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename Context, typename MappedFile>
|
||||
@@ -53,7 +57,7 @@ inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
|
||||
if (!ctx.arg.plugin.empty()) {
|
||||
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
|
||||
if (name.starts_with(".gnu.lto_.symtab."))
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
|
||||
+10
-3
@@ -79,6 +79,14 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
case '*':
|
||||
vec.push_back({STAR});
|
||||
break;
|
||||
case '\\':
|
||||
if (pat.empty())
|
||||
return {};
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.push_back({STRING});
|
||||
vec.back().str += pat[0];
|
||||
pat = pat.substr(1);
|
||||
break;
|
||||
default:
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.push_back({STRING});
|
||||
@@ -101,7 +109,7 @@ bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
|
||||
switch (e.kind) {
|
||||
case STRING:
|
||||
if (str.empty() || !str.starts_with(e.str))
|
||||
if (!str.starts_with(e.str))
|
||||
return false;
|
||||
str = str.substr(e.str.size());
|
||||
break;
|
||||
@@ -116,13 +124,12 @@ bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
for (;;) {
|
||||
size_t pos = str.find(elements[0].str);
|
||||
if (pos == str.npos)
|
||||
break;
|
||||
return false;
|
||||
if (do_match(str.substr(pos + elements[0].str.size()),
|
||||
elements.subspan(1)))
|
||||
return true;
|
||||
str = str.substr(pos + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Other cases are handled here.
|
||||
|
||||
@@ -9,21 +9,20 @@
|
||||
// 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 "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <fcntl.h>
|
||||
# include <pwd.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
void acquire_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
static int lock_fd = -1;
|
||||
|
||||
void acquire_global_lock() {
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || jobs != "1"s)
|
||||
return;
|
||||
@@ -40,22 +39,12 @@ void acquire_global_lock(Context<E> &ctx) {
|
||||
|
||||
if (lockf(fd, F_LOCK, 0) == -1)
|
||||
return;
|
||||
|
||||
ctx.global_lock_fd = fd;
|
||||
#endif
|
||||
lock_fd = fd;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void release_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
if (ctx.global_lock_fd)
|
||||
close(*ctx.global_lock_fd);
|
||||
#endif
|
||||
void release_global_lock() {
|
||||
if (lock_fd != -1)
|
||||
close(lock_fd);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void acquire_global_lock(Context<E> &);
|
||||
template void release_global_lock(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mold {
|
||||
|
||||
void acquire_global_lock() {}
|
||||
void release_global_lock() {}
|
||||
|
||||
} // namespace mold
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
#include "common.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string mold_version_string = MOLD_VERSION;
|
||||
|
||||
namespace elf {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
namespace macho {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
static std::string get_mold_version() {
|
||||
std::string name = MOLD_IS_SOLD ? "mold (sold) " : "mold ";
|
||||
if (mold_git_hash.empty())
|
||||
return name + MOLD_VERSION + " (compatible with GNU ld)";
|
||||
return name + MOLD_VERSION + " (" + mold_git_hash + "; compatible with GNU ld)";
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
std::string errno_string() {
|
||||
// strerror is not thread-safe, so guard it with a lock.
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#ifdef __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS or structured
|
||||
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
#ifdef _WIN32
|
||||
|
||||
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
PEXCEPTION_RECORD exception_record = exception_info->ExceptionRecord;
|
||||
ULONG_PTR *exception_information = exception_record->ExceptionInformation;
|
||||
if (exception_record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
|
||||
(ULONG_PTR)output_buffer_start <= exception_information[1] &&
|
||||
exception_information[1] < (ULONG_PTR)output_buffer_end) {
|
||||
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
AddVectoredExceptionHandler(0, vectored_handler);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
switch (signo) {
|
||||
case SIGSEGV:
|
||||
case SIGBUS:
|
||||
if (output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
}
|
||||
break;
|
||||
case SIGABRT: {
|
||||
const char msg[] =
|
||||
"mold: aborted\n"
|
||||
"mold: If mold failed due to a spurious failure of pthread_create, "
|
||||
"it's likely because of https://github.com/oneapi-src/oneTBB/pull/824. "
|
||||
"You should ensure that you are using 2021.9.0 or newer version of libtbb.\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGABRT, &action, NULL);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
mold::mold_version = mold::get_mold_version();
|
||||
|
||||
#if MOLD_IS_SOLD
|
||||
std::string cmd = mold::filepath(argv[0]).filename().string();
|
||||
if (cmd == "ld64" || cmd.starts_with("ld64."))
|
||||
return mold::macho::main(argc, argv);
|
||||
#endif
|
||||
|
||||
return mold::elf::main(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "config.h"
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
# include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
MappedFile *open_file_impl(const std::string &path, std::string &error) {
|
||||
i64 fd = ::open(path.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
if (errno != ENOENT)
|
||||
error = "opening " + path + " failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1)
|
||||
error = path + ": fstat failed: " + errno_string();
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
if (st.st_size > 0) {
|
||||
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE, fd, 0);
|
||||
if (mf->data == MAP_FAILED)
|
||||
error = path + ": mmap failed: " + errno_string();
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
}
|
||||
|
||||
void MappedFile::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
munmap(data, size);
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
MappedFile *open_file_impl(const std::string &path, std::string &error) {
|
||||
HANDLE fd = CreateFileA(path.c_str(), GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
auto err = GetLastError();
|
||||
if (err != ERROR_FILE_NOT_FOUND)
|
||||
error = "opening " + path + " failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (GetFileType(fd) != FILE_TYPE_DISK) {
|
||||
CloseHandle(fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DWORD size_hi;
|
||||
DWORD size_lo = GetFileSize(fd, &size_hi);
|
||||
|
||||
if (size_lo == INVALID_FILE_SIZE) {
|
||||
error = path + ": GetFileSize failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u64 size = ((u64)size_hi << 32) + size_lo;
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = path;
|
||||
mf->size = size;
|
||||
mf->fd = fd;
|
||||
|
||||
if (size > 0) {
|
||||
HANDLE h = CreateFileMapping(fd, nullptr, PAGE_READONLY, 0, size, nullptr);
|
||||
if (!h) {
|
||||
error = path + ": CreateFileMapping failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mf->data = (u8 *)MapViewOfFile(h, FILE_MAP_COPY, 0, 0, size);
|
||||
CloseHandle(h);
|
||||
|
||||
if (!mf->data) {
|
||||
error = path + ": MapViewOfFile failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return mf;
|
||||
}
|
||||
|
||||
void MappedFile::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
UnmapViewOfFile(data);
|
||||
if (fd != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(fd);
|
||||
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+51
-31
@@ -24,44 +24,54 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<u32> MultiGlob::find(std::string_view str) {
|
||||
std::optional<i64> MultiGlob::find(std::string_view str) {
|
||||
std::call_once(once, [&] { compile(); });
|
||||
u32 val = UINT32_MAX;
|
||||
i64 val = -1;
|
||||
|
||||
if (root) {
|
||||
// Match against simple glob patterns
|
||||
TrieNode *node = root.get();
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (;;) {
|
||||
if (node->children[c]) {
|
||||
node = node->children[c].get();
|
||||
val = std::min(val, node->value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->suffix_link)
|
||||
return;
|
||||
node = node->suffix_link;
|
||||
}
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str)
|
||||
walk(c);
|
||||
walk('\0');
|
||||
}
|
||||
// Match against simple glob patterns
|
||||
if (root)
|
||||
val = find_aho_corasick(str);
|
||||
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<Glob, u32> &glob : globs)
|
||||
for (std::pair<Glob, i64> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
val = std::min(val, glob.second);
|
||||
val = std::max(val, glob.second);
|
||||
|
||||
if (val == UINT32_MAX)
|
||||
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);
|
||||
@@ -82,7 +92,7 @@ static std::string handle_stars(std::string_view pat) {
|
||||
return "\0"s + str + "\0"s;
|
||||
}
|
||||
|
||||
bool MultiGlob::add(std::string_view pat, u32 val) {
|
||||
bool MultiGlob::add(std::string_view pat, i64 val) {
|
||||
assert(!is_compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
@@ -108,7 +118,7 @@ bool MultiGlob::add(std::string_view pat, u32 val) {
|
||||
node = node->children[c].get();
|
||||
}
|
||||
|
||||
node->value = std::min(node->value, val);
|
||||
node->value = std::max(node->value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -117,6 +127,16 @@ void MultiGlob::compile() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +177,7 @@ void MultiGlob::fix_values() {
|
||||
for (std::unique_ptr<TrieNode> &child : node->children) {
|
||||
if (!child)
|
||||
continue;
|
||||
child->value = std::min(child->value, child->suffix_link->value);
|
||||
child->value = std::max(child->value, child->suffix_link->value);
|
||||
queue.push(child.get());
|
||||
}
|
||||
} while (!queue.empty());
|
||||
|
||||
@@ -39,11 +39,16 @@ open_or_create_file(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
|
||||
}
|
||||
|
||||
if (ftruncate(fd, filesize))
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
|
||||
if (fchmod(fd, (perm & ~get_umask())) == -1)
|
||||
Fatal(ctx) << "fchmod failed: " << errno_string();
|
||||
|
||||
#ifdef __linux__
|
||||
if (fallocate(fd, 0, 0, filesize) == 0)
|
||||
return {fd, path2};
|
||||
#endif
|
||||
|
||||
if (ftruncate(fd, filesize) == -1)
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
return {fd, path2};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,66 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
class MemoryMappedOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, true) {
|
||||
// TODO: use intermediate temporary file for output.
|
||||
DWORD file_attrs =
|
||||
(perm & 0200) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY;
|
||||
file_handle =
|
||||
CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, CREATE_ALWAYS, file_attrs, nullptr);
|
||||
if (file_handle == INVALID_HANDLE_VALUE)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << GetLastError();
|
||||
|
||||
HANDLE mapping_handle = CreateFileMapping(
|
||||
file_handle, nullptr, PAGE_READWRITE, 0, filesize, nullptr);
|
||||
if (!mapping_handle)
|
||||
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
|
||||
|
||||
this->buf =
|
||||
(u8 *)MapViewOfFile(mapping_handle, FILE_MAP_WRITE, 0, 0, filesize);
|
||||
CloseHandle(mapping_handle);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
~MemoryMappedOutputFile() {
|
||||
if (file_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(file_handle);
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
UnmapViewOfFile(this->buf);
|
||||
|
||||
if (!this->buf2.empty()) {
|
||||
if (SetFilePointer(file_handle, 0, nullptr, FILE_END) ==
|
||||
INVALID_SET_FILE_POINTER)
|
||||
Fatal(ctx) << this->path
|
||||
<< ": SetFilePointer failed: " << GetLastError();
|
||||
|
||||
DWORD written;
|
||||
if (!WriteFile(file_handle, this->buf2.data(), this->buf2.size(),
|
||||
&written, nullptr))
|
||||
Fatal(ctx) << this->path << ": WriteFile failed: " << GetLastError();
|
||||
}
|
||||
|
||||
CloseHandle(file_handle);
|
||||
file_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE file_handle;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
@@ -14,7 +74,26 @@ OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
OutputFile<Context> *file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
bool is_special = false;
|
||||
if (path == "-") {
|
||||
is_special = true;
|
||||
} else {
|
||||
HANDLE file_handle =
|
||||
CreateFileA(path.c_str(), GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file_handle != INVALID_HANDLE_VALUE) {
|
||||
if (GetFileType(file_handle) != FILE_TYPE_DISK)
|
||||
is_special = true;
|
||||
CloseHandle(file_handle);
|
||||
}
|
||||
}
|
||||
|
||||
OutputFile<Context> *file;
|
||||
if (is_special)
|
||||
file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
else
|
||||
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
|
||||
+1
-7
@@ -26,19 +26,13 @@ void Counter::print() {
|
||||
}
|
||||
|
||||
static i64 now_nsec() {
|
||||
#ifdef _WIN32
|
||||
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
#else
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::pair<i64, i64> get_usage() {
|
||||
#ifdef _WIN32
|
||||
auto to_nsec = [](FILETIME t) -> i64 {
|
||||
return ((u64)t.dwHighDateTime << 32 + (u64)t.dwLowDateTime) * 100;
|
||||
return (((u64)t.dwHighDateTime << 32) + (u64)t.dwLowDateTime) * 100;
|
||||
};
|
||||
|
||||
FILETIME creation, exit, kernel, user;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace mold {
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size) {
|
||||
std::random_device rand;
|
||||
i64 i = 0;
|
||||
|
||||
for (; i < size - 4; i += 4) {
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, 4);
|
||||
}
|
||||
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, size - i);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <tbb/version.h>
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string errno_string() {
|
||||
// strerror is not thread-safe, so guard it with a lock.
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS or structured
|
||||
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
static std::string sigabrt_msg;
|
||||
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
// Handle disk full error
|
||||
switch (signo) {
|
||||
case SIGSEGV:
|
||||
case SIGBUS:
|
||||
if (output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
}
|
||||
break;
|
||||
case SIGABRT: {
|
||||
(void)!write(STDERR_FILENO, &sigabrt_msg[0], sigabrt_msg.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-throw the signal
|
||||
signal(SIGSEGV, SIG_DFL);
|
||||
signal(SIGBUS, SIG_DFL);
|
||||
signal(SIGABRT, SIG_DFL);
|
||||
|
||||
cleanup();
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGSEGV, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
|
||||
// OneTBB 2021.9.0 has the 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 " +
|
||||
std::string(TBB_runtime_version()) +
|
||||
". Please upgrade your libtbb library and try again.\n";
|
||||
|
||||
sigaction(SIGABRT, &action, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
_unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
std::string errno_string() {
|
||||
LPVOID buf;
|
||||
DWORD dw = GetLastError();
|
||||
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, dw,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPTSTR)&buf, 0, nullptr);
|
||||
|
||||
std::string ret = (char *)buf;
|
||||
LocalFree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
PEXCEPTION_RECORD rec = exception_info->ExceptionRecord;
|
||||
ULONG_PTR *p = rec->ExceptionInformation;
|
||||
|
||||
if (rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
|
||||
(ULONG_PTR)output_buffer_start <= p[1] &&
|
||||
p[1] < (ULONG_PTR)output_buffer_end) {
|
||||
static const char msg[] =
|
||||
"mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!_write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
AddVectoredExceptionHandler(0, vectored_handler);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,144 @@
|
||||
// This is a header-only C++20 implementation of SipHash based on the
|
||||
// reference implementation. To use, just copy this header file into
|
||||
// your project and #include it.
|
||||
//
|
||||
// https://github.com/rui314/siphash/blob/main/siphash.h
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
template <int C_ROUNDS, int D_ROUNDS, int OUTLEN>
|
||||
class SipHashTmpl {
|
||||
public:
|
||||
static_assert(OUTLEN == 64 || OUTLEN == 128);
|
||||
|
||||
SipHashTmpl(void *key) {
|
||||
uint64_t k0 = read64(key);
|
||||
uint64_t k1 = read64((char *)key + 8);
|
||||
|
||||
v0 = 0x736f6d6570736575 ^ k0;
|
||||
v1 = 0x646f72616e646f6d ^ k1;
|
||||
v2 = 0x6c7967656e657261 ^ k0;
|
||||
v3 = 0x7465646279746573 ^ k1;
|
||||
|
||||
if (OUTLEN == 128)
|
||||
v1 ^= 0xee;
|
||||
}
|
||||
|
||||
void update(void *msgp, int64_t msglen) {
|
||||
char *msg = (char *)msgp;
|
||||
sum += msglen;
|
||||
|
||||
if (buflen) {
|
||||
if (buflen + msglen < 8) {
|
||||
memcpy(buf + buflen, msg, msglen);
|
||||
buflen += msglen;
|
||||
return;
|
||||
}
|
||||
|
||||
int j = 8 - buflen;
|
||||
memcpy(buf + buflen, msg, j);
|
||||
compress(read64(buf));
|
||||
|
||||
msg += j;
|
||||
msglen -= j;
|
||||
buflen = 0;
|
||||
}
|
||||
|
||||
while (msglen >= 8) {
|
||||
compress(read64(msg));
|
||||
msg += 8;
|
||||
msglen -= 8;
|
||||
}
|
||||
|
||||
memcpy(buf, msg, msglen);
|
||||
buflen = msglen;
|
||||
}
|
||||
|
||||
void finish(void *out) {
|
||||
memset(buf + buflen, 0, 8 - buflen);
|
||||
compress(((uint64_t)sum << 56) | read64(buf));
|
||||
|
||||
v2 ^= (OUTLEN == 128) ? 0xee : 0xff;
|
||||
finalize();
|
||||
write64(out, v0 ^ v1 ^ v2 ^ v3);
|
||||
|
||||
if (OUTLEN == 128) {
|
||||
v1 ^= 0xdd;
|
||||
finalize();
|
||||
write64((char *)out + 8, v0 ^ v1 ^ v2 ^ v3);
|
||||
}
|
||||
}
|
||||
|
||||
static void hash(void *out, void *key, void *in, int inlen) {
|
||||
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN> h(key);
|
||||
h.update(in, inlen);
|
||||
h.finish(out);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t v0, v1, v2, v3;
|
||||
uint8_t buf[8];
|
||||
uint8_t buflen = 0;
|
||||
uint8_t sum = 0;
|
||||
|
||||
uint64_t read64(void *loc) {
|
||||
uint64_t val;
|
||||
memcpy(&val, loc, 8);
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void write64(void *loc, uint64_t val) {
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
memcpy(loc, &val, 8);
|
||||
}
|
||||
|
||||
uint64_t bswap(uint64_t val) {
|
||||
return ((val << 56) & 0xff00000000000000) |
|
||||
((val << 40) & 0x00ff000000000000) |
|
||||
((val << 24) & 0x0000ff0000000000) |
|
||||
((val << 8) & 0x000000ff00000000) |
|
||||
((val >> 8) & 0x00000000ff000000) |
|
||||
((val >> 24) & 0x0000000000ff0000) |
|
||||
((val >> 40) & 0x000000000000ff00) |
|
||||
((val >> 56) & 0x00000000000000ff);
|
||||
}
|
||||
|
||||
void round() {
|
||||
v0 += v1;
|
||||
v1 = std::rotl(v1, 13);
|
||||
v1 ^= v0;
|
||||
v0 = std::rotl(v0, 32);
|
||||
v2 += v3;
|
||||
v3 = std::rotl(v3, 16);
|
||||
v3 ^= v2;
|
||||
v0 += v3;
|
||||
v3 = std::rotl(v3, 21);
|
||||
v3 ^= v0;
|
||||
v2 += v1;
|
||||
v1 = std::rotl(v1, 17);
|
||||
v1 ^= v2;
|
||||
v2 = std::rotl(v2, 32);
|
||||
}
|
||||
|
||||
void compress(uint64_t m) {
|
||||
v3 ^= m;
|
||||
for (int i = 0; i < C_ROUNDS; i++)
|
||||
round();
|
||||
v0 ^= m;
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
for (int i = 0; i < D_ROUNDS; i++)
|
||||
round();
|
||||
}
|
||||
};
|
||||
|
||||
using SipHash = SipHashTmpl<2, 4, 64>;
|
||||
using SipHash128 = SipHashTmpl<2, 4, 128>;
|
||||
using SipHash13 = SipHashTmpl<1, 3, 64>;
|
||||
using SipHash13_128 = SipHashTmpl<1, 3, 128>;
|
||||
+30
-33
@@ -1,7 +1,13 @@
|
||||
#include "common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ftruncate _chsize_s
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
// A tar file consists of one or more Ustar header followed by data.
|
||||
// Each Ustar header represents a single file in an archive.
|
||||
//
|
||||
@@ -11,26 +17,6 @@ namespace mold {
|
||||
//
|
||||
// For simplicity, we always emit a PAX header even for a short filename.
|
||||
struct UstarHeader {
|
||||
UstarHeader() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
memset(checksum, ' ', sizeof(checksum));
|
||||
memcpy(magic, "ustar", 5);
|
||||
memcpy(version, "00", 2);
|
||||
|
||||
// Compute checksum
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(*this); i++)
|
||||
sum += ((u8 *)this)[i];
|
||||
|
||||
// We need to convince the compiler that sum isn't too big to silence
|
||||
// -Werror=format-truncation.
|
||||
ASSUME(sum < 01'000'000);
|
||||
snprintf(checksum, sizeof(checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
@@ -50,7 +36,24 @@ struct UstarHeader {
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UstarHeader) == 512);
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
|
||||
static void finalize(UstarHeader &hdr) {
|
||||
memset(hdr.checksum, ' ', sizeof(hdr.checksum));
|
||||
memcpy(hdr.magic, "ustar", 5);
|
||||
memcpy(hdr.version, "00", 2);
|
||||
|
||||
// Compute checksum
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(hdr); i++)
|
||||
sum += ((u8 *)&hdr)[i];
|
||||
|
||||
// We need to convince the compiler that sum isn't too big to silence
|
||||
// -Werror=format-truncation.
|
||||
if (sum >= 01'000'000)
|
||||
unreachable();
|
||||
snprintf(hdr.checksum, sizeof(hdr.checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
static std::string encode_path(std::string basedir, std::string path) {
|
||||
path = path_clean(basedir + "/" + path);
|
||||
@@ -78,14 +81,13 @@ TarWriter::~TarWriter() {
|
||||
|
||||
void TarWriter::append(std::string path, std::string_view data) {
|
||||
// Write PAX header
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
UstarHeader pax;
|
||||
UstarHeader pax = {};
|
||||
|
||||
std::string attr = encode_path(basedir, path);
|
||||
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
|
||||
pax.name[0] = '/';
|
||||
pax.typeflag[0] = 'x';
|
||||
pax.finalize();
|
||||
finalize(pax);
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
|
||||
// Write pathname
|
||||
@@ -93,23 +95,18 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// Write Ustar header
|
||||
UstarHeader ustar;
|
||||
UstarHeader ustar = {};
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
|
||||
ustar.finalize();
|
||||
finalize(ustar);
|
||||
fwrite(&ustar, sizeof(ustar), 1, out);
|
||||
|
||||
// Write file contents
|
||||
fwrite(data.data(), data.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// A tar file must ends with two empty blocks, so write such
|
||||
// terminator and seek back.
|
||||
u8 terminator[BLOCK_SIZE * 2] = {};
|
||||
fwrite(&terminator, BLOCK_SIZE * 2, 1, out);
|
||||
fseek(out, -BLOCK_SIZE * 2, SEEK_END);
|
||||
|
||||
assert(ftell(out) % BLOCK_SIZE == 0);
|
||||
// A tar file must ends with two empty blocks
|
||||
(void)!ftruncate(fileno(out), ftell(out) + BLOCK_SIZE * 2);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::array<u8, 16> get_uuid_v4() {
|
||||
std::array<u8, 16> bytes;
|
||||
|
||||
std::random_device rand;
|
||||
u32 buf[4] = { rand(), rand(), rand(), rand() };
|
||||
memcpy(bytes.data(), buf, 16);
|
||||
|
||||
// Indicate that this is UUIDv4 as defined by RFC4122.
|
||||
bytes[6] = (bytes[6] & 0b00001111) | 0b01000000;
|
||||
bytes[8] = (bytes[8] & 0b00111111) | 0b10000000;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,78 +1,172 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
# This script creates a mold binary distribution. The output is written in
|
||||
# this directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
|
||||
# `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically-linked to
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
|
||||
# these libraries almost always exist on any Linux system. We can't
|
||||
# statically link libc because doing so would disable dlopen(), which is
|
||||
# necessary to open the LTO linker plugin.
|
||||
#
|
||||
# This script aims to produce reproducible outputs. That means if you run
|
||||
# the script twice on the same git commit, it should produce bit-by-bit
|
||||
# identical binary files. This property is crucial as a countermeasure
|
||||
# against supply chain attacks. With this, you can verify that the binary
|
||||
# files distributed on the GitHub release pages are created from the
|
||||
# commit with release tags by rebuilding the binaries yourself.
|
||||
#
|
||||
# Debian provides snapshot.debian.org to host all historical binary
|
||||
# packages. We use it to construct Docker images pinned to a
|
||||
# particular timestamp.
|
||||
#
|
||||
# We aim to use a reasonably old Debian version because we'll dynamically
|
||||
# link glibc to mold, and a binary linked against a newer version of glibc
|
||||
# won't work on a system with an older version of glibc.
|
||||
#
|
||||
# We need GCC 10 or newer to build mold. If GCC 10 is not available on an
|
||||
# old Debian version, we'll build it ourselves.
|
||||
#
|
||||
# You may need to run the following command to use Docker with Qemu:
|
||||
#
|
||||
# $ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
set -e -x
|
||||
cd "$(dirname $0)"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
[[ $arch = arm* ]] && arch=arm
|
||||
if [ $arch = arm64 ]; then
|
||||
arch=aarch64
|
||||
elif [[ $arch = arm* ]]; then
|
||||
arch=arm
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
|
||||
exit 1
|
||||
usage
|
||||
esac
|
||||
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || usage
|
||||
|
||||
image=mold-builder-$arch:1
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
# Create a Docker image if not exists.
|
||||
#
|
||||
# We want to use a reasonably old Linux distro because we'll dynamically
|
||||
# link glibc to mold, and a binary linked against a newer version of
|
||||
# glibc won't work on a system with a older version of glibc.
|
||||
#
|
||||
# We might want to instead statically-link everything, but that'll
|
||||
# disable dlopen(), which means we can't use the linker plugin for LTO.
|
||||
# So we don't want to do that.
|
||||
if ! docker image ls $image | grep -q $image; then
|
||||
if [ $arch = riscv64 ]; then
|
||||
# Ubuntu 18 didn't support RISC-V, so use Ubuntu 20 instead
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM riscv64/ubuntu:20.04
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
else
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM ubuntu:18.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev gcc-10 g++-10 python3 libssl-dev && \
|
||||
\
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
|
||||
rm -rf /var/lib/apt/lists /cmake
|
||||
EOF
|
||||
fi
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-builder-$arch
|
||||
docker_build="docker build --platform linux/$arch -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created Docker image in GitHub's Docker repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
|
||||
docker_build="docker buildx build --platform linux/$arch -t $image --push --cache-to type=inline --cache-from type=registry,ref=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch -"
|
||||
fi
|
||||
|
||||
# Create a Docker image.
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 8 (Jessie) released in April 2015
|
||||
cat <<EOF | $docker_build
|
||||
FROM debian:jessie-20210326@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends wget bzip2 file make autoconf gcc g++ libssl-dev && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
# Build CMake 3.27
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --no-check-certificate https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
./bootstrap --parallel=\$(nproc) && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GCC 10
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/gcc/gcc-10.5.0/gcc-10.5.0.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir isl gmp mpc mpfr && \
|
||||
wget -O- --no-check-certificate https://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2 | tar xjf - --strip-components=1 -C isl && \
|
||||
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/gmp/gmp-6.1.2.tar.bz2 | tar xjf - --strip-components=1 -C gmp && \
|
||||
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/mpc/mpc-1.2.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
|
||||
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/mpfr/mpfr-4.1.0.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
|
||||
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
|
||||
rm -rf /build
|
||||
EOF
|
||||
;;
|
||||
aarch64 | arm | ppc64le | s390x)
|
||||
# Debian 10 (Bullseye) released in July 2019
|
||||
#
|
||||
# We don't want to build GCC for these targets with Qemu becuase
|
||||
# that'd take extremely long time. Also I believe old build machines
|
||||
# are usually x86-64.
|
||||
[ $arch = aarch64 ] && digest=d5ed76c5265576982e6599b6f12392290d9b52b315b19b28b640aaba6e8af002
|
||||
[ $arch = arm ] && digest=bede2623dae269454c5b6dd4af15a10810a5f4ef75963d4eb6531628f98bd633
|
||||
[ $arch = ppc64le ] && digest=255f385e735469493b3465befad59a16f9d46f41d0b50e4fa6d5928c5ee7702a
|
||||
[ $arch = s390x ] && digest=96fb9ce5d3ce7f3dab7c34c18edfee093904cbc7fc19162dbcca22b2cc273b9d
|
||||
|
||||
cat <<EOF | $docker_build
|
||||
FROM debian:bullseye-20231030@sha256:$digest
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
|
||||
ln -sf /usr/bin/gcc-10 /usr/bin/cc && \
|
||||
ln -sf /usr/bin/g++-10 /usr/bin/c++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
riscv64)
|
||||
cat <<EOF | $docker_build
|
||||
FROM riscv64/debian:sid-20240311@sha256:8c02dbe4faa999b588e873cc1759dd9b340f39daf4d7aabdb2c1a87cdc586459
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-12 g++-12 cmake && \
|
||||
ln -sf /usr/bin/gcc-12 /usr/bin/cc && \
|
||||
ln -sf /usr/bin/g++-12 /usr/bin/c++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
# Source tarballs available on GitHub don't contain .git history.
|
||||
# Clone the repo if missing.
|
||||
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp="$(git log -1 --format=%ci)"
|
||||
|
||||
# Build mold in a container.
|
||||
docker run --platform linux/$arch -i --rm -v "$(realpath $(dirname $0)):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" $image \
|
||||
bash -c "mkdir /build &&
|
||||
cd /build &&
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On /mold &&
|
||||
cmake --build . -j\$(nproc) &&
|
||||
[ $arch = arm ] || ctest -j\$(nproc) &&
|
||||
cmake --install . --prefix $dest --strip &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
chown \$OWNER /mold/$dest.tar.gz"
|
||||
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" $image bash -c "
|
||||
set -e
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=On /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date='$timestamp'
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /mold/$dest.tar.gz
|
||||
cp mold /mold
|
||||
chown $(id -u):$(id -g) /mold/$dest.tar.gz /mold/mold
|
||||
sha256sum /mold/$dest.tar.gz
|
||||
"
|
||||
@@ -0,0 +1 @@
|
||||
<svg version="1.1" viewBox="0.0 0.0 600.0 371.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" width="600" height="371" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M0 0L600.0 0L600.0 371.0L0 371.0L0 0Z" fill-rule="nonzero"/><path stroke="#333333" stroke-width="1.0" stroke-linecap="butt" d="M82.5 275.5L581.5 275.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 231.5L581.5 231.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 186.5L581.5 186.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 142.5L581.5 142.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 98.5L581.5 98.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 53.5L581.5 53.5" fill-rule="nonzero"/><clipPath id="id_0"><path d="M82.55 53.916668L581.45 53.916668L581.45 275.45L82.55 275.45L82.55 53.916668Z" clip-rule="nonzero"/></clipPath><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroLine truncated
|
||||
|
After Width: | Height: | Size: 99 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
+44
-15
@@ -1,6 +1,6 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "MOLD" "1" "July 2023" ""
|
||||
.TH "MOLD" "1" "May 2024" ""
|
||||
.SH "NAME"
|
||||
\fBmold\fR \- a modern linker
|
||||
.SH "SYNOPSIS"
|
||||
@@ -49,6 +49,10 @@ Whether good or bad, you should keep these semantics in mind to understand Unix
|
||||
\fBmold\fR's output is deterministic\. That is, if you pass the same object files and the same command\-line options to the same version of \fBmold\fR, it is guaranteed that \fBmold\fR produces the bit\-by\-bit identical output\. The linker's internal randomness, such as the timing of thread scheduling or iteration orders of hash tables, doesn't affect the output\.
|
||||
.P
|
||||
\fBmold\fR does not have any host\-specific default settings\. This is contrary to the GNU linkers, for which some configurable values, such as system\-dependent library search paths, are hard\-coded\. \fBmold\fR depends only on its command\-line arguments\.
|
||||
.SH "OPTION NOTATIONS"
|
||||
Multi\-letter long options may precede either a single dash or double dashes, except for those starting with the letter "o"\. For historical reasons, long options beginning with "o" must precede double dashes\.
|
||||
.P
|
||||
For example, you can spell \fB\-\-as\-needed\fR as \fB\-as\-needed\fR, but \fB\-\-omagic\fR must not be spelled as \fB\-omagic\fR\. \fB\-omagic\fR will be interpreted not as \fB\-\-omagic\fR but as \fB\-o magic\fR\.
|
||||
.SH "MOLD\-SPECIFIC OPTIONS"
|
||||
.TP
|
||||
\fB\-\-chroot\fR=\fIdir\fR
|
||||
@@ -74,6 +78,11 @@ Print out dependency information for input files\.
|
||||
.IP
|
||||
Each line of the output for this option shows which file depends on which file to use a specific symbol\. This option is useful for debugging why some object file in a static archive got linked or why some shared library is kept in an output file's dependency list even with \fB\-\-as\-needed\fR\.
|
||||
.TP
|
||||
\fB\-\-relocatable\-merge\-sections\fR
|
||||
By default, \fBmold\fR doesn't merge input sections by name when merging input object files into a single output object file for \fB\-r\fR\. For example, \fB\.text\.foo\fR and \fB\.text\.bar\fR aren't merged for \fB\-r\fR even though they are merged into \fB\.text\fR based on the default section merging rules\.
|
||||
.IP
|
||||
This option changes the behavior so that \fBmold\fR merges input sections by name by the default section merging rules\.
|
||||
.TP
|
||||
\fB\-\-repro\fR
|
||||
Archive input files, as well as a text file containing command line options, in a tar file so that you can run \fBmold\fR with the exact same inputs again\. This is useful for reporting a bug with a reproducer\. The output filename is \fBpath/to/output\.tar\fR, where \fBpath/to/output\fR is an output filename specified by \fB\-o\fR\.
|
||||
.TP
|
||||
@@ -94,6 +103,11 @@ This option is useful for benchmarking\. Modern CPUs are sensitive to a program'
|
||||
.IP
|
||||
By running a benchmark multiple times with randomized memory layouts using \fB\-\-shuffle\-sections\fR, you can isolate your program's real performance number from the randomness caused by memory layout changes\.
|
||||
.TP
|
||||
\fB\-\-spare\-program\-headers\fR=\fInumber\fR
|
||||
Append the given number of \fBPT_NULL\fR entries to the end of the program header, so that post\-link processing tools can easily add new segments by overwriting the null entries\.
|
||||
.IP
|
||||
Note that ELF requires all \fBPT_LOAD\fR segments to be sorted by \fBp_vaddr\fR\. Therefore, if you add a new LOAD segment, you may need to sort the entire program header\.
|
||||
.TP
|
||||
\fB\-\-stats\fR
|
||||
Print input statistics\.
|
||||
.TP
|
||||
@@ -168,17 +182,15 @@ Use \fIfile\fR as the output file name instead of the default name \fBa\.out\fR\
|
||||
\fB\-r\fR, \fB\-\-relocatable\fR
|
||||
Instead of generating an executable or a shared object file, combine input object files to generate another object file that can be used as an input to a linker\.
|
||||
.TP
|
||||
\fB\-\-relocatable\-merge\-sections\fR
|
||||
By default, \fBmold\fR doesn't merge input sections by name when merging input object files into a single output object file for \fB\-r\fR\. For example, \fB\.text\.foo\fR and \fB\.text\.bar\fR aren't merged for \fB\-r\fR even though they are merged into \fB\.text\fR according to the default section merging rules\.
|
||||
.IP
|
||||
This option changes the behavior so that \fBmold\fR merges input sections by name by the default section merging rules\.
|
||||
.TP
|
||||
\fB\-s\fR, \fB\-\-strip\-all\fR
|
||||
Omit \fB\.symtab\fR section from the output file\.
|
||||
.TP
|
||||
\fB\-u\fR \fIsymbol\fR, \fB\-\-undefined\fR=\fIsymbol\fR
|
||||
If \fIsymbol\fR remains as an undefined symbol after reading all object files, and if there is a static archive that contains an object file defining \fIsymbol\fR, pull out the object file and link it so that the output file contains a definition of \fIsymbol\fR\.
|
||||
.TP
|
||||
\fB\-y\fR \fIsymbol\fR, \fB\-\-trace\-symbol\fR=\fIsymbol\fR
|
||||
Trace references to \fIsymbol\fR\.
|
||||
.TP
|
||||
\fB\-\-Bdynamic\fR
|
||||
Link against shared libraries\.
|
||||
.TP
|
||||
@@ -191,8 +203,14 @@ When creating a shared library, make global symbols export\-only (i\.e\. do not
|
||||
\fB\-\-Bsymbolic\-functions\fR
|
||||
This option has the same effect as \fB\-\-Bsymbolic\fR but works only for function symbols\. Data symbols remain being both imported and exported\.
|
||||
.TP
|
||||
\fB\-\-Bsymbolic\-non\-weak\fR
|
||||
This option has the same effect as \fB\-\-Bsymbolic\fR but works only for non\-weak symbols\. Weak symbols remain being both imported and exported\.
|
||||
.TP
|
||||
\fB\-\-Bsymbolic\-non\-weak\-functions\fR
|
||||
This option has the same effect as \fB\-\-Bsymbolic\fR but works only for non\-weak function symbols\. Data symbols and weak function symbols remain being both imported and exported\.
|
||||
.TP
|
||||
\fB\-\-Bno\-symbolic\fR
|
||||
Cancel \fB\-\-Bsymbolic\fR and \fB\-\-Bsymbolic\-functions\fR\.
|
||||
Cancel \fB\-\-Bsymbolic\fR, \fB\-\-Bsymbolic\-functions\fR, \fB\-\-Bsymbolic\-non\-weak\fR and \fB\-\-Bsymbolic\-non\-weak\-functions\fR\.
|
||||
.TP
|
||||
\fB\-\-Map\fR=\fIfile\fR
|
||||
Write map file to \fIfile\fR\.
|
||||
@@ -352,7 +370,7 @@ Set \fIaddress\fR to section\. \fIaddress\fR is a hexadecimal number that may st
|
||||
Create a share library\.
|
||||
.TP
|
||||
\fB\-\-spare\-dynamic\-tags\fR=\fInumber\fR
|
||||
Reserve the given \fInumber\fR of tags in \fB\.dynamic\fR section\.
|
||||
Append the given number of \fBDT_NULL\fR entries to the end of the \fB\.dynamic\fR section, so that post\-link processing tools can easily add new dynamic tags by overwriting the null entries\.
|
||||
.TP
|
||||
\fB\-\-start\-lib\fR, \fB\-\-end\-lib\fR
|
||||
Handle object files between \fB\-\-start\-lib\fR and \fB\-\-end\-lib\fR as if they were in an archive file\. That means object files between them are linked only when they are needed to resolve undefined symbols\. The options are useful if you want to link object files only when they are needed but want to avoid the overhead of running ar(3)\.
|
||||
@@ -366,6 +384,9 @@ Set target system root directory to \fIdir\fR\.
|
||||
\fB\-\-trace\fR
|
||||
Print name of each input file\.
|
||||
.TP
|
||||
\fB\-\-undefined\-glob\fR=\fIpattern\fR
|
||||
Synonym for \fB\-\-undefined\fR, except that \fB\-\-undefined\-glob\fR takes a glob pattern instead of just a single symbol name\.
|
||||
.TP
|
||||
\fB\-\-undefined\-version\fR, \fB\-\-no\-undefined\-version\fR
|
||||
By default, \fBmold\fR warns on a symbol specified by a version script or by \fB\-\-export\-dynamic\-symbol\fR if it is not defined\. You can silence the warning by \fB\-\-undefined\-version\fR\.
|
||||
.TP
|
||||
@@ -418,8 +439,11 @@ By default, the pages for the stack area (i\.e\. the pages where local variables
|
||||
\fB\-z keep\-text\-section\-prefix\fR, \fB\-z nokeep\-text\-section\-prefix\fR
|
||||
Keep \fB\.text\.hot\fR, \fB\.text\.unknown\fR, \fB\.text\.unlikely\fR, \fB\.text\.startup\fR, and \fB\.text\.exit\fR as separate sections in the final binary instead of merging them as \fB\.text\fR\.
|
||||
.TP
|
||||
\fB\-z rodynamic\fR
|
||||
Make the \fB\.dynamic\fR section read\-only\.
|
||||
.TP
|
||||
\fB\-z relro\fR, \fB\-z norelro\fR
|
||||
Some sections such as \fB\.dynamic\fR have to be writable only during an executable or a shared library file is being loaded to memory\. Once the dynamic linker finishes its job, such sections won't be mutated by anyone\. As a security mitigation, it is preferred to make such segments read\-only during program execution\.
|
||||
Some sections such as \fB\.dynamic\fR have to be writable only during a module is being loaded to memory\. Once the dynamic linker finishes its job, such sections won't be mutated by anyone\. As a security mitigation, it is preferred to make such segments read\-only during program execution\.
|
||||
.IP
|
||||
\fB\-z relro\fR puts such sections into a special segment called \fBrelro\fR\. The dynamic linker makes a relro segment read\-only after it finishes its job\.
|
||||
.IP
|
||||
@@ -443,11 +467,16 @@ Report undefined symbols (even with \fB\-\-shared\fR)\.
|
||||
\fB\-z shstk\fR
|
||||
Enforce shadow stack by turning \fBGNU_PROPERTY_X86_FEATURE_1_SHSTK\fR bit in \fB\.note\.gnu\.property\fR output section\. Shadow stack is part of Intel Control\-flow Enforcement Technology (CET), which is available since Tiger Lake (2020)\.
|
||||
.TP
|
||||
\fB\-z start_stop_visibility\fR=[ \fBhidden\fR | \fBprotected\fR ]
|
||||
If a section name is valid as a C identifier (i\.e\., it matches \fB/^[_a\-zA\-Z][_a\-zA\-Z0\-9]*$/\fR), mold creates \fB__start_SECNAME\fR and \fB__stop_SECNAME\fR symbols to mark the beginning and end of the section, where \fBSECNAME\fR is the section name\.
|
||||
.IP
|
||||
You can make these marker symbols visible from other ELF modules by passing \fB\-z start_stop_visibility=protected\fR\. Default is \fBhidden\fR\.
|
||||
.TP
|
||||
\fB\-z text\fR, \fB\-z notext\fR, \fB\-z textoff\fR
|
||||
\fBmold\fR by default reports an error if dynamic relocations are created in read\-only sections\. If \fB\-z notext\fR or \fB\-z textoff\fR are given, \fBmold\fR creates such dynamic relocations without reporting an error\. \fB\-z text\fR restores the default behavior\.
|
||||
.TP
|
||||
\fB\-z max\-page\-size\fR=\fInumber\fR
|
||||
Some CPU ISAs support multiple different memory page sizes\. This option specifies the maximum page size that an output binary can run on\. The default value is 4 KiB for i386, x86\-64, and RISC\-V, and 64 KiB for ARM64\.
|
||||
Some CPU ISAs support multiple memory page sizes\. This option specifies the maximum page size that an output binary can run on\. In general, binaries built for a larger page size can run on a system with a smaller page size, but not vice versa\. The default value is 4 KiB for i386, x86\-64, and RISC\-V, and 64 KiB for ARM64\.
|
||||
.TP
|
||||
\fB\-z nodefaultlib\fR
|
||||
Make the dynamic loader ignore default search paths\.
|
||||
@@ -470,18 +499,18 @@ Mark DSO to be initialized first at runtime\.
|
||||
\fB\-z interpose\fR
|
||||
Mark object to interpose all DSOs but executable\.
|
||||
.TP
|
||||
\fB\-(\fR, \fB\-)\fR, \fB\-EL\fR, \fB\-O\fR\fInumber\fR, \fB\-\-allow\-shlib\-undefined\fR, \fB\-\-dc\fR, \fB\-\-dp\fR, \fB\-\-end\-group\fR, \fB\-\-no\-add\-needed\fR, \fB\-\-no\-allow\-shlib\-undefined\fR, \fB\-\-no\-copy\-dt\-needed\-entries\fR, \fB\-\-no\-fatal\-warnings\fR, \fB\-\-nostdlib\fR, \fB\-\-rpath\-link=Ar dir\fR, \fB\-\-sort\-common\fR, \fB\-\-sort\-section\fR, \fB\-\-start\-group\fR, \fB\-\-warn\-constructors\fR, \fB\-\-warn\-once\fR, \fB\-\-fix\-cortex\-a53\-835769\fR, \fB\-\-fix\-cortex\-a53\-843419\fR, \fB\-z combreloc\fR, \fB\-z common\-page\-size\fR, \fB\-z nocombreloc\fR
|
||||
\fB\-(\fR, \fB\-)\fR, \fB\-EL\fR, \fB\-O\fR\fInumber\fR, \fB\-\-allow\-shlib\-undefined\fR, \fB\-\-dc\fR, \fB\-\-dp\fR, \fB\-\-end\-group\fR, \fB\-\-no\-add\-needed\fR, \fB\-\-no\-allow\-shlib\-undefined\fR, \fB\-\-no\-copy\-dt\-needed\-entries\fR, \fB\-\-nostdlib\fR, \fB\-\-rpath\-link=Ar dir\fR, \fB\-\-sort\-common\fR, \fB\-\-sort\-section\fR, \fB\-\-start\-group\fR, \fB\-\-warn\-constructors\fR, \fB\-\-warn\-once\fR, \fB\-\-fix\-cortex\-a53\-835769\fR, \fB\-\-fix\-cortex\-a53\-843419\fR, \fB\-z combreloc\fR, \fB\-z common\-page\-size\fR, \fB\-z nocombreloc\fR
|
||||
Ignored
|
||||
.SH "ENVIRONMENT VARIABLES"
|
||||
.TP
|
||||
\fBMOLD_JOBS\fR
|
||||
If this variable is set to \fB1\fR, only one process of \fBmold\fR runs actively\. A mold process invoked while another active mold process is running will wait before doing anything until the active process exits\.
|
||||
If this variable is set to \fB1\fR, only one \fBmold\fR process will run at a time\. If a new mold process is initiated while another is already active, the new process will wait until the active one completes before starting\.
|
||||
.IP
|
||||
The purpose of this environment variable is to reduce peak memory usage\. Since mold is highly parallelized, there's no point in running it simultaneously\. If you run N instances of mold in parallel, it would take N times more time and N times more memory\. If you run them serially, it would still take N times more to finish, but their peak memory usage is reduced to normal\.
|
||||
The primary reason for this environment variable is to minimize peak memory usage\. Since mold is designed to operate with high parallelism, running multiple mold instances simultaneously may not be beneficial\. If you execute N instances of mold concurrently, it could require N times the time and N times the memory\. On the other hand, running them one after the other might still take N times longer, but the peak memory usage would be the same as running just a single instance\.
|
||||
.IP
|
||||
If your build system tends to invoke multiple linker processes simultaneously, you may want to try to set this environment variable to \fB1\fR to see if it could improve overall performance\.
|
||||
If your build system invokes multiple linker processes simultaneously and some of them often get killed due to out\-of\-memory errors, you might consider setting this environment variable to \fB1\fR to see if it addresses the OOM issue\.
|
||||
.IP
|
||||
Currently, any value other than 1 is silently ignored\.
|
||||
Currently, any value other than \fB1\fR is silently ignored\.
|
||||
.TP
|
||||
\fBMOLD_DEBUG\fR
|
||||
If this variable is set to a non\-empty string, \fBmold\fR embeds its command\-line options in the output file's \fB\.comment\fR section\.
|
||||
|
||||
+71
-20
@@ -126,6 +126,16 @@ GNU linkers, for which some configurable values, such as system-dependent
|
||||
library search paths, are hard-coded. `mold` depends only on its command-line
|
||||
arguments.
|
||||
|
||||
## OPTION NOTATIONS
|
||||
|
||||
Multi-letter long options may precede either a single dash or double dashes,
|
||||
except for those starting with the letter "o". For historical reasons, long
|
||||
options beginning with "o" must precede double dashes.
|
||||
|
||||
For example, you can spell `--as-needed` as `-as-needed`, but `--omagic` must
|
||||
not be spelled as `-omagic`. `-omagic` will be interpreted not as `--omagic`
|
||||
but as `-o magic`.
|
||||
|
||||
## MOLD-SPECIFIC OPTIONS
|
||||
|
||||
* `--chroot`=_dir_:
|
||||
@@ -158,6 +168,15 @@ arguments.
|
||||
object file in a static archive got linked or why some shared library is
|
||||
kept in an output file's dependency list even with `--as-needed`.
|
||||
|
||||
* `--relocatable-merge-sections`:
|
||||
By default, `mold` doesn't merge input sections by name when merging input
|
||||
object files into a single output object file for `-r`. For example,
|
||||
`.text.foo` and `.text.bar` aren't merged for `-r` even though they are
|
||||
merged into `.text` based on the default section merging rules.
|
||||
|
||||
This option changes the behavior so that `mold` merges input sections by
|
||||
name by the default section merging rules.
|
||||
|
||||
* `--repro`:
|
||||
Archive input files, as well as a text file containing command line options,
|
||||
in a tar file so that you can run `mold` with the exact same inputs again.
|
||||
@@ -203,6 +222,15 @@ arguments.
|
||||
`--shuffle-sections`, you can isolate your program's real performance number
|
||||
from the randomness caused by memory layout changes.
|
||||
|
||||
* `--spare-program-headers`=_number_:
|
||||
Append the given number of `PT_NULL` entries to the end of the program
|
||||
header, so that post-link processing tools can easily add new segments by
|
||||
overwriting the null entries.
|
||||
|
||||
Note that ELF requires all `PT_LOAD` segments to be sorted by `p_vaddr`.
|
||||
Therefore, if you add a new LOAD segment, you may need to sort the entire
|
||||
program header.
|
||||
|
||||
* `--stats`:
|
||||
Print input statistics.
|
||||
|
||||
@@ -303,15 +331,6 @@ arguments.
|
||||
object files to generate another object file that can be used as an input to
|
||||
a linker.
|
||||
|
||||
* `--relocatable-merge-sections`:
|
||||
By default, `mold` doesn't merge input sections by name when merging input
|
||||
object files into a single output object file for `-r`. For example,
|
||||
`.text.foo` and `.text.bar` aren't merged for `-r` even though they are
|
||||
merged into `.text` according to the default section merging rules.
|
||||
|
||||
This option changes the behavior so that `mold` merges input sections by
|
||||
name by the default section merging rules.
|
||||
|
||||
* `-s`, `--strip-all`:
|
||||
Omit `.symtab` section from the output file.
|
||||
|
||||
@@ -321,6 +340,9 @@ arguments.
|
||||
_symbol_, pull out the object file and link it so that the output file
|
||||
contains a definition of _symbol_.
|
||||
|
||||
* `-y` _symbol_, `--trace-symbol`=_symbol_:
|
||||
Trace references to _symbol_.
|
||||
|
||||
* `--Bdynamic`:
|
||||
Link against shared libraries.
|
||||
|
||||
@@ -337,8 +359,18 @@ arguments.
|
||||
This option has the same effect as `--Bsymbolic` but works only for function
|
||||
symbols. Data symbols remain being both imported and exported.
|
||||
|
||||
* `--Bsymbolic-non-weak`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for non-weak
|
||||
symbols. Weak symbols remain being both imported and exported.
|
||||
|
||||
* `--Bsymbolic-non-weak-functions`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for non-weak
|
||||
function symbols. Data symbols and weak function symbols remain being both
|
||||
imported and exported.
|
||||
|
||||
* `--Bno-symbolic`:
|
||||
Cancel `--Bsymbolic` and `--Bsymbolic-functions`.
|
||||
Cancel `--Bsymbolic`, `--Bsymbolic-functions`, `--Bsymbolic-non-weak` and
|
||||
`--Bsymbolic-non-weak-functions`.
|
||||
|
||||
* `--Map`=_file_:
|
||||
Write map file to _file_.
|
||||
@@ -579,7 +611,9 @@ arguments.
|
||||
Create a share library.
|
||||
|
||||
* `--spare-dynamic-tags`=_number_:
|
||||
Reserve the given _number_ of tags in `.dynamic` section.
|
||||
Append the given number of `DT_NULL` entries to the end of the `.dynamic`
|
||||
section, so that post-link processing tools can easily add new dynamic tags
|
||||
by overwriting the null entries.
|
||||
|
||||
* `--start-lib`, `--end-lib`:
|
||||
Handle object files between `--start-lib` and `--end-lib` as if they were in
|
||||
@@ -597,6 +631,10 @@ arguments.
|
||||
* `--trace`:
|
||||
Print name of each input file.
|
||||
|
||||
* `--undefined-glob`=_pattern_:
|
||||
Synonym for `--undefined`, except that `--undefined-glob` takes a glob
|
||||
pattern instead of just a single symbol name.
|
||||
|
||||
* `--undefined-version`, `--no-undefined-version`:
|
||||
By default, `mold` warns on a symbol specified by a version script or by
|
||||
`--export-dynamic-symbol` if it is not defined. You can silence the warning
|
||||
@@ -684,12 +722,14 @@ arguments.
|
||||
`.text.exit` as separate sections in the final binary instead of merging
|
||||
them as `.text`.
|
||||
|
||||
* `-z rodynamic`:
|
||||
Make the `.dynamic` section read-only.
|
||||
|
||||
* `-z relro`, `-z norelro`:
|
||||
Some sections such as `.dynamic` have to be writable only during an
|
||||
executable or a shared library file is being loaded to memory. Once the
|
||||
dynamic linker finishes its job, such sections won't be mutated by anyone.
|
||||
As a security mitigation, it is preferred to make such segments read-only
|
||||
during program execution.
|
||||
Some sections such as `.dynamic` have to be writable only during a module is
|
||||
being loaded to memory. Once the dynamic linker finishes its job, such
|
||||
sections won't be mutated by anyone. As a security mitigation, it is
|
||||
preferred to make such segments read-only during program execution.
|
||||
|
||||
`-z relro` puts such sections into a special segment called `relro`. The
|
||||
dynamic linker makes a relro segment read-only after it finishes its job.
|
||||
@@ -724,6 +764,15 @@ arguments.
|
||||
Control-flow Enforcement Technology (CET), which is available since Tiger
|
||||
Lake (2020).
|
||||
|
||||
* `-z start_stop_visibility`=[ `hidden` | `protected` ]:
|
||||
If a section name is valid as a C identifier (i.e., it matches
|
||||
`/^[_a-zA-Z][_a-zA-Z0-9]*$/`), mold creates `__start_SECNAME` and
|
||||
`__stop_SECNAME` symbols to mark the beginning and end of the section,
|
||||
where `SECNAME` is the section name.
|
||||
|
||||
You can make these marker symbols visible from other ELF modules by passing
|
||||
`-z start_stop_visibility=protected`. Default is `hidden`.
|
||||
|
||||
* `-z text`, `-z notext`, `-z textoff`:
|
||||
`mold` by default reports an error if dynamic relocations are created in
|
||||
read-only sections. If `-z notext` or `-z textoff` are given, `mold` creates
|
||||
@@ -731,9 +780,11 @@ arguments.
|
||||
default behavior.
|
||||
|
||||
* `-z max-page-size`=_number_:
|
||||
Some CPU ISAs support multiple different memory page sizes. This option
|
||||
specifies the maximum page size that an output binary can run on. The
|
||||
default value is 4 KiB for i386, x86-64, and RISC-V, and 64 KiB for ARM64.
|
||||
Some CPU ISAs support multiple memory page sizes. This option specifies the
|
||||
maximum page size that an output binary can run on. In general, binaries
|
||||
built for a larger page size can run on a system with a smaller page size,
|
||||
but not vice versa. The default value is 4 KiB for i386, x86-64, and RISC-V,
|
||||
and 64 KiB for ARM64.
|
||||
|
||||
* `-z nodefaultlib`:
|
||||
Make the dynamic loader ignore default search paths.
|
||||
@@ -758,7 +809,7 @@ arguments.
|
||||
* `-z interpose`:
|
||||
Mark object to interpose all DSOs but executable.
|
||||
|
||||
* `-(`, `-)`, `-EL`, `-O`_number_, `--allow-shlib-undefined`, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-allow-shlib-undefined`, `--no-copy-dt-needed-entries`, `--no-fatal-warnings`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
|
||||
* `-(`, `-)`, `-EL`, `-O`_number_, `--allow-shlib-undefined`, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-allow-shlib-undefined`, `--no-copy-dt-needed-entries`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
|
||||
Ignored
|
||||
|
||||
## ENVIRONMENT VARIABLES
|
||||
|
||||
+21
-6
@@ -228,11 +228,24 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM and Thumb branch instructions can jump within ±16 MiB.
|
||||
static bool is_jump_reachable(i64 val) {
|
||||
@@ -593,6 +606,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_TARGET1:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
@@ -633,9 +650,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_THM_JUMP19:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_V4BX:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
@@ -647,7 +662,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
|
||||
+7
-7
@@ -52,10 +52,10 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[2]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
@@ -90,7 +90,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
@@ -602,12 +602,12 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f'0200, // br x16
|
||||
0xd503'201f, // nop
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
|
||||
+47
-4
@@ -254,6 +254,34 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x8b83; // lea 0(%ebx), %eax -> mov 0(%ebx), %eax
|
||||
case 0x8d9b: return 0x8b9b; // lea 0(%ebx), %ebx -> mov 0(%ebx), %ebx
|
||||
case 0x8d8b: return 0x8b8b; // lea 0(%ebx), %ecx -> mov 0(%ebx), %ecx
|
||||
case 0x8d93: return 0x8b93; // lea 0(%ebx), %edx -> mov 0(%ebx), %edx
|
||||
case 0x8db3: return 0x8bb3; // lea 0(%ebx), %esi -> mov 0(%ebx), %esi
|
||||
case 0x8dbb: return 0x8bbb; // lea 0(%ebx), %edi -> mov 0(%ebx), %edi
|
||||
case 0x8da3: return 0x8ba3; // lea 0(%ebx), %esp -> mov 0(%ebx), %esp
|
||||
case 0x8dab: return 0x8bab; // lea 0(%ebx), %ebp -> mov 0(%ebx), %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x90b8; // lea 0(%ebx), %eax -> mov $0, %eax
|
||||
case 0x8d9b: return 0x90bb; // lea 0(%ebx), %ebx -> mov $0, %ebx
|
||||
case 0x8d8b: return 0x90b9; // lea 0(%ebx), %ecx -> mov $0, %ecx
|
||||
case 0x8d93: return 0x90ba; // lea 0(%ebx), %edx -> mov $0, %edx
|
||||
case 0x8db3: return 0x90be; // lea 0(%ebx), %esi -> mov $0, %esi
|
||||
case 0x8dbb: return 0x90bf; // lea 0(%ebx), %edi -> mov $0, %edi
|
||||
case 0x8da3: return 0x90bc; // lea 0(%ebx), %esp -> mov $0, %esp
|
||||
case 0x8dab: return 0x90bd; // lea 0(%ebx), %ebp -> mov $0, %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -373,15 +401,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
//
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// lea 0(%ebx), %reg
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// mov %reg, %eax
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x83;
|
||||
u32 insn = relax_tlsdesc_to_ie(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
loc[-2] = 0x90;
|
||||
loc[-1] = 0xb8;
|
||||
u32 insn = relax_tlsdesc_to_le(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -569,11 +569,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
@@ -658,7 +653,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x1e00'000c, // pcaddu18i $t0, 0
|
||||
0x4c00'0180, // jirl $zero, $t0, 0
|
||||
|
||||
+1
-1
@@ -408,7 +408,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ub32 local_thunk[] = {
|
||||
// Get this thunk's address
|
||||
0x7c08'02a6, // mflr r0
|
||||
|
||||
+16
-9
@@ -262,12 +262,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - ctx.tp_addr) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
@@ -378,6 +384,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
@@ -401,6 +408,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -409,7 +417,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// If the destination is .plt.got, we save the current r2, read an
|
||||
// address of a function descriptor from .got, restore %r2 and jump
|
||||
// to the function.
|
||||
@@ -638,15 +646,14 @@ void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Functions referenced by the ELF header also have to have .opd entries.
|
||||
auto mark = [&](std::string_view name) {
|
||||
if (!name.empty())
|
||||
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
};
|
||||
if (!ctx.arg.entry->is_imported)
|
||||
ctx.arg.entry->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
mark(ctx.arg.entry);
|
||||
mark(ctx.arg.init);
|
||||
mark(ctx.arg.fini);
|
||||
if (!ctx.arg.init->is_imported)
|
||||
ctx.arg.init->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
if (!ctx.arg.fini->is_imported)
|
||||
ctx.arg.fini->flags |= NEEDS_PPC_OPD;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
|
||||
+109
-1
@@ -319,6 +319,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_DTPREL34:
|
||||
write34(loc, S + A - ctx.dtp_addr);
|
||||
break;
|
||||
@@ -328,6 +331,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - ctx.tp_addr) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_TPREL34:
|
||||
write34(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
@@ -448,6 +454,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
case R_PPC64_TPREL34:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
@@ -475,6 +482,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
case R_PPC64_DTPREL34:
|
||||
break;
|
||||
default:
|
||||
@@ -484,7 +492,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// If the destination is PLT, we read an address from .got.plt or .got
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
@@ -564,6 +572,106 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// GCC may emit references to the following functions in function prologue
|
||||
// and epiilogue if -Os is specified. For some reason, these functions are
|
||||
// not in libgcc.a and expected to be synthesized by the linker.
|
||||
const std::vector<std::pair<std::string_view, u32>>
|
||||
ppc64_save_restore_insns = {
|
||||
{ "_savegpr0_14", 0xf9c1ff70 }, // std r14,-144(r1)
|
||||
{ "_savegpr0_15", 0xf9e1ff78 }, // std r15,-136(r1)
|
||||
{ "_savegpr0_16", 0xfa01ff80 }, // std r16,-128(r1)
|
||||
{ "_savegpr0_17", 0xfa21ff88 }, // std r17,-120(r1)
|
||||
{ "_savegpr0_18", 0xfa41ff90 }, // std r18,-112(r1)
|
||||
{ "_savegpr0_19", 0xfa61ff98 }, // std r19,-104(r1)
|
||||
{ "_savegpr0_20", 0xfa81ffa0 }, // std r20,-96(r1)
|
||||
{ "_savegpr0_21", 0xfaa1ffa8 }, // std r21,-88(r1)
|
||||
{ "_savegpr0_22", 0xfac1ffb0 }, // std r22,-80(r1)
|
||||
{ "_savegpr0_23", 0xfae1ffb8 }, // std r23,-72(r1)
|
||||
{ "_savegpr0_24", 0xfb01ffc0 }, // std r24,-64(r1)
|
||||
{ "_savegpr0_25", 0xfb21ffc8 }, // std r25,-56(r1)
|
||||
{ "_savegpr0_26", 0xfb41ffd0 }, // std r26,-48(r1)
|
||||
{ "_savegpr0_27", 0xfb61ffd8 }, // std r27,-40(r1)
|
||||
{ "_savegpr0_28", 0xfb81ffe0 }, // std r28,-32(r1)
|
||||
{ "_savegpr0_29", 0xfba1ffe8 }, // std r29,-24(r1)
|
||||
{ "_savegpr0_30", 0xfbc1fff0 }, // std r30,-16(r1)
|
||||
{ "_savegpr0_31", 0xfbe1fff8 }, // std r31,-8(r1)
|
||||
{ "", 0xf8010010 }, // std r0,16(r1)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
|
||||
{ "_restgpr0_14", 0xe9c1ff70 }, // ld r14,-144(r1)
|
||||
{ "_restgpr0_15", 0xe9e1ff78 }, // ld r15,-136(r1)
|
||||
{ "_restgpr0_16", 0xea01ff80 }, // ld r16,-128(r1)
|
||||
{ "_restgpr0_17", 0xea21ff88 }, // ld r17,-120(r1)
|
||||
{ "_restgpr0_18", 0xea41ff90 }, // ld r18,-112(r1)
|
||||
{ "_restgpr0_19", 0xea61ff98 }, // ld r19,-104(r1)
|
||||
{ "_restgpr0_20", 0xea81ffa0 }, // ld r20,-96(r1)
|
||||
{ "_restgpr0_21", 0xeaa1ffa8 }, // ld r21,-88(r1)
|
||||
{ "_restgpr0_22", 0xeac1ffb0 }, // ld r22,-80(r1)
|
||||
{ "_restgpr0_23", 0xeae1ffb8 }, // ld r23,-72(r1)
|
||||
{ "_restgpr0_24", 0xeb01ffc0 }, // ld r24,-64(r1)
|
||||
{ "_restgpr0_25", 0xeb21ffc8 }, // ld r25,-56(r1)
|
||||
{ "_restgpr0_26", 0xeb41ffd0 }, // ld r26,-48(r1)
|
||||
{ "_restgpr0_27", 0xeb61ffd8 }, // ld r27,-40(r1)
|
||||
{ "_restgpr0_28", 0xeb81ffe0 }, // ld r28,-32(r1)
|
||||
{ "_restgpr0_29", 0xe8010010 }, // ld r0,16(r1)
|
||||
{ "", 0xeba1ffe8 }, // ld r29,-24(r1)
|
||||
{ "", 0x7c0803a6 }, // mtlr r0
|
||||
{ "", 0xebc1fff0 }, // ld r30,-16(r1)
|
||||
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
{ "_restgpr0_30", 0xebc1fff0 }, // ld r30,-16(r1)
|
||||
{ "_restgpr0_31", 0xe8010010 }, // ld r0,16(r1)
|
||||
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
|
||||
{ "", 0x7c0803a6 }, // mtlr r0
|
||||
{ "", 0x4e800020 }, // blr
|
||||
|
||||
{ "_savegpr1_14", 0xf9ccff70 }, // std r14,-144(r12)
|
||||
{ "_savegpr1_15", 0xf9ecff78 }, // std r15,-136(r12)
|
||||
{ "_savegpr1_16", 0xfa0cff80 }, // std r16,-128(r12)
|
||||
{ "_savegpr1_17", 0xfa2cff88 }, // std r17,-120(r12)
|
||||
{ "_savegpr1_18", 0xfa4cff90 }, // std r18,-112(r12)
|
||||
{ "_savegpr1_19", 0xfa6cff98 }, // std r19,-104(r12)
|
||||
{ "_savegpr1_20", 0xfa8cffa0 }, // std r20,-96(r12)
|
||||
{ "_savegpr1_21", 0xfaacffa8 }, // std r21,-88(r12)
|
||||
{ "_savegpr1_22", 0xfaccffb0 }, // std r22,-80(r12)
|
||||
{ "_savegpr1_23", 0xfaecffb8 }, // std r23,-72(r12)
|
||||
{ "_savegpr1_24", 0xfb0cffc0 }, // std r24,-64(r12)
|
||||
{ "_savegpr1_25", 0xfb2cffc8 }, // std r25,-56(r12)
|
||||
{ "_savegpr1_26", 0xfb4cffd0 }, // std r26,-48(r12)
|
||||
{ "_savegpr1_27", 0xfb6cffd8 }, // std r27,-40(r12)
|
||||
{ "_savegpr1_28", 0xfb8cffe0 }, // std r28,-32(r12)
|
||||
{ "_savegpr1_29", 0xfbacffe8 }, // std r29,-24(r12)
|
||||
{ "_savegpr1_30", 0xfbccfff0 }, // std r30,-16(r12)
|
||||
{ "_savegpr1_31", 0xfbecfff8 }, // std r31,-8(r12)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
|
||||
{ "_restgpr1_14", 0xe9ccff70 }, // ld r14,-144(r12)
|
||||
{ "_restgpr1_15", 0xe9ecff78 }, // ld r15,-136(r12)
|
||||
{ "_restgpr1_16", 0xea0cff80 }, // ld r16,-128(r12)
|
||||
{ "_restgpr1_17", 0xea2cff88 }, // ld r17,-120(r12)
|
||||
{ "_restgpr1_18", 0xea4cff90 }, // ld r18,-112(r12)
|
||||
{ "_restgpr1_19", 0xea6cff98 }, // ld r19,-104(r12)
|
||||
{ "_restgpr1_20", 0xea8cffa0 }, // ld r20,-96(r12)
|
||||
{ "_restgpr1_21", 0xeaacffa8 }, // ld r21,-88(r12)
|
||||
{ "_restgpr1_22", 0xeaccffb0 }, // ld r22,-80(r12)
|
||||
{ "_restgpr1_23", 0xeaecffb8 }, // ld r23,-72(r12)
|
||||
{ "_restgpr1_24", 0xeb0cffc0 }, // ld r24,-64(r12)
|
||||
{ "_restgpr1_25", 0xeb2cffc8 }, // ld r25,-56(r12)
|
||||
{ "_restgpr1_26", 0xeb4cffd0 }, // ld r26,-48(r12)
|
||||
{ "_restgpr1_27", 0xeb6cffd8 }, // ld r27,-40(r12)
|
||||
{ "_restgpr1_28", 0xeb8cffe0 }, // ld r28,-32(r12)
|
||||
{ "_restgpr1_29", 0xebacffe8 }, // ld r29,-24(r12)
|
||||
{ "_restgpr1_30", 0xebccfff0 }, // ld r30,-16(r12)
|
||||
{ "_restgpr1_31", 0xebecfff8 }, // ld r31,-8(r12)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
};
|
||||
|
||||
void PPC64SaveRestoreSection::copy_buf(Context<E> &ctx) {
|
||||
ul32 *buf = (ul32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
for (auto [label, insn] : ppc64_save_restore_insns)
|
||||
*buf++ = insn;
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return 2;
|
||||
|
||||
+121
-13
@@ -176,14 +176,14 @@ static const ul32 plt_entry_64[] = {
|
||||
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e'3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e'0367, // jalr t1, t3
|
||||
0x0000'0013, // nop
|
||||
0x0010'0073, // ebreak
|
||||
};
|
||||
|
||||
static const ul32 plt_entry_32[] = {
|
||||
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e'2e03, // lw t3, %pcrel_lo(1b)(t3)
|
||||
0x000e'0367, // jalr t1, t3
|
||||
0x0000'0013, // nop
|
||||
0x0010'0073, // ebreak
|
||||
};
|
||||
|
||||
template <>
|
||||
@@ -363,9 +363,55 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
write_utype(loc, G + GOT + A - P);
|
||||
case R_RISCV_GOT_HI20: {
|
||||
// This relocation usually refers to an AUIPC + LD instruction
|
||||
// pair to load a symbol value from the GOT. If the symbol value
|
||||
// is actually a link-time constant, we can materialize the value
|
||||
// directly into a register to eliminate a memory load.
|
||||
i64 rd = get_rd(rel.r_offset);
|
||||
|
||||
switch (removed_bytes) {
|
||||
case 6:
|
||||
// c.li <rd>, val
|
||||
*(ul16 *)loc = 0b010'0'00000'00000'01 | (rd << 7);
|
||||
write_citype(loc, sym.get_addr(ctx));
|
||||
i += 3;
|
||||
break;
|
||||
case 4:
|
||||
// addi <rd>, zero, val
|
||||
*(ul32 *)loc = 0b0010011 | (rd << 7);
|
||||
write_itype(loc, sym.get_addr(ctx));
|
||||
i += 3;
|
||||
break;
|
||||
case 0:
|
||||
if (ctx.arg.relax &&
|
||||
sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 3 < rels.size() &&
|
||||
rels[i + 1].r_type == R_RISCV_RELAX &&
|
||||
rels[i + 2].r_type == R_RISCV_PCREL_LO12_I &&
|
||||
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
|
||||
file.symbols[rels[i + 2].r_sym]->value == r_offset &&
|
||||
rels[i + 3].r_type == R_RISCV_RELAX) {
|
||||
i64 val = S + A - P;
|
||||
if (rd == get_rd(rel.r_offset + 4) && (i32)val == val) {
|
||||
// auipc <rd>, %hi20(val)
|
||||
write_utype(loc, val);
|
||||
|
||||
// addi <rd>, <rd>, %lo12(val)
|
||||
*(ul32 *)(loc + 4) = 0b0010011 | (rd << 15) | (rd << 7);
|
||||
write_itype(loc + 4, val);
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write_utype(loc, G + GOT + A - P);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
write_utype(loc, sym.get_gottp_addr(ctx) + A - P);
|
||||
break;
|
||||
@@ -466,12 +512,49 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TLSDESC_HI20:
|
||||
// RISC-V TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
//
|
||||
// .L0:
|
||||
// auipc tX, 0
|
||||
// R_RISCV_TLSDESC_HI20 foo
|
||||
// l[d|w] tY, tX, 0
|
||||
// R_RISCV_TLSDESC_LOAD_LO12_I .L0
|
||||
// addi a0, tX, 0
|
||||
// R_RISCV_TLSDESC_ADD_LO12_I .L0
|
||||
// jalr t0, tY
|
||||
// R_RISCV_TLSDESC_CALL .L0
|
||||
//
|
||||
// For non-dlopen'd DSO, we may relax the instructions to the following:
|
||||
//
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// auipc a0, %gottp_hi(a0)
|
||||
// l[d|w] a0, %gottp_lo(a0)
|
||||
//
|
||||
// For executable, if the TP offset is small enough, we'll relax
|
||||
// it to the following:
|
||||
//
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// addi a0, zero, %tpoff_lo(a0)
|
||||
//
|
||||
// Otherwise, the following sequence is used:
|
||||
//
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// lui a0, %tpoff_hi(a0)
|
||||
// addi a0, a0, %tpoff_lo(a0)
|
||||
if (removed_bytes == 0)
|
||||
write_utype(loc, sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
case R_RISCV_TLSDESC_ADD_LO12:
|
||||
case R_RISCV_TLSDESC_CALL: {
|
||||
if (removed_bytes == 4)
|
||||
break;
|
||||
|
||||
i64 idx2 = find_paired_reloc();
|
||||
const ElfRel<E> &rel2 = rels[idx2];
|
||||
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
|
||||
@@ -482,27 +565,24 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
if (sym2.has_tlsdesc(ctx))
|
||||
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
|
||||
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_RISCV_TLSDESC_ADD_LO12:
|
||||
if (sym2.has_tlsdesc(ctx)) {
|
||||
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else if (sym2.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x517; // auipc a0,<hi20>
|
||||
*(ul32 *)loc = 0x517; // auipc a0,<hi20>
|
||||
write_utype(loc, sym2.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
if (removed_bytes == 0) {
|
||||
*(ul32 *)loc = 0x537; // lui a0,<hi20>
|
||||
write_utype(loc, S + A - ctx.tp_addr);
|
||||
}
|
||||
*(ul32 *)loc = 0x537; // lui a0,<hi20>
|
||||
write_utype(loc, S + A - ctx.tp_addr);
|
||||
}
|
||||
break;
|
||||
case R_RISCV_TLSDESC_CALL:
|
||||
if (sym2.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym2.has_gottp(ctx)) {
|
||||
// {ld,lw} a0, <lo12>(a0)
|
||||
// l[d|w] a0,<lo12>
|
||||
*(ul32 *)loc = E::is_64 ? 0x53503 : 0x52503;
|
||||
write_itype(loc, sym2.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
@@ -688,7 +768,7 @@ template <>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// If a section is not relaxed, we can copy it as a one big chunk.
|
||||
if (extra.r_deltas.empty()) {
|
||||
uncompress_to(ctx, buf);
|
||||
copy_contents(ctx, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -949,6 +1029,34 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20: {
|
||||
// A GOT_HI20 followed by a PCREL_LO12_I is used to load a value from
|
||||
// GOT. If the loaded value is a link-time constant, we can rewrite
|
||||
// the instructions to directly materialize the value, eliminating a
|
||||
// memory load.
|
||||
if (sym.is_absolute() &&
|
||||
i + 3 < rels.size() &&
|
||||
rels[i + 1].r_type == R_RISCV_RELAX &&
|
||||
rels[i + 2].r_type == R_RISCV_PCREL_LO12_I &&
|
||||
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
|
||||
isec.file.symbols[rels[i + 2].r_sym]->value == rels[i].r_offset &&
|
||||
rels[i + 3].r_type == R_RISCV_RELAX) {
|
||||
i64 rd = get_rd(r.r_offset);
|
||||
|
||||
if (rd == get_rd(r.r_offset + 4)) {
|
||||
u64 val = sym.get_addr(ctx) + r.r_addend;
|
||||
|
||||
if (use_rvc && rd != 0 && sign_extend(val, 5) == val) {
|
||||
// Replace AUIPC + LD with C.LI.
|
||||
delta += 6;
|
||||
} else if (sign_extend(val, 11) == val) {
|
||||
// Replace AUIPC + LD with ADDI.
|
||||
delta += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_HI20: {
|
||||
u64 val = sym.get_addr(ctx) + r.r_addend;
|
||||
i64 rd = get_rd(r.r_offset);
|
||||
|
||||
@@ -90,6 +90,28 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
case R_SH_REL32:
|
||||
case R_SH_TLS_GD_32:
|
||||
case R_SH_TLS_LD_32:
|
||||
case R_SH_TLS_LDO_32:
|
||||
case R_SH_TLS_IE_32:
|
||||
case R_SH_TLS_LE_32:
|
||||
case R_SH_TLS_DTPMOD32:
|
||||
case R_SH_TLS_DTPOFF32:
|
||||
case R_SH_TLS_TPOFF32:
|
||||
case R_SH_GOT32:
|
||||
case R_SH_PLT32:
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTPLT32:
|
||||
*(ul32 *)loc = val;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
|
||||
+65
-8
@@ -178,6 +178,50 @@ static u32 relax_gottpoff(u8 *loc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x488b05; // lea 0(%rip), %rax -> mov 0(%rip), %rax
|
||||
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %rcx -> mov 0(%rip), %rcx
|
||||
case 0x488d15: return 0x488b15; // lea 0(%rip), %rdx -> mov 0(%rip), %rdx
|
||||
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %rbx -> mov 0(%rip), %rbx
|
||||
case 0x488d25: return 0x488b25; // lea 0(%rip), %rsp -> mov 0(%rip), %rsp
|
||||
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %rbp -> mov 0(%rip), %rbp
|
||||
case 0x488d35: return 0x488b35; // lea 0(%rip), %rsi -> mov 0(%rip), %rsi
|
||||
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %rdi -> mov 0(%rip), %rdi
|
||||
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r8 -> mov 0(%rip), %r8
|
||||
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r9 -> mov 0(%rip), %r9
|
||||
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r10 -> mov 0(%rip), %r10
|
||||
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r11 -> mov 0(%rip), %r11
|
||||
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r12 -> mov 0(%rip), %r12
|
||||
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r13 -> mov 0(%rip), %r13
|
||||
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r14 -> mov 0(%rip), %r14
|
||||
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r15 -> mov 0(%rip), %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. We can do this when we know the thread-local variable's TP-
|
||||
// relative address at link-time.
|
||||
@@ -501,19 +545,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
//
|
||||
// mov $foo@TPOFF, %rax
|
||||
// nop
|
||||
//
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// lea 0(%rip), %reg
|
||||
// R_X86_64_GOTPC32_TLSDESC foo
|
||||
// mov %reg, %rax
|
||||
// call *(%rax)
|
||||
// R_X86_64_TLSDESC_CALL foo
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// mov foo@gottpoff(%rip), %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x05;
|
||||
u32 insn = relax_tlsdesc_to_ie(loc - 3);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
// mov $foo@tpoff, %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0xc7;
|
||||
loc[-1] = 0xc0;
|
||||
u32 insn = relax_tlsdesc_to_le(loc - 3);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S - ctx.tp_addr);
|
||||
}
|
||||
break;
|
||||
|
||||
+252
-90
@@ -1,15 +1,15 @@
|
||||
#include "mold.h"
|
||||
#include "../common/cmdline.h"
|
||||
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef _WIN32
|
||||
# define _isatty isatty
|
||||
# define isatty _isatty
|
||||
# define STDERR_FILENO (_fileno(stderr))
|
||||
#else
|
||||
# include <unistd.h>
|
||||
@@ -34,7 +34,7 @@ Options:
|
||||
--no-dynamic-linker
|
||||
-L DIR, --library-path DIR Add DIR to library search path
|
||||
-M, --print-map Write map file to stdout
|
||||
-N, --omagic Do not page align data, do not make text readonly
|
||||
-N, --omagic Do not page align data; do not make text readonly
|
||||
--no-omagic
|
||||
-O NUMBER Ignored
|
||||
-S, --strip-debug Strip .debug_* sections
|
||||
@@ -52,24 +52,29 @@ Options:
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
-y SYMBOL, --trace-symbol SYMBOL
|
||||
Trace references to SYMBOL
|
||||
--Bdynamic, --dy Link against shared libraries (default)
|
||||
--Bstatic, --dn, --static Do not link against shared libraries
|
||||
--Bsymbolic Bind global symbols locally
|
||||
--Bsymbolic-functions Bind global functions locally
|
||||
--Bno-symbolic Cancel --Bsymbolic and --Bsymbolic-functions
|
||||
--Bsymbolic Bind all symbols locally
|
||||
--Bsymbolic-functions Bind function symbols locally
|
||||
--Bsymbolic-non-weak Bind all but weak symbols locally
|
||||
--Bsymbolic-non-weak-functions
|
||||
Bind all but weak function symbols locally
|
||||
--Bno-symbolic Cancel --Bsymbolic options
|
||||
--Map FILE Write map file to a given file
|
||||
--Tbss=ADDR Set address to .bss
|
||||
--Tdata Set address to .data
|
||||
--Ttext Set address to .text
|
||||
--Tdata=ADDR Set address to .data
|
||||
--Ttext=ADDR Set address to .text
|
||||
--allow-multiple-definition Allow multiple definitions
|
||||
--apply-dynamic-relocs Apply link-time values for dynamic relocations (defualt)
|
||||
--apply-dynamic-relocs Apply link-time values for dynamic relocations (default)
|
||||
--no-apply-dynamic-relocs
|
||||
--as-needed Only set DT_NEEDED if used
|
||||
--no-as-needed
|
||||
--build-id [none,md5,sha1,sha256,uuid,HEXSTRING]
|
||||
Generate build ID
|
||||
--no-build-id
|
||||
--chroot DIR Set a given path to root directory
|
||||
--chroot DIR Set a given path to the root directory
|
||||
--color-diagnostics=[auto,always,never]
|
||||
Use colors in diagnostics
|
||||
--color-diagnostics Alias for --color-diagnostics=always
|
||||
@@ -84,12 +89,12 @@ Options:
|
||||
--disable-new-dtags Emit DT_RPATH for --rpath
|
||||
--execute-only Make executable segments unreadable
|
||||
--dp Ignored
|
||||
--dynamic-list Read a list of dynamic symbols (implies -Bsymbolic)
|
||||
--dynamic-list=FILE Read a list of dynamic symbols (implies -Bsymbolic)
|
||||
--eh-frame-hdr Create .eh_frame_hdr section
|
||||
--no-eh-frame-hdr
|
||||
--exclude-libs LIB,LIB,.. Mark all symbols in given libraries hidden
|
||||
--exclude-libs LIB,LIB,.. Mark all symbols in given libraries as hidden
|
||||
--export-dynamic-symbol Put symbols matching glob in the dynamic symbol table
|
||||
--export-dynamic-symbol-list
|
||||
--export-dynamic-symbol-list=FILE
|
||||
Read a list of dynamic symbols
|
||||
--fatal-warnings Treat warnings as errors
|
||||
--no-fatal-warnings Do not treat warnings as errors (default)
|
||||
@@ -107,54 +112,59 @@ Options:
|
||||
Allow merging non-executable sections with --icf
|
||||
--image-base ADDR Set the base address to a given value
|
||||
--init SYMBOL Call SYMBOL at load-time
|
||||
--nmagic Do not page align sections
|
||||
--no-nmagic
|
||||
--no-undefined Report undefined symbols (even with --shared)
|
||||
--noinhibit-exec Create an output file even if errors occur
|
||||
--oformat=binary Omit ELF, section and program headers
|
||||
--oformat=binary Omit ELF, section, and program headers
|
||||
--pack-dyn-relocs=[relr,none]
|
||||
Pack dynamic relocations
|
||||
--package-metadata=STRING Set a given string to .note.package
|
||||
--perf Print performance statistics
|
||||
--pie, --pic-executable Create a position independent executable
|
||||
--pie, --pic-executable Create a position-independent executable
|
||||
--no-pie, --no-pic-executable
|
||||
--pop-state Restore state of flags governing input file handling
|
||||
--pop-state Restore the state of flags governing input file handling
|
||||
--print-gc-sections Print removed unreferenced sections
|
||||
--no-print-gc-sections
|
||||
--print-icf-sections Print folded identical sections
|
||||
--no-print-icf-sections
|
||||
--push-state Save state of flags governing input file handling
|
||||
--push-state Save the state of flags governing input file handling
|
||||
--quick-exit Use quick_exit to exit (default)
|
||||
--no-quick-exit
|
||||
--relax Optimize instructions (default)
|
||||
--no-relax
|
||||
--repro Embed input files to .repro section
|
||||
--repro Embed input files in .repro section
|
||||
--require-defined SYMBOL Require SYMBOL be defined in the final output
|
||||
--retain-symbols-file FILE Keep only symbols listed in FILE
|
||||
--reverse-sections Reverses input sections in the output file
|
||||
--reverse-sections Reverse input sections in the output file
|
||||
--rosegment Put read-only non-executable sections in their own segment (default)
|
||||
--no-rosegment Put read-only non-executable sections in an executable segment
|
||||
--rpath DIR Add DIR to runtime search path
|
||||
--rpath DIR Add DIR to the runtime search path
|
||||
--rpath-link DIR Ignored
|
||||
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
|
||||
--section-start=SECTION=ADDR Set address to section
|
||||
--shared, --Bshareable Create a share library
|
||||
--section-start=SECTION=ADDR Set address for section
|
||||
--shared, --Bshareable Create a shared library
|
||||
--shuffle-sections[=SEED] Randomize the output by shuffling input sections
|
||||
--sort-common Ignored
|
||||
--sort-section Ignored
|
||||
--spare-dynamic-tags NUMBER Reserve give number of tags in .dynamic section
|
||||
--spare-dynamic-tags NUMBER Reserve the given number of tags in the .dynamic section
|
||||
--spare-program-headers NUMBER
|
||||
Reserve the given number of slots in the program header
|
||||
--start-lib Give following object files in-archive-file semantics
|
||||
--end-lib End the effect of --start-lib
|
||||
--stats Print input statistics
|
||||
--sysroot DIR Set target system root directory
|
||||
--sysroot DIR Set the target system root directory
|
||||
--thread-count COUNT, --threads=COUNT
|
||||
Use COUNT number of threads
|
||||
--threads Use multiple threads (default)
|
||||
--no-threads
|
||||
--trace Print name of each input file
|
||||
--undefined-version Do not report version scripts that refer undefined symbols
|
||||
--no-undefined-version Report version scripts that refer undefined symbols (default)
|
||||
--trace Print the name of each input file
|
||||
--undefined-glob PATTERN Force to resolve all symbols that match a given pattern
|
||||
--undefined-version Do not report version scripts that refer to undefined symbols
|
||||
--no-undefined-version Report version scripts that refer to undefined symbols (default)
|
||||
--unique PATTERN Don't merge input sections that match a given pattern
|
||||
--unresolved-symbols [report-all,ignore-all,ignore-in-object-files,ignore-in-shared-libs]
|
||||
How to handle unresolved symbols
|
||||
Handle unresolved symbols
|
||||
--version-script FILE Read version script
|
||||
--warn-common Warn about common symbols
|
||||
--no-warn-common
|
||||
@@ -166,21 +176,21 @@ Options:
|
||||
Report unresolved symbols as errors (default)
|
||||
--whole-archive Include all objects from static archives
|
||||
--no-whole-archive
|
||||
--wrap SYMBOL Use wrapper function for a given symbol
|
||||
--wrap SYMBOL Use a wrapper function for a given symbol
|
||||
-z defs Report undefined symbols (even with --shared)
|
||||
-z nodefs
|
||||
-z common-page-size=VALUE Ignored
|
||||
-z execstack Require executable stack
|
||||
-z execstack Require an executable stack
|
||||
-z noexecstack
|
||||
-z execstack-if-needed Make the stack area execuable if an input file explicitly requests it
|
||||
-z execstack-if-needed Make the stack area executable if an input file explicitly requests it
|
||||
-z initfirst Mark DSO to be initialized first at runtime
|
||||
-z interpose Mark object to interpose all DSOs but executable
|
||||
-z interpose Mark object to interpose all DSOs but the executable
|
||||
-z keep-text-section-prefix Keep .text.{hot,unknown,unlikely,startup,exit} as separate sections in the final binary
|
||||
-z nokeep-text-section-prefix
|
||||
-z lazy Enable lazy function resolution (default)
|
||||
-z max-page-size=VALUE Use VALUE as the memory page size
|
||||
-z nocopyreloc Do not create copy relocations
|
||||
-z nodefaultlib Make the dynamic loader to ignore default search paths
|
||||
-z nodefaultlib Make the dynamic loader ignore default search paths
|
||||
-z nodelete Mark DSO non-deletable at runtime
|
||||
-z nodlopen Mark DSO not available to dlopen
|
||||
-z nodump Mark DSO not available to dldump
|
||||
@@ -190,13 +200,16 @@ Options:
|
||||
-z nopack-relative-relocs
|
||||
-z sectionheader Do not omit section header (default)
|
||||
-z nosectionheader Omit section header
|
||||
-z start_stop_visibility=[hidden,protected]
|
||||
Specify symbol visibility for "__start_SECNAME" and "__stop_SECNAME" symbols
|
||||
-z separate-loadable-segments
|
||||
Separate all loadable segments to different pages
|
||||
-z separate-code Separate code and data into different pages
|
||||
Separate all loadable segments onto different pages
|
||||
-z separate-code Separate code and data onto different pages
|
||||
-z noseparate-code Allow overlap in pages
|
||||
-z stack-size=VALUE Set size of stack segment
|
||||
-z stack-size=VALUE Set the size of the stack segment
|
||||
-z relro Make some sections read-only after relocation (default)
|
||||
-z norelro
|
||||
-z rodynamic Make the .dynamic section read-only
|
||||
-z text Report error if DT_TEXTREL is set
|
||||
-z notext
|
||||
-z textoff
|
||||
@@ -204,6 +217,110 @@ Options:
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha elf64-loongarch elf32-loongarch
|
||||
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha elf64loongarch elf32loongarch)";
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string_view>
|
||||
read_response_file(Context<E> &ctx, std::string_view path, i64 depth) {
|
||||
if (depth > 10)
|
||||
Fatal(ctx) << path << ": response file nesting too deep";
|
||||
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile *mf = must_open_file(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
mf->is_dependency = false;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (isspace(data[0])) {
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto read_quoted = [&]() {
|
||||
char quote = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string buf;
|
||||
while (!data.empty() && data[0] != quote) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
} else {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
}
|
||||
}
|
||||
if (data.empty())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
data = data.substr(1);
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
auto read_unquoted = [&] {
|
||||
std::string buf;
|
||||
while (!data.empty()) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[0])) {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
std::string_view tok;
|
||||
if (data[0] == '\'' || data[0] == '\"')
|
||||
tok = read_quoted();
|
||||
else
|
||||
tok = read_unquoted();
|
||||
|
||||
if (tok.starts_with('@'))
|
||||
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
|
||||
else
|
||||
vec.push_back(tok);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
expand_response_files(Context<E> &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1, 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
static inline std::string_view string_trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return "";
|
||||
str = str.substr(pos);
|
||||
|
||||
pos = str.find_last_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return str;
|
||||
return str.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
static std::vector<std::string> add_dashes(std::string name) {
|
||||
// Single-letter option
|
||||
if (name.size() == 1)
|
||||
@@ -291,8 +408,7 @@ split_by_comma_or_colon(std::string_view str) {
|
||||
|
||||
template <typename E>
|
||||
static void read_retain_symbols_file(Context<E> &ctx, std::string_view path) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(path));
|
||||
MappedFile *mf = must_open_file(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
ctx.arg.retain_symbols_file.reset(new std::unordered_set<std::string_view>);
|
||||
@@ -406,7 +522,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
|
||||
bool version_shown = false;
|
||||
bool warn_shared_textrel = false;
|
||||
bool error_unresolved_symbols = true;
|
||||
std::optional<SeparateCodeKind> z_separate_code;
|
||||
std::optional<bool> report_undefined;
|
||||
std::optional<bool> z_relro;
|
||||
std::optional<u64> shuffle_sections_seed;
|
||||
std::unordered_set<std::string_view> rpaths;
|
||||
@@ -430,8 +548,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if constexpr (is_sparc<E>)
|
||||
ctx.arg.apply_dynamic_relocs = false;
|
||||
|
||||
// For some reason, SH4 always stores relocation addends to
|
||||
// relocated places even though its RELA.
|
||||
if constexpr (is_sh4<E>)
|
||||
ctx.arg.apply_dynamic_relocs = true;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
for (const std::string &opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
if (args.size() == 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
@@ -451,7 +574,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
auto read_eq = [&](std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
for (const std::string &opt : add_dashes(name)) {
|
||||
if (args[0].starts_with(opt + "=")) {
|
||||
arg = args[0].substr(opt.size() + 1);
|
||||
args = args.subspan(1);
|
||||
@@ -462,7 +585,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
auto read_flag = [&](std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
for (const std::string &opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
@@ -501,8 +624,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
|
||||
while (!args.empty()) {
|
||||
if (read_flag("help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
Out(ctx) << "Usage: " << ctx.cmdline_args[0]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -513,19 +636,19 @@ 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")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
Out(ctx) << get_mold_version();
|
||||
version_shown = true;
|
||||
} else if (read_flag("version")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
Out(ctx) << get_mold_version();
|
||||
exit(0);
|
||||
} else if (read_flag("V")) {
|
||||
SyncOut(ctx) << mold_version
|
||||
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
|
||||
<< " aarch64linux\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"
|
||||
<< " elf64alpha\n elf64loongarch\n elf32loongarch";
|
||||
Out(ctx) << get_mold_version()
|
||||
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
|
||||
<< " aarch64linux\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"
|
||||
<< " elf64alpha\n elf64loongarch\n elf32loongarch";
|
||||
version_shown = true;
|
||||
} else if (read_arg("m")) {
|
||||
if (arg == "elf_x86_64") {
|
||||
@@ -574,19 +697,22 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("no-export-dynamic")) {
|
||||
ctx.arg.export_dynamic = false;
|
||||
} else if (read_flag("Bsymbolic")) {
|
||||
ctx.arg.Bsymbolic = true;
|
||||
ctx.arg.Bsymbolic = BSYMBOLIC_ALL;
|
||||
} else if (read_flag("Bsymbolic-functions")) {
|
||||
ctx.arg.Bsymbolic_functions = true;
|
||||
ctx.arg.Bsymbolic = BSYMBOLIC_FUNCTIONS;
|
||||
} else if (read_flag("Bsymbolic-non-weak")) {
|
||||
ctx.arg.Bsymbolic = BSYMBOLIC_NON_WEAK;
|
||||
} else if (read_flag("Bsymbolic-non-weak-functions")) {
|
||||
ctx.arg.Bsymbolic = BSYMBOLIC_NON_WEAK_FUNCTIONS;
|
||||
} else if (read_flag("Bno-symbolic")) {
|
||||
ctx.arg.Bsymbolic = false;
|
||||
ctx.arg.Bsymbolic_functions = false;
|
||||
ctx.arg.Bsymbolic = BSYMBOLIC_NONE;
|
||||
} else if (read_arg("exclude-libs")) {
|
||||
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
|
||||
} else if (read_flag("q") || read_flag("emit-relocs")) {
|
||||
ctx.arg.emit_relocs = true;
|
||||
ctx.arg.discard_locals = false;
|
||||
} else if (read_arg("e") || read_arg("entry")) {
|
||||
ctx.arg.entry = arg;
|
||||
ctx.arg.entry = get_symbol(ctx, arg);
|
||||
} else if (read_arg("Map")) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
@@ -604,6 +730,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.shared = true;
|
||||
} else if (read_arg("spare-dynamic-tags")) {
|
||||
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
|
||||
} else if (read_arg("spare-program-headers")) {
|
||||
ctx.arg.spare_program_headers
|
||||
= parse_number(ctx, "spare-program-headers", arg);
|
||||
} else if (read_flag("start-lib")) {
|
||||
remaining.push_back("--start-lib");
|
||||
} else if (read_flag("start-stop")) {
|
||||
@@ -654,19 +783,22 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.unique = std::move(*pat);
|
||||
} else if (read_arg("unresolved-symbols")) {
|
||||
if (arg == "report-all" || arg == "ignore-in-shared-libs")
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
|
||||
report_undefined = true;
|
||||
else if (arg == "ignore-all" || arg == "ignore-in-object-files")
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_IGNORE;
|
||||
report_undefined = false;
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg("undefined") || read_arg("u")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
ctx.arg.undefined.push_back(get_symbol(ctx, arg));
|
||||
} else if (read_arg("undefined-glob")) {
|
||||
if (!ctx.arg.undefined_glob.add(arg, 0))
|
||||
Fatal(ctx) << "--undefined-glob: invalid pattern: " << arg;
|
||||
} else if (read_arg("require-defined")) {
|
||||
ctx.arg.require_defined.push_back(arg);
|
||||
ctx.arg.require_defined.push_back(get_symbol(ctx, arg));
|
||||
} else if (read_arg("init")) {
|
||||
ctx.arg.init = arg;
|
||||
ctx.arg.init = get_symbol(ctx, arg);
|
||||
} else if (read_arg("fini")) {
|
||||
ctx.arg.fini = arg;
|
||||
ctx.arg.fini = get_symbol(ctx, arg);
|
||||
} else if (read_arg("hash-style")) {
|
||||
if (arg == "sysv") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
@@ -823,19 +955,20 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.page_size = parse_number(ctx, "-z max-page-size", arg);
|
||||
if (!has_single_bit(ctx.page_size))
|
||||
Fatal(ctx) << "-z max-page-size " << arg << ": value must be a power of 2";
|
||||
} else if (read_z_arg("start-stop-visibility")) {
|
||||
if (arg != "hidden")
|
||||
Fatal(ctx) << "-z start-stop-visibility: unsupported visibility: " << arg;
|
||||
} else if (read_z_flag("start-stop-visibility=protected")) {
|
||||
ctx.arg.z_start_stop_visibility_protected = true;
|
||||
} else if (read_z_flag("start-stop-visibility=hidden")) {
|
||||
ctx.arg.z_start_stop_visibility_protected = false;
|
||||
} else if (read_z_flag("noexecstack")) {
|
||||
ctx.arg.z_execstack = false;
|
||||
} else if (read_z_flag("relro")) {
|
||||
z_relro = true;
|
||||
} else if (read_z_flag("norelro")) {
|
||||
z_relro = false;
|
||||
} else if (read_z_flag("defs")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_z_flag("defs") || read_flag("no-undefined")) {
|
||||
report_undefined = true;
|
||||
} else if (read_z_flag("undefs")) {
|
||||
ctx.arg.z_defs = false;
|
||||
report_undefined = false;
|
||||
} else if (read_z_flag("nodlopen")) {
|
||||
ctx.arg.z_dlopen = false;
|
||||
} else if (read_z_flag("nodelete")) {
|
||||
@@ -885,8 +1018,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.z_sectionheader = false;
|
||||
} else if (read_z_flag("rewrite-endbr")) {
|
||||
ctx.arg.z_rewrite_endbr = true;
|
||||
} else if (read_flag("no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_z_flag("rodynamic")) {
|
||||
ctx.arg.z_rodynamic = true;
|
||||
} else if (read_flag("nmagic")) {
|
||||
ctx.arg.nmagic = true;
|
||||
} else if (read_flag("no-nmagic")) {
|
||||
ctx.arg.nmagic = false;
|
||||
} else if (read_flag("fatal-warnings")) {
|
||||
ctx.arg.fatal_warnings = true;
|
||||
} else if (read_flag("no-fatal-warnings")) {
|
||||
@@ -1012,9 +1149,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("strip-debug") || read_flag("S")) {
|
||||
ctx.arg.strip_debug = true;
|
||||
} else if (read_flag("warn-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_WARN;
|
||||
error_unresolved_symbols = false;
|
||||
} else if (read_flag("error-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
|
||||
error_unresolved_symbols = true;
|
||||
} else if (read_arg("rpath")) {
|
||||
add_rpath(arg);
|
||||
} else if (read_arg("R")) {
|
||||
@@ -1096,6 +1233,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("warn-constructors")) {
|
||||
} else if (read_flag("warn-execstack")) {
|
||||
} else if (read_flag("no-warn-execstack")) {
|
||||
} else if (read_flag("long-plt")) {
|
||||
} else if (read_flag("secure-plt")) {
|
||||
} else if (read_arg("rpath-link")) {
|
||||
} else if (read_z_flag("combreloc")) {
|
||||
@@ -1104,21 +1242,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("no-keep-memory")) {
|
||||
} else if (read_arg("max-cache-size")) {
|
||||
} else if (read_arg("version-script")) {
|
||||
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
|
||||
// are treated as positional arguments even though they are actually not
|
||||
// positional. This is because linker scripts (a positional argument)
|
||||
// can also specify a version script, and it's better to consolidate
|
||||
// parsing in read_input_files. In particular, version scripts can
|
||||
// modify ctx.default_version which we initialize *after* parsing
|
||||
// non-positional args, so the parsing cannot be done right here.
|
||||
// --version-script is treated as positional arguments even though
|
||||
// they are actually not positional. This is because linker scripts
|
||||
// (a positional argument) can also specify a version script, and
|
||||
// it's better to consolidate parsing in read_input_files. In
|
||||
// particular, version scripts can modify ctx.default_version which
|
||||
// we initialize *after* parsing non-positional args, so the parsing
|
||||
// cannot be done right here.
|
||||
remaining.push_back("--version-script=" + std::string(arg));
|
||||
} else if (read_arg("dynamic-list")) {
|
||||
ctx.arg.Bsymbolic = true;
|
||||
remaining.push_back("--dynamic-list=" + std::string(arg));
|
||||
ctx.arg.Bsymbolic = BSYMBOLIC_ALL;
|
||||
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
|
||||
} else if (read_arg("export-dynamic-symbol")) {
|
||||
remaining.push_back("--export-dynamic-symbol=" + std::string(arg));
|
||||
ctx.dynamic_list_patterns.push_back({arg, "<command line>"});
|
||||
} else if (read_arg("export-dynamic-symbol-list")) {
|
||||
remaining.push_back("--export-dynamic-symbol-list=" + std::string(arg));
|
||||
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
|
||||
} else if (read_flag("as-needed")) {
|
||||
remaining.push_back("--as-needed");
|
||||
} else if (read_flag("no-as-needed")) {
|
||||
@@ -1172,6 +1310,18 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
if (!report_undefined)
|
||||
report_undefined = !ctx.arg.shared;
|
||||
|
||||
if (*report_undefined) {
|
||||
if (error_unresolved_symbols)
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
|
||||
else
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_WARN;
|
||||
} else {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_IGNORE;
|
||||
}
|
||||
|
||||
if (ctx.arg.retain_symbols_file) {
|
||||
ctx.arg.strip_all = false;
|
||||
ctx.arg.discard_all = false;
|
||||
@@ -1200,6 +1350,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
else if (!ctx.arg.section_order.empty())
|
||||
ctx.arg.z_relro = false;
|
||||
|
||||
if (ctx.arg.nmagic)
|
||||
ctx.arg.z_relro = false;
|
||||
|
||||
if (!ctx.arg.shared) {
|
||||
if (!ctx.arg.filter.empty())
|
||||
Fatal(ctx) << "-filter may not be used without -shared";
|
||||
@@ -1207,7 +1360,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
Fatal(ctx) << "-auxiliary may not be used without -shared";
|
||||
}
|
||||
|
||||
if constexpr (!E::is_rela)
|
||||
if constexpr (!E::is_rela || is_sh4<E>)
|
||||
if (!ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
|
||||
<< E::target_name;
|
||||
@@ -1228,11 +1381,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
|
||||
if (ctx.arg.shared || ctx.arg.export_dynamic)
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.default_version = VER_NDX_LOCAL;
|
||||
|
||||
if (ctx.arg.default_symver) {
|
||||
std::string ver = ctx.arg.soname.empty() ?
|
||||
filepath(ctx.arg.output).filename().string() : std::string(ctx.arg.soname);
|
||||
@@ -1245,6 +1393,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
|
||||
ctx.arg.undefined.push_back(ctx.arg.entry);
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
std::variant<Symbol<E> *, u64> &val = ctx.arg.defsyms[i].second;
|
||||
if (Symbol<E> **sym = std::get_if<Symbol<E> *>(&val))
|
||||
ctx.arg.undefined.push_back(*sym);
|
||||
}
|
||||
|
||||
// --oformat=binary implies --strip-all because without a section
|
||||
// header, there's no way to identify the locations of a symbol
|
||||
// table in an output file in the first place.
|
||||
@@ -1272,6 +1426,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.dependency_file = ctx.arg.chroot + "/" + ctx.arg.dependency_file;
|
||||
}
|
||||
|
||||
// Mark GC root symbols
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
sym->gc_root = true;
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
sym->gc_root = true;
|
||||
ctx.arg.entry->gc_root = true;
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
return remaining;
|
||||
@@ -1279,6 +1440,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<std::string_view> expand_response_files(Context<E> &, char **);
|
||||
template std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
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::elf
|
||||
@@ -81,15 +81,19 @@ enum : u32 {
|
||||
SHT_GROUP = 17,
|
||||
SHT_SYMTAB_SHNDX = 18,
|
||||
SHT_RELR = 19,
|
||||
SHT_LOOS = 0x60000000,
|
||||
SHT_LLVM_ADDRSIG = 0x6fff4c03,
|
||||
SHT_GNU_HASH = 0x6ffffff6,
|
||||
SHT_GNU_VERDEF = 0x6ffffffd,
|
||||
SHT_GNU_VERNEED = 0x6ffffffe,
|
||||
SHT_GNU_VERSYM = 0x6fffffff,
|
||||
SHT_HIOS = 0x6fffffff,
|
||||
SHT_X86_64_UNWIND = 0x70000001,
|
||||
SHT_ARM_EXIDX = 0x70000001,
|
||||
SHT_ARM_ATTRIBUTES = 0x70000003,
|
||||
SHT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
SHT_LOUSER = 0x80000000,
|
||||
SHT_HIUSER = 0xffffffff,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -100,6 +104,7 @@ enum : u32 {
|
||||
SHF_STRINGS = 0x20,
|
||||
SHF_INFO_LINK = 0x40,
|
||||
SHF_LINK_ORDER = 0x80,
|
||||
SHF_OS_NONCONFORMING = 0x100,
|
||||
SHF_GROUP = 0x200,
|
||||
SHF_TLS = 0x400,
|
||||
SHF_COMPRESSED = 0x800,
|
||||
@@ -161,6 +166,7 @@ enum : u32 {
|
||||
VER_NDX_LOCAL = 0,
|
||||
VER_NDX_GLOBAL = 1,
|
||||
VER_NDX_LAST_RESERVED = 1,
|
||||
VER_NDX_UNSPECIFIED = 0xffff,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -1739,7 +1745,7 @@ struct ElfSym<ARM64> {
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 arm64_variant_pcs : 1;
|
||||
u8 : 6;
|
||||
u8 : 5;
|
||||
u8 st_visibility : 2;
|
||||
#endif
|
||||
|
||||
@@ -1821,6 +1827,22 @@ struct ElfRel<SPARC64> {
|
||||
ib64 r_addend;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfRel<SH4> {
|
||||
ElfRel() = default;
|
||||
|
||||
// Addend is ignored except for base relocations because even though
|
||||
// SH4 is RELA, r_addend is ignored in most cases and works as if it
|
||||
// were REL.
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
: r_offset(offset), r_type(type), r_sym(sym), r_addend(sym ? 0 : addend) {}
|
||||
|
||||
ul32 r_offset;
|
||||
u8 r_type;
|
||||
ul24 r_sym;
|
||||
il32 r_addend;
|
||||
};
|
||||
|
||||
//
|
||||
// Machine descriptions
|
||||
//
|
||||
@@ -1868,7 +1890,7 @@ struct X86_64 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
static constexpr u8 filler[] = { 0xcc }; // int3
|
||||
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
@@ -1893,7 +1915,7 @@ struct I386 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
static constexpr u8 filler[] = { 0xcc }; // int3
|
||||
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
@@ -1920,7 +1942,7 @@ struct ARM64 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 };
|
||||
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 }; // brk
|
||||
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
|
||||
@@ -1947,7 +1969,7 @@ struct ARM32 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 16;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0xff, 0xde };
|
||||
static constexpr u8 filler[] = { 0xff, 0xde }; // udf
|
||||
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
|
||||
@@ -1973,7 +1995,7 @@ struct RV64 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
@@ -2006,7 +2028,7 @@ struct RV32 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
@@ -2043,7 +2065,7 @@ struct PPC32 {
|
||||
static constexpr u32 pltgot_size = 36;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 36;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
|
||||
@@ -2085,7 +2107,7 @@ struct PPC64V1 : PPC64 {
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 28;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
|
||||
};
|
||||
|
||||
struct PPC64V2 : PPC64 {
|
||||
@@ -2096,7 +2118,7 @@ struct PPC64V2 : PPC64 {
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 24;
|
||||
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f };
|
||||
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f }; // trap
|
||||
};
|
||||
|
||||
struct S390X {
|
||||
@@ -2109,7 +2131,7 @@ struct S390X {
|
||||
static constexpr u32 plt_hdr_size = 48;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x07, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x07, 0x00 }; // nop
|
||||
|
||||
static constexpr u32 R_COPY = R_390_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
|
||||
@@ -2133,7 +2155,7 @@ struct SPARC64 {
|
||||
static constexpr u32 plt_hdr_size = 128;
|
||||
static constexpr u32 plt_size = 32;
|
||||
static constexpr u32 pltgot_size = 32;
|
||||
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 };
|
||||
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 }; // ta 5
|
||||
|
||||
static constexpr u32 R_COPY = R_SPARC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
|
||||
@@ -2157,7 +2179,7 @@ struct M68K {
|
||||
static constexpr u32 plt_hdr_size = 18;
|
||||
static constexpr u32 plt_size = 14;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0x4a, 0xfc };
|
||||
static constexpr u8 filler[] = { 0x4a, 0xfc }; // illegal
|
||||
|
||||
static constexpr u32 R_COPY = R_68K_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
|
||||
@@ -2180,7 +2202,7 @@ struct SH4 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 12;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x00, 0x90 }; // nop
|
||||
|
||||
static constexpr u32 R_COPY = R_SH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
|
||||
@@ -2203,7 +2225,7 @@ struct ALPHA {
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 0;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 }; // bugchk
|
||||
|
||||
static constexpr u32 R_COPY = R_ALPHA_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
|
||||
@@ -2228,7 +2250,7 @@ struct LOONGARCH64 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
|
||||
@@ -2254,7 +2276,7 @@ struct LOONGARCH32 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
|
||||
|
||||
+49
-53
@@ -15,6 +15,10 @@ static bool should_keep(const InputSection<E> &isec) {
|
||||
u32 flags = isec.shdr().sh_flags;
|
||||
std::string_view name = isec.name();
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
return true;
|
||||
|
||||
return (flags & SHF_GNU_RETAIN) ||
|
||||
type == SHT_NOTE ||
|
||||
type == SHT_INIT_ARRAY ||
|
||||
@@ -34,44 +38,10 @@ static bool mark_section(InputSection<E> *isec) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
|
||||
// If this is a text section, .eh_frame may contain records
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
// Mark a fragment as alive.
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (mark_section(sym.get_input_section())) {
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void collect_root_set(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
static tbb::concurrent_vector<InputSection<E> *>
|
||||
collect_root_set(Context<E> &ctx) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
|
||||
auto enqueue_section = [&](InputSection<E> *isec) {
|
||||
if (mark_section(isec))
|
||||
@@ -106,22 +76,13 @@ static void collect_root_set(Context<E> &ctx,
|
||||
}
|
||||
});
|
||||
|
||||
// Add sections containing exported symbols
|
||||
// Add sections containing gc root or exported symbols
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file && sym->is_exported)
|
||||
if (sym->file == file && (sym->gc_root || sym->is_exported))
|
||||
enqueue_symbol(sym);
|
||||
});
|
||||
|
||||
// Add sections referenced by root symbols.
|
||||
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
// .eh_frame consists of variable-length records called CIE and FDE
|
||||
// records, and they are a unit of inclusion or exclusion.
|
||||
// We just keep all CIEs and everything that are referenced by them.
|
||||
@@ -130,6 +91,43 @@ static void collect_root_set(Context<E> &ctx,
|
||||
for (const ElfRel<E> &rel : cie.get_rels())
|
||||
enqueue_symbol(file->symbols[rel.r_sym]);
|
||||
});
|
||||
|
||||
return rootset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
|
||||
// If this is a text section, .eh_frame may contain records
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Symbol can refer to either a section fragment or an input section.
|
||||
// Mark a fragment as alive.
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (mark_section(sym.get_input_section())) {
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all reachable sections
|
||||
@@ -139,7 +137,7 @@ static void mark(Context<E> &ctx,
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder) {
|
||||
tbb::feeder<InputSection<E> *> &feeder) {
|
||||
visit(ctx, isec, feeder, 0);
|
||||
});
|
||||
}
|
||||
@@ -154,7 +152,7 @@ static void sweep(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && !isec->is_visited) {
|
||||
if (ctx.arg.print_gc_sections)
|
||||
SyncOut(ctx) << "removing unused section " << *isec;
|
||||
Out(ctx) << "removing unused section " << *isec;
|
||||
isec->kill();
|
||||
counter++;
|
||||
}
|
||||
@@ -165,9 +163,7 @@ static void sweep(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
collect_root_set(ctx, rootset);
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
+7
-6
@@ -135,13 +135,13 @@ struct NameType {
|
||||
}
|
||||
|
||||
std::string_view name;
|
||||
u32 hash;
|
||||
u64 hash;
|
||||
u8 type;
|
||||
};
|
||||
|
||||
struct MapValue {
|
||||
u32 hash = 0;
|
||||
Atomic<u32> count = 0;
|
||||
u32 gdb_hash = 0;
|
||||
Atomic<u32> count;
|
||||
u32 name_offset = 0;
|
||||
u32 type_offset = 0;
|
||||
};
|
||||
@@ -539,7 +539,7 @@ static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
|
||||
u8 type = *p++;
|
||||
std::string_view name = (char *)p;
|
||||
p += name.size() + 1;
|
||||
cu->nametypes.push_back({name, gdb_hash(name), type});
|
||||
cu->nametypes.push_back({name, hash_string(name), type});
|
||||
}
|
||||
|
||||
return size;
|
||||
@@ -683,7 +683,8 @@ void write_gdb_index(Context<E> &ctx) {
|
||||
for (NameType &nt : cu.nametypes) {
|
||||
MapValue *ent;
|
||||
bool inserted;
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash, MapValue{nt.hash});
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash,
|
||||
MapValue{gdb_hash(nt.name)});
|
||||
ent->count++;
|
||||
cu.entries.push_back(ent);
|
||||
}
|
||||
@@ -750,7 +751,7 @@ void write_gdb_index(Context<E> &ctx) {
|
||||
ul32 *ht = (ul32 *)(buf + hdr.symtab_offset);
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
u32 hash = ent->value.hash;
|
||||
u32 hash = ent->value.gdb_hash;
|
||||
u32 step = ((hash * 17) & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
|
||||
|
||||
+45
-46
@@ -65,7 +65,7 @@
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "blake3.h"
|
||||
#include "../common/siphash.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
@@ -78,34 +78,43 @@
|
||||
|
||||
static constexpr int64_t HASH_SIZE = 16;
|
||||
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
using Digest = std::array<uint8_t, HASH_SIZE>;
|
||||
|
||||
namespace std {
|
||||
template <> struct hash<Digest> {
|
||||
size_t operator()(const Digest &k) const {
|
||||
return *(int64_t *)&k[0];
|
||||
static_assert(sizeof(size_t) <= HASH_SIZE);
|
||||
size_t val;
|
||||
memcpy(&val, k.data(), sizeof(size_t));
|
||||
return val;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static u8 hmac_key[16];
|
||||
|
||||
template <typename E>
|
||||
static void uniquify_cies(Context<E> &ctx) {
|
||||
Timer t(ctx, "uniquify_cies");
|
||||
std::vector<CieRecord<E> *> cies;
|
||||
|
||||
auto find = [&](CieRecord<E> &cie) -> i64 {
|
||||
for (i64 i = 0; i < cies.size(); i++)
|
||||
if (cie_equals(cie, *cies[i]))
|
||||
return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (CieRecord<E> &cie : file->cies) {
|
||||
for (i64 i = 0; i < cies.size(); i++) {
|
||||
if (cie.equals(*cies[i])) {
|
||||
cie.icf_idx = i;
|
||||
goto found;
|
||||
}
|
||||
if (i64 idx = find(cie); idx != -1) {
|
||||
cie.icf_idx = idx;
|
||||
} else {
|
||||
cie.icf_idx = cies.size();
|
||||
cies.push_back(&cie);
|
||||
}
|
||||
cie.icf_idx = cies.size();
|
||||
cies.push_back(&cie);
|
||||
found:;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,26 +128,14 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
|
||||
return false;
|
||||
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
if (shdr.sh_flags & SHF_EXECINSTR)
|
||||
return (ctx.arg.icf_all || !isec.address_taken) &&
|
||||
name != ".init" && name != ".fini";
|
||||
} else {
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.output_section && isec.output_section->is_relro;
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
}
|
||||
|
||||
static Digest digest_final(blake3_hasher *hasher) {
|
||||
assert(HASH_SIZE <= BLAKE3_OUT_LEN);
|
||||
|
||||
u8 buf[BLAKE3_OUT_LEN];
|
||||
blake3_hasher_finalize(hasher, buf, BLAKE3_OUT_LEN);
|
||||
|
||||
Digest digest;
|
||||
memcpy(digest.data(), buf, HASH_SIZE);
|
||||
return digest;
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.output_section && isec.output_section->is_relro;
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -234,16 +231,15 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
SipHash13_128 hasher(hmac_key);
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
blake3_hasher_update(&hasher, (u8 *)&val, sizeof(val));
|
||||
hasher.update((u8 *)&val, sizeof(val));
|
||||
};
|
||||
|
||||
auto hash_string = [&](std::string_view str) {
|
||||
hash(str.size());
|
||||
blake3_hasher_update(&hasher, (u8 *)str.data(), str.size());
|
||||
hasher.update((u8 *)str.data(), str.size());
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
@@ -299,7 +295,9 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
|
||||
return digest_final(&hasher);
|
||||
Digest digest;
|
||||
hasher.finish(digest.data());
|
||||
return digest;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -400,7 +398,7 @@ static void gather_edges(Context<E> &ctx,
|
||||
template <typename E>
|
||||
static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
std::span<u32> edges, std::span<u32> edge_indices,
|
||||
bool &slot, BitVector &converged,
|
||||
bool &slot, std::span<u8> converged,
|
||||
tbb::affinity_partitioner &ap) {
|
||||
static Counter round("icf_round");
|
||||
round++;
|
||||
@@ -409,25 +407,24 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
tbb::enumerable_thread_specific<i64> changed;
|
||||
|
||||
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
|
||||
if (converged.get(i))
|
||||
if (converged[i])
|
||||
return;
|
||||
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
blake3_hasher_update(&hasher, digests[2][i].data(), HASH_SIZE);
|
||||
SipHash13_128 hasher(hmac_key);
|
||||
hasher.update(digests[2][i].data(), HASH_SIZE);
|
||||
|
||||
i64 begin = edge_indices[i];
|
||||
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
|
||||
|
||||
for (i64 j : edges.subspan(begin, end - begin))
|
||||
blake3_hasher_update(&hasher, digests[slot][j].data(), HASH_SIZE);
|
||||
hasher.update(digests[slot][j].data(), HASH_SIZE);
|
||||
|
||||
digests[!slot][i] = digest_final(&hasher);
|
||||
hasher.finish(digests[!slot][i].data());
|
||||
|
||||
if (digests[slot][i] == digests[!slot][i]) {
|
||||
// This node has converged. Skip further iterations as it will
|
||||
// yield the same hash.
|
||||
converged.set(i);
|
||||
converged[i] = true;
|
||||
} else {
|
||||
changed.local()++;
|
||||
}
|
||||
@@ -479,17 +476,17 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
if (begin == end)
|
||||
continue;
|
||||
|
||||
SyncOut(ctx) << "selected section " << *leader;
|
||||
Out(ctx) << "selected section " << *leader;
|
||||
|
||||
i64 n = 0;
|
||||
for (auto it = begin; it != end; it++) {
|
||||
SyncOut(ctx) << " removing identical section " << *it->second;
|
||||
Out(ctx) << " removing identical section " << *it->second;
|
||||
n++;
|
||||
}
|
||||
saved_bytes += leader->contents.size() * n;
|
||||
}
|
||||
|
||||
SyncOut(ctx) << "ICF saved " << saved_bytes << " bytes";
|
||||
Out(ctx) << "ICF saved " << saved_bytes << " bytes";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -498,6 +495,8 @@ void icf_sections(Context<E> &ctx) {
|
||||
if (ctx.objs.empty())
|
||||
return;
|
||||
|
||||
get_random_bytes(hmac_key, sizeof(hmac_key));
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
|
||||
@@ -522,7 +521,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
std::vector<u32> edge_indices;
|
||||
gather_edges<E>(ctx, sections, edges, edge_indices);
|
||||
|
||||
BitVector converged(digests[0].size());
|
||||
std::vector<u8> converged(digests[0].size());
|
||||
bool slot = 0;
|
||||
|
||||
// Execute the propagation rounds until convergence is obtained.
|
||||
@@ -565,7 +564,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group sections by BLAKE3 digest.
|
||||
// Group sections by hash values.
|
||||
{
|
||||
Timer t(ctx, "group");
|
||||
|
||||
|
||||
+277
-204
@@ -10,8 +10,59 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// If we haven't seen the same `key` before, create a new instance
|
||||
// of Symbol and returns it. Otherwise, returns the previously-
|
||||
// instantiated object. `key` is usually the same as `name`.
|
||||
template <typename E>
|
||||
InputFile<E>::InputFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
Symbol<E> *get_symbol(Context<E> &ctx, std::string_view key,
|
||||
std::string_view name) {
|
||||
typename decltype(ctx.symbol_map)::const_accessor acc;
|
||||
ctx.symbol_map.insert(acc, {key, Symbol<E>(name, ctx.arg.demangle)});
|
||||
return const_cast<Symbol<E> *>(&acc->second);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Symbol<E> *get_symbol(Context<E> &ctx, std::string_view key) {
|
||||
std::string_view name = key.substr(0, key.find('@'));
|
||||
return get_symbol(ctx, key, name);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_rust_symbol(const Symbol<E> &sym) {
|
||||
// The legacy Rust mangling scheme is indistinguishtable from C++.
|
||||
// We don't want to accidentally demangle C++ symbols as Rust ones.
|
||||
// So, the legacy mangling scheme will be demangled only when we
|
||||
// know the object file was created by rustc.
|
||||
if (sym.file && !sym.file->is_dso && ((ObjectFile<E> *)sym.file)->is_rust_obj)
|
||||
return true;
|
||||
|
||||
// "_R" is the prefix of the new Rust mangling scheme.
|
||||
return sym.name().starts_with("_R");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string_view demangle(const Symbol<E> &sym) {
|
||||
if (is_rust_symbol(sym)) {
|
||||
if (std::optional<std::string_view> s = demangle_rust(sym.name()))
|
||||
return *s;
|
||||
} else {
|
||||
if (std::optional<std::string_view> s = demangle_cpp(sym.name()))
|
||||
return *s;
|
||||
}
|
||||
return sym.name();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym) {
|
||||
if (sym.demangle)
|
||||
out << demangle(sym);
|
||||
else
|
||||
out << sym.name();
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputFile<E>::InputFile(Context<E> &ctx, MappedFile *mf)
|
||||
: mf(mf), filename(mf->name) {
|
||||
if (mf->size < sizeof(ElfEhdr<E>))
|
||||
Fatal(ctx) << *this << ": file too small";
|
||||
@@ -56,22 +107,6 @@ ElfShdr<E> *InputFile<E>::find_section(i64 type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : get_global_syms()) {
|
||||
if (__atomic_load_n(&sym->file, __ATOMIC_ACQUIRE) == this) {
|
||||
sym->origin = 0;
|
||||
sym->value = -1;
|
||||
sym->sym_idx = -1;
|
||||
sym->ver_idx = 0;
|
||||
sym->is_weak = false;
|
||||
sym->is_imported = false;
|
||||
sym->is_exported = false;
|
||||
__atomic_store_n(&sym->file, nullptr, __ATOMIC_RELEASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the source filename. It should be listed in symtab as STT_FILE.
|
||||
template <typename E>
|
||||
std::string_view InputFile<E>::get_source_name() const {
|
||||
@@ -82,7 +117,7 @@ std::string_view InputFile<E>::get_source_name() const {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E>::ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
ObjectFile<E>::ObjectFile(Context<E> &ctx, MappedFile *mf,
|
||||
std::string archive_name, bool is_in_lib)
|
||||
: InputFile<E>(ctx, mf), archive_name(archive_name), is_in_lib(is_in_lib) {
|
||||
this->is_alive = !is_in_lib;
|
||||
@@ -90,7 +125,7 @@ ObjectFile<E>::ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile *mf,
|
||||
std::string archive_name, bool is_in_lib) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>(ctx, mf, archive_name, is_in_lib);
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
@@ -191,11 +226,44 @@ static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_known_section_type(const ElfShdr<E> &shdr) {
|
||||
u32 ty = shdr.sh_type;
|
||||
u32 flags = shdr.sh_flags;
|
||||
|
||||
if (ty == SHT_PROGBITS ||
|
||||
ty == SHT_NOTE ||
|
||||
ty == SHT_NOBITS ||
|
||||
ty == SHT_INIT_ARRAY ||
|
||||
ty == SHT_FINI_ARRAY ||
|
||||
ty == SHT_PREINIT_ARRAY)
|
||||
return true;
|
||||
|
||||
if (SHT_LOUSER <= ty && ty <= SHT_HIUSER && !(flags & SHF_ALLOC))
|
||||
return true;
|
||||
if (SHT_LOOS <= ty && ty <= SHT_HIOS && !(flags & SHF_OS_NONCONFORMING))
|
||||
return true;
|
||||
if (is_x86<E> && ty == SHT_X86_64_UNWIND)
|
||||
return true;
|
||||
if (is_arm32<E> && (ty == SHT_ARM_EXIDX || ty == SHT_ARM_ATTRIBUTES))
|
||||
return true;
|
||||
if (is_riscv<E> && ty == SHT_RISCV_ATTRIBUTES)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// Read sections
|
||||
for (i64 i = 0; i < this->elf_sections.size(); i++) {
|
||||
const ElfShdr<E> &shdr = this->elf_sections[i];
|
||||
std::string_view name = this->shstrtab.data() + shdr.sh_name;
|
||||
|
||||
if ((shdr.sh_flags & SHF_EXCLUDE) &&
|
||||
name.starts_with(".gnu.offload_lto_.symtab.")) {
|
||||
this->is_gcc_offload_obj = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((shdr.sh_flags & SHF_EXCLUDE) && !(shdr.sh_flags & SHF_ALLOC) &&
|
||||
shdr.sh_type != SHT_LLVM_ADDRSIG && !ctx.arg.relocatable)
|
||||
@@ -245,7 +313,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
typename decltype(ctx.comdat_groups)::const_accessor acc;
|
||||
ctx.comdat_groups.insert(acc, {signature, ComdatGroup()});
|
||||
ComdatGroup *group = const_cast<ComdatGroup *>(&acc->second);
|
||||
comdat_groups.push_back({group, (u32)i, entries.subspan(1)});
|
||||
comdat_groups.push_back({group, (i32)i, entries.subspan(1)});
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
@@ -255,8 +323,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
case SHT_STRTAB:
|
||||
case SHT_NULL:
|
||||
break;
|
||||
default: {
|
||||
std::string_view name = this->shstrtab.data() + shdr.sh_name;
|
||||
default:
|
||||
if (!is_known_section_type(shdr))
|
||||
Fatal(ctx) << *this << ": " << name << ": unsupported section type: 0x"
|
||||
<< std::hex << (u32)shdr.sh_type;
|
||||
|
||||
// .note.GNU-stack section controls executable-ness of the stack
|
||||
// area in GNU linkers. We ignore that section because silently
|
||||
@@ -299,6 +369,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
is_debug_section(shdr, name))
|
||||
continue;
|
||||
|
||||
if (name == ".comment" &&
|
||||
this->get_string(ctx, shdr).starts_with("rustc "))
|
||||
this->is_rust_obj = true;
|
||||
|
||||
// If an output file doesn't have a section header (i.e.
|
||||
// --oformat=binary is given), we discard all non-memory-allocated
|
||||
// sections. This is because without a section header, we can't find
|
||||
@@ -310,12 +384,25 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
llvm_addrsig = std::move(this->sections[i]);
|
||||
// sh_link should be the index of the symbol table section.
|
||||
// Tools that mutates the symbol table, such as objcopy or `ld -r`
|
||||
// tend to not preserve sh_link, so we ignore such section.
|
||||
if (shdr.sh_link != 0)
|
||||
llvm_addrsig = std::move(this->sections[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shdr.sh_type == SHT_INIT_ARRAY ||
|
||||
shdr.sh_type == SHT_FINI_ARRAY ||
|
||||
shdr.sh_type == SHT_PREINIT_ARRAY)
|
||||
this->has_init_array = true;
|
||||
|
||||
if (name == ".ctors" || name.starts_with(".ctors.") ||
|
||||
name == ".dtors" || name.starts_with(".dtors."))
|
||||
this->has_ctors = true;
|
||||
|
||||
if (name == ".eh_frame")
|
||||
eh_frame_section = this->sections[i].get();
|
||||
eh_frame_sections.push_back(this->sections[i].get());
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
@@ -356,7 +443,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
counter++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach relocation sections to their target sections.
|
||||
@@ -407,78 +493,75 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// This function parses an input .eh_frame section.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
|
||||
if (!eh_frame_section)
|
||||
return;
|
||||
for (InputSection<E> *isec : eh_frame_sections) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
|
||||
InputSection<E> &isec = *eh_frame_section;
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
// Read CIEs and FDEs until empty.
|
||||
std::string_view contents = this->get_string(ctx, isec->shdr());
|
||||
i64 rel_idx = 0;
|
||||
|
||||
// Read CIEs and FDEs until empty.
|
||||
std::string_view contents = this->get_string(ctx, isec.shdr());
|
||||
i64 rel_idx = 0;
|
||||
for (std::string_view data = contents; !data.empty();) {
|
||||
i64 size = *(U32<E> *)data.data();
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
for (std::string_view data = contents; !data.empty();) {
|
||||
i64 size = *(U32<E> *)data.data();
|
||||
if (size == 0)
|
||||
break;
|
||||
i64 begin_offset = data.data() - contents.data();
|
||||
i64 end_offset = begin_offset + size + 4;
|
||||
i64 id = *(U32<E> *)(data.data() + 4);
|
||||
data = data.substr(size + 4);
|
||||
|
||||
i64 begin_offset = data.data() - contents.data();
|
||||
i64 end_offset = begin_offset + size + 4;
|
||||
i64 id = *(U32<E> *)(data.data() + 4);
|
||||
data = data.substr(size + 4);
|
||||
i64 rel_begin = rel_idx;
|
||||
while (rel_idx < rels.size() && rels[rel_idx].r_offset < end_offset)
|
||||
rel_idx++;
|
||||
assert(rel_idx == rels.size() || begin_offset <= rels[rel_begin].r_offset);
|
||||
|
||||
i64 rel_begin = rel_idx;
|
||||
while (rel_idx < rels.size() && rels[rel_idx].r_offset < end_offset)
|
||||
rel_idx++;
|
||||
assert(rel_idx == rels.size() || begin_offset <= rels[rel_begin].r_offset);
|
||||
if (id == 0) {
|
||||
// This is CIE.
|
||||
cies.emplace_back(ctx, *this, *isec, begin_offset, rels, rel_begin);
|
||||
} else {
|
||||
// This is FDE.
|
||||
if (rel_begin == rel_idx || rels[rel_begin].r_sym == 0) {
|
||||
// FDE has no valid relocation, which means FDE is dead from
|
||||
// the beginning. Compilers usually don't create such FDE, but
|
||||
// `ld -r` tend to generate such dead FDEs.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
// This is CIE.
|
||||
cies.emplace_back(ctx, *this, isec, begin_offset, rels, rel_begin);
|
||||
} else {
|
||||
// This is FDE.
|
||||
if (rel_begin == rel_idx || rels[rel_begin].r_sym == 0) {
|
||||
// FDE has no valid relocation, which means FDE is dead from
|
||||
// the beginning. Compilers usually don't create such FDE, but
|
||||
// `ld -r` tend to generate such dead FDEs.
|
||||
continue;
|
||||
if (rels[rel_begin].r_offset - begin_offset != 8)
|
||||
Fatal(ctx) << *isec << ": FDE's first relocation should have offset 8";
|
||||
|
||||
fdes.emplace_back(begin_offset, rel_begin);
|
||||
}
|
||||
}
|
||||
|
||||
if (rels[rel_begin].r_offset - begin_offset != 8)
|
||||
Fatal(ctx) << isec << ": FDE's first relocation should have offset 8";
|
||||
// Associate CIEs to FDEs.
|
||||
auto find_cie = [&](i64 offset) {
|
||||
for (i64 i = cies_begin; i < cies.size(); i++)
|
||||
if (cies[i].input_offset == offset)
|
||||
return i;
|
||||
Fatal(ctx) << *isec << ": bad FDE pointer";
|
||||
};
|
||||
|
||||
fdes.emplace_back(begin_offset, rel_begin);
|
||||
for (i64 i = fdes_begin; i < fdes.size(); i++) {
|
||||
i64 cie_offset = *(I32<E> *)(contents.data() + fdes[i].input_offset + 4);
|
||||
fdes[i].cie_idx = find_cie(fdes[i].input_offset + 4 - cie_offset);
|
||||
}
|
||||
}
|
||||
|
||||
// Associate CIEs to FDEs.
|
||||
auto find_cie = [&](i64 offset) {
|
||||
for (i64 i = cies_begin; i < cies.size(); i++)
|
||||
if (cies[i].input_offset == offset)
|
||||
return i;
|
||||
Fatal(ctx) << isec << ": bad FDE pointer";
|
||||
};
|
||||
|
||||
for (i64 i = fdes_begin; i < fdes.size(); i++) {
|
||||
i64 cie_offset = *(I32<E> *)(contents.data() + fdes[i].input_offset + 4);
|
||||
fdes[i].cie_idx = find_cie(fdes[i].input_offset + 4 - cie_offset);
|
||||
}
|
||||
|
||||
auto get_isec = [&](const FdeRecord<E> &fde) -> InputSection<E> * {
|
||||
return get_section(this->elf_syms[rels[fde.rel_idx].r_sym]);
|
||||
auto get_isec = [&](const FdeRecord<E> &fde) {
|
||||
return get_section(this->elf_syms[fde.get_rels(*this)[0].r_sym]);
|
||||
};
|
||||
|
||||
// We assume that FDEs for the same input sections are contiguous
|
||||
// in `fdes` vector.
|
||||
std::stable_sort(fdes.begin() + fdes_begin, fdes.end(),
|
||||
[&](const FdeRecord<E> &a, const FdeRecord<E> &b) {
|
||||
sort(fdes, [&](const FdeRecord<E> &a, const FdeRecord<E> &b) {
|
||||
return get_isec(a)->get_priority() < get_isec(b)->get_priority();
|
||||
});
|
||||
|
||||
// Associate FDEs to input sections.
|
||||
for (i64 i = fdes_begin; i < fdes.size();) {
|
||||
for (i64 i = 0; i < fdes.size();) {
|
||||
InputSection<E> *isec = get_isec(fdes[i]);
|
||||
assert(isec->fde_begin == -1);
|
||||
isec->fde_begin = i++;
|
||||
@@ -489,29 +572,9 @@ void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a symbol object for a given key. This function handles
|
||||
// the -wrap option.
|
||||
template <typename E>
|
||||
static Symbol<E> *insert_symbol(Context<E> &ctx, const ElfSym<E> &esym,
|
||||
std::string_view key, std::string_view name) {
|
||||
if (esym.is_undef() && name.starts_with("__real_") &&
|
||||
ctx.arg.wrap.contains(name.substr(7))) {
|
||||
return get_symbol(ctx, key.substr(7), name.substr(7));
|
||||
}
|
||||
|
||||
Symbol<E> *sym = get_symbol(ctx, key, name);
|
||||
|
||||
if (esym.is_undef() && sym->is_wrapped) {
|
||||
key = save_string(ctx, "__wrap_" + std::string(key));
|
||||
name = save_string(ctx, "__wrap_" + std::string(name));
|
||||
return get_symbol(ctx, key, name);
|
||||
}
|
||||
return sym;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
if (!symtab_sec)
|
||||
if (this->elf_syms.empty())
|
||||
return;
|
||||
|
||||
static Counter counter("all_syms");
|
||||
@@ -555,6 +618,9 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
|
||||
if (esym.is_common())
|
||||
has_common_symbol = true;
|
||||
|
||||
// Get a symbol name
|
||||
std::string_view key = this->symbol_strtab.data() + esym.st_name;
|
||||
std::string_view name = key;
|
||||
@@ -571,9 +637,21 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
this->symbols[i] = insert_symbol(ctx, esym, key, name);
|
||||
if (esym.is_common())
|
||||
has_common_symbol = true;
|
||||
// Handle --wrap option
|
||||
Symbol<E> *sym;
|
||||
if (esym.is_undef() && name.starts_with("__real_") &&
|
||||
ctx.arg.wrap.contains(name.substr(7))) {
|
||||
sym = get_symbol(ctx, key.substr(7), name.substr(7));
|
||||
} else {
|
||||
sym = get_symbol(ctx, key, name);
|
||||
if (esym.is_undef() && sym->is_wrapped) {
|
||||
key = save_string(ctx, "__wrap_" + std::string(key));
|
||||
name = save_string(ctx, "__wrap_" + std::string(name));
|
||||
sym = get_symbol(ctx, key, name);
|
||||
}
|
||||
}
|
||||
|
||||
this->symbols[i] = sym;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,13 +677,13 @@ void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
static size_t find_null(std::string_view data, u64 entsize) {
|
||||
static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
|
||||
if (entsize == 1)
|
||||
return data.find('\0');
|
||||
return data.find('\0', pos);
|
||||
|
||||
for (i64 i = 0; i <= data.size() - entsize; i += entsize)
|
||||
if (data.substr(i, entsize).find_first_not_of('\0') == data.npos)
|
||||
return i;
|
||||
for (; pos <= data.size() - entsize; pos += entsize)
|
||||
if (data.substr(pos, entsize).find_first_not_of('\0') == data.npos)
|
||||
return pos;
|
||||
|
||||
return data.npos;
|
||||
}
|
||||
@@ -630,7 +708,7 @@ static size_t find_null(std::string_view data, u64 entsize) {
|
||||
template <typename E>
|
||||
static std::unique_ptr<MergeableSection<E>>
|
||||
split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
if (!sec.is_alive || sec.relsec_idx != -1)
|
||||
if (!sec.is_alive || sec.relsec_idx != -1 || sec.sh_size == 0)
|
||||
return nullptr;
|
||||
|
||||
const ElfShdr<E> &shdr = sec.shdr();
|
||||
@@ -644,60 +722,57 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
if (entsize == 0)
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<MergeableSection<E>> rec(new MergeableSection<E>);
|
||||
rec->parent = MergedSection<E>::get_instance(ctx, sec.name(), shdr.sh_type,
|
||||
shdr.sh_flags, entsize);
|
||||
rec->p2align = sec.p2align;
|
||||
i64 addralign = shdr.sh_addralign;
|
||||
if (addralign == 0)
|
||||
addralign = 1;
|
||||
|
||||
if (sec.sh_size == 0)
|
||||
return rec;
|
||||
std::unique_ptr<MergeableSection<E>> m(new MergeableSection<E>);
|
||||
m->parent = MergedSection<E>::get_instance(ctx, sec.name(), shdr.sh_type,
|
||||
shdr.sh_flags, entsize, addralign);
|
||||
m->p2align = sec.p2align;
|
||||
|
||||
// If thes section contents are compressed, uncompress them.
|
||||
sec.uncompress(ctx);
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
const char *begin = data.data();
|
||||
HyperLogLog estimator;
|
||||
m->contents = sec.contents;
|
||||
|
||||
if (data.size() > UINT32_MAX)
|
||||
Fatal(ctx) << sec << ": mergeable section too large";
|
||||
|
||||
// Split sections
|
||||
if (shdr.sh_flags & SHF_STRINGS) {
|
||||
while (!data.empty()) {
|
||||
size_t end = find_null(data, entsize);
|
||||
for (i64 pos = 0; pos < data.size();) {
|
||||
m->frag_offsets.push_back(pos);
|
||||
size_t end = find_null(data, pos, entsize);
|
||||
if (end == data.npos)
|
||||
Fatal(ctx) << sec << ": string is not null terminated";
|
||||
|
||||
std::string_view substr = data.substr(0, end + entsize);
|
||||
data = data.substr(end + entsize);
|
||||
|
||||
rec->strings.push_back(substr);
|
||||
rec->frag_offsets.push_back(substr.data() - begin);
|
||||
|
||||
u64 hash = hash_string(substr);
|
||||
rec->hashes.push_back(hash);
|
||||
estimator.insert(hash);
|
||||
pos = end + entsize;
|
||||
}
|
||||
} else {
|
||||
if (data.size() % entsize)
|
||||
Fatal(ctx) << sec << ": section size is not multiple of sh_entsize";
|
||||
m->frag_offsets.reserve(data.size() / entsize);
|
||||
|
||||
while (!data.empty()) {
|
||||
std::string_view substr = data.substr(0, entsize);
|
||||
data = data.substr(entsize);
|
||||
|
||||
rec->strings.push_back(substr);
|
||||
rec->frag_offsets.push_back(substr.data() - begin);
|
||||
|
||||
u64 hash = hash_string(substr);
|
||||
rec->hashes.push_back(hash);
|
||||
estimator.insert(hash);
|
||||
}
|
||||
for (i64 pos = 0; pos < data.size(); pos += entsize)
|
||||
m->frag_offsets.push_back(pos);
|
||||
}
|
||||
|
||||
rec->parent->estimator.merge(estimator);
|
||||
// Compute hashes for section pieces
|
||||
HyperLogLog estimator;
|
||||
m->hashes.reserve(m->frag_offsets.size());
|
||||
|
||||
for (i64 i = 0; i < m->frag_offsets.size(); i++) {
|
||||
u64 hash = hash_string(m->get_contents(i));
|
||||
m->hashes.push_back(hash);
|
||||
estimator.insert(hash);
|
||||
}
|
||||
|
||||
m->parent->estimator.merge(estimator);
|
||||
|
||||
static Counter counter("string_fragments");
|
||||
counter += rec->fragments.size();
|
||||
return rec;
|
||||
counter += m->frag_offsets.size();
|
||||
return m;
|
||||
}
|
||||
|
||||
// Usually a section is an atomic unit of inclusion or exclusion.
|
||||
@@ -759,14 +834,17 @@ template <typename E>
|
||||
void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
for (std::unique_ptr<MergeableSection<E>> &m : mergeable_sections) {
|
||||
if (m) {
|
||||
m->fragments.reserve(m->strings.size());
|
||||
for (i64 i = 0; i < m->strings.size(); i++)
|
||||
m->fragments.push_back(m->parent->insert(ctx, m->strings[i], m->hashes[i],
|
||||
m->p2align));
|
||||
m->fragments.reserve(m->frag_offsets.size());
|
||||
|
||||
// Shrink vectors that we will never use again to reclaim memory.
|
||||
m->strings.clear();
|
||||
for (i64 i = 0; i < m->frag_offsets.size(); i++) {
|
||||
SectionFragment<E> *frag =
|
||||
m->parent->insert(ctx, m->get_contents(i), m->hashes[i], m->p2align);
|
||||
m->fragments.push_back(frag);
|
||||
}
|
||||
|
||||
// Reclaim memory as we'll never use this vector again
|
||||
m->hashes.clear();
|
||||
m->hashes.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -941,11 +1019,11 @@ template <typename E>
|
||||
static void print_trace_symbol(Context<E> &ctx, InputFile<E> &file,
|
||||
const ElfSym<E> &esym, Symbol<E> &sym) {
|
||||
if (!esym.is_undef())
|
||||
SyncOut(ctx) << "trace-symbol: " << file << ": definition of " << sym;
|
||||
Out(ctx) << "trace-symbol: " << file << ": definition of " << sym;
|
||||
else if (esym.is_weak())
|
||||
SyncOut(ctx) << "trace-symbol: " << file << ": weak reference to " << sym;
|
||||
Out(ctx) << "trace-symbol: " << file << ": weak reference to " << sym;
|
||||
else
|
||||
SyncOut(ctx) << "trace-symbol: " << file << ": reference to " << sym;
|
||||
Out(ctx) << "trace-symbol: " << file << ": reference to " << sym;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -994,19 +1072,14 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
if (sym.is_traced)
|
||||
print_trace_symbol(ctx, *this, esym, sym);
|
||||
|
||||
if (esym.is_weak())
|
||||
continue;
|
||||
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
bool keep = esym.is_undef() || (esym.is_common() && !sym.esym().is_common());
|
||||
if (keep && !sym.file->is_alive.test_and_set()) {
|
||||
if (sym.file && !esym.is_weak() &&
|
||||
(esym.is_undef() || (esym.is_common() && !sym.esym().is_common())) &&
|
||||
!sym.file->is_alive.test_and_set()) {
|
||||
feeder(sym.file);
|
||||
|
||||
if (sym.is_traced)
|
||||
SyncOut(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
|
||||
<< " for " << sym;
|
||||
Out(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
|
||||
<< " for " << sym;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1075,23 +1148,19 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
elf_sections2.push_back({});
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
|
||||
if (sym.get_type() == STT_TLS) {
|
||||
ElfShdr<E> shdr = {};
|
||||
if (sym.get_type() == STT_TLS)
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_TLS;
|
||||
} else {
|
||||
else
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
}
|
||||
|
||||
shdr.sh_type = SHT_NOBITS;
|
||||
shdr.sh_size = this->elf_syms[i].st_size;
|
||||
shdr.sh_addralign = this->elf_syms[i].st_value;
|
||||
elf_sections2.push_back(shdr);
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, idx);
|
||||
auto isec = std::make_unique<InputSection<E>>(ctx, *this, idx);
|
||||
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec.get());
|
||||
@@ -1099,7 +1168,6 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = false;
|
||||
|
||||
sections.push_back(std::move(isec));
|
||||
}
|
||||
}
|
||||
@@ -1223,15 +1291,14 @@ std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
SharedFile<E> *
|
||||
SharedFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
SharedFile<E> *SharedFile<E>::create(Context<E> &ctx, MappedFile *mf) {
|
||||
SharedFile<E> *obj = new SharedFile(ctx, mf);
|
||||
ctx.dso_pool.emplace_back(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile *mf)
|
||||
: InputFile<E>(ctx, mf) {
|
||||
this->is_alive = !ctx.as_needed;
|
||||
}
|
||||
@@ -1277,17 +1344,17 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_hidden = (!vers.empty() && (vers[i] & VERSYM_HIDDEN));
|
||||
bool is_default = vers.empty() || !(vers[i] & VERSYM_HIDDEN);
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
this->versyms.push_back(ver);
|
||||
|
||||
if (is_hidden) {
|
||||
if (is_default) {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
std::string_view mangled_name = save_string(
|
||||
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
|
||||
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
|
||||
} else {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1330,29 +1397,31 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
// default version of the library) at load-time.
|
||||
template <typename E>
|
||||
std::vector<std::string_view> SharedFile<E>::read_verdef(Context<E> &ctx) {
|
||||
std::vector<std::string_view> ret(VER_NDX_LAST_RESERVED + 1);
|
||||
|
||||
ElfShdr<E> *verdef_sec = this->find_section(SHT_GNU_VERDEF);
|
||||
if (!verdef_sec)
|
||||
return ret;
|
||||
return {};
|
||||
|
||||
std::string_view verdef = this->get_string(ctx, *verdef_sec);
|
||||
std::string_view strtab = this->get_string(ctx, verdef_sec->sh_link);
|
||||
|
||||
ElfVerdef<E> *ver = (ElfVerdef<E> *)verdef.data();
|
||||
std::vector<std::string_view> vec;
|
||||
u8 *ptr = (u8 *)verdef.data();
|
||||
|
||||
for (;;) {
|
||||
if (ret.size() <= ver->vd_ndx)
|
||||
ret.resize(ver->vd_ndx + 1);
|
||||
ElfVerdef<E> *ver = (ElfVerdef<E> *)ptr;
|
||||
if (ver->vd_ndx == VER_NDX_UNSPECIFIED)
|
||||
Fatal(ctx) << *this << ": symbol version too large";
|
||||
|
||||
ElfVerdaux<E> *aux = (ElfVerdaux<E> *)((u8 *)ver + ver->vd_aux);
|
||||
ret[ver->vd_ndx] = strtab.data() + aux->vda_name;
|
||||
if (vec.size() <= ver->vd_ndx)
|
||||
vec.resize(ver->vd_ndx + 1);
|
||||
|
||||
ElfVerdaux<E> *aux = (ElfVerdaux<E> *)(ptr + ver->vd_aux);
|
||||
vec[ver->vd_ndx] = strtab.data() + aux->vda_name;
|
||||
if (!ver->vd_next)
|
||||
break;
|
||||
|
||||
ver = (ElfVerdef<E> *)((u8 *)ver + ver->vd_next);
|
||||
ptr += ver->vd_next;
|
||||
}
|
||||
return ret;
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1371,7 +1440,7 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.value = esym.st_value;
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = versyms[i];
|
||||
sym.is_weak = false;
|
||||
sym.is_weak = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1392,22 +1461,22 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
feeder(sym.file);
|
||||
|
||||
if (sym.is_traced)
|
||||
SyncOut(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
|
||||
<< " for " << sym;
|
||||
Out(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
|
||||
<< " for " << sym;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
std::span<Symbol<E> *> SharedFile<E>::get_symbols_at(Symbol<E> *sym) {
|
||||
assert(sym->file == this);
|
||||
|
||||
std::call_once(init_aliases, [&] {
|
||||
std::call_once(init_sorted_syms, [&] {
|
||||
for (Symbol<E> *sym : this->symbols)
|
||||
if (sym->file == this)
|
||||
aliases.push_back(sym);
|
||||
sorted_syms.push_back(sym);
|
||||
|
||||
tbb::parallel_sort(aliases.begin(), aliases.end(),
|
||||
tbb::parallel_sort(sorted_syms.begin(), sorted_syms.end(),
|
||||
[](Symbol<E> *a, Symbol<E> *b) {
|
||||
const ElfSym<E> &x = a->esym();
|
||||
const ElfSym<E> &y = b->esym();
|
||||
@@ -1415,12 +1484,12 @@ std::span<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
});
|
||||
});
|
||||
|
||||
auto [begin, end] = std::equal_range(aliases.begin(), aliases.end(), sym,
|
||||
[&](Symbol<E> *x, Symbol<E> *y) {
|
||||
return x->esym().st_value < y->esym().st_value;
|
||||
auto [begin, end] = std::equal_range(sorted_syms.begin(), sorted_syms.end(),
|
||||
sym, [&](Symbol<E> *a, Symbol<E> *b) {
|
||||
return a->esym().st_value < b->esym().st_value;
|
||||
});
|
||||
|
||||
return {&*begin, &*end};
|
||||
return {&*begin, (size_t)(end - begin)};
|
||||
}
|
||||
|
||||
// Infer an alignment of a DSO symbol. An alignment of a symbol in other
|
||||
@@ -1479,9 +1548,8 @@ void SharedFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
i64 strtab_off = this->strtab_offset;
|
||||
|
||||
for (i64 i = 0, j = this->first_global; j < this->elf_syms.size(); i++, j++) {
|
||||
Symbol<E> &sym = *this->symbols[j];
|
||||
if (sym.file != this || !sym.write_to_symtab)
|
||||
for (i64 i = 0; Symbol<E> *sym : this->get_global_syms()) {
|
||||
if (sym->file != this || !sym->write_to_symtab)
|
||||
continue;
|
||||
|
||||
U32<E> *xindex = nullptr;
|
||||
@@ -1489,8 +1557,9 @@ 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);
|
||||
strtab_off += write_string(strtab + strtab_off, sym.name());
|
||||
*symtab++ = to_output_esym(ctx, *sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab + strtab_off, sym->name());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1499,6 +1568,10 @@ using E = MOLD_TARGET;
|
||||
template class InputFile<E>;
|
||||
template class ObjectFile<E>;
|
||||
template class SharedFile<E>;
|
||||
template Symbol<E> *get_symbol(Context<E> &, std::string_view, std::string_view);
|
||||
template Symbol<E> *get_symbol(Context<E> &, std::string_view);
|
||||
template std::string_view demangle(const Symbol<E> &);
|
||||
template std::ostream &operator<<(std::ostream &, const Symbol<E> &);
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+56
-51
@@ -11,32 +11,31 @@ typedef enum {
|
||||
BASEREL, IFUNC_DYNREL,
|
||||
} Action;
|
||||
|
||||
template <typename E>
|
||||
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<const ElfRel<E>> x = get_rels();
|
||||
std::span<const ElfRel<E>> y = other.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++) {
|
||||
if (x[i].r_offset - input_offset != y[i].r_offset - other.input_offset ||
|
||||
x[i].r_type != y[i].r_type ||
|
||||
file.symbols[x[i].r_sym] != other.file.symbols[y[i].r_sym] ||
|
||||
get_addend(input_section, x[i]) != get_addend(other.input_section, y[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool cie_equals(const CieRecord<E> &a, const CieRecord<E> &b) {
|
||||
if (a.get_contents() != b.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<const ElfRel<E>> x = a.get_rels();
|
||||
std::span<const ElfRel<E>> y = b.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++)
|
||||
if (x[i].r_offset - a.input_offset != y[i].r_offset - b.input_offset ||
|
||||
x[i].r_type != y[i].r_type ||
|
||||
a.file.symbols[x[i].r_sym] != b.file.symbols[y[i].r_sym] ||
|
||||
get_addend(a.input_section, x[i]) != get_addend(b.input_section, y[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
@@ -70,14 +69,14 @@ void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress_to(ctx, buf);
|
||||
copy_contents(ctx, buf);
|
||||
contents = std::string_view((char *)buf, sh_size);
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
@@ -107,6 +106,16 @@ void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool
|
||||
is_relr_reloc(Context<E> &ctx, InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
ElfShdr<E> shdr = isec.shdr();
|
||||
return ctx.arg.pack_dyn_relocs_relr &&
|
||||
!(shdr.sh_flags & SHF_EXECINSTR) &&
|
||||
shdr.sh_addralign % sizeof(Word<E>) == 0 &&
|
||||
rel.r_offset % sizeof(Word<E>) == 0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, Action action) {
|
||||
@@ -201,7 +210,7 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
case BASEREL:
|
||||
// Create a base relocation.
|
||||
check_textrel();
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
if (!is_relr_reloc(ctx, isec, rel))
|
||||
isec.file.num_dynrel++;
|
||||
break;
|
||||
case IFUNC_DYNREL:
|
||||
@@ -274,7 +283,7 @@ static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
template <typename E>
|
||||
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return ctx.arg.pic ? IFUNC_DYNREL : NONE;
|
||||
return sym.is_pde_ifunc(ctx) ? NONE : IFUNC_DYNREL;
|
||||
|
||||
// This is a decision table for absolute relocations for the pointer
|
||||
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
|
||||
@@ -384,7 +393,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case BASEREL:
|
||||
if (isec.is_relr_reloc(ctx, rel)) {
|
||||
if (is_relr_reloc(ctx, isec, rel)) {
|
||||
*(Word<E> *)loc = S + A;
|
||||
} else {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
|
||||
@@ -449,7 +458,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if constexpr (is_riscv<E>)
|
||||
copy_contents_riscv(ctx, buf);
|
||||
else
|
||||
uncompress_to(ctx, buf);
|
||||
copy_contents(ctx, buf);
|
||||
|
||||
// Apply relocations
|
||||
if (!ctx.arg.relocatable) {
|
||||
@@ -457,31 +466,20 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
|
||||
if constexpr (is_x86_64<E>) {
|
||||
u8 endbr[] = {0xf3, 0x0f, 0x1e, 0xfa};
|
||||
u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
|
||||
|
||||
// Rewrite the leading endbr instruction with a nop if the section
|
||||
// is not address-taken.
|
||||
if (ctx.arg.z_rewrite_endbr && (shdr().sh_flags & SHF_EXECINSTR) &&
|
||||
!address_taken && sh_size >= 4 && memcmp(buf, endbr, 4) == 0) {
|
||||
memcpy(buf, nop, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
|
||||
for (const ElfSym<E> &esym : file.elf_syms) {
|
||||
std::string_view
|
||||
InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
|
||||
for (Symbol<E> *sym : file.symbols) {
|
||||
const ElfSym<E> &esym = sym->esym();
|
||||
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
|
||||
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
|
||||
std::string_view name = file.symbol_strtab.data() + esym.st_name;
|
||||
if (ctx.arg.demangle)
|
||||
return demangle(name);
|
||||
return name;
|
||||
return demangle(*sym);
|
||||
return sym->name();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
@@ -518,22 +516,29 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
|
||||
ss << ">>> " << file;
|
||||
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
|
||||
ss << ":(" << func << ")";
|
||||
ss << '\n';
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
ctx.undef_errors.insert(acc, {&sym, {}});
|
||||
acc->second.push_back(ss.str());
|
||||
};
|
||||
|
||||
// A non-weak undefined symbol must be promoted to an imported
|
||||
// symbol or resolved to an defined symbol. Otherwise, it's an
|
||||
// undefined symbol error.
|
||||
// A non-weak undefined symbol must be promoted to an imported symbol
|
||||
// or resolved to an defined symbol. Otherwise, we need to report an
|
||||
// error or warn on it.
|
||||
//
|
||||
// Every ELF file has an absolute local symbol as its first symbol.
|
||||
// Referring to that symbol is always valid.
|
||||
bool is_undef = esym.is_undef() && !esym.is_weak() && sym.sym_idx;
|
||||
if (!sym.is_imported && is_undef && sym.esym().is_undef()) {
|
||||
record();
|
||||
return true;
|
||||
if (is_undef && sym.esym().is_undef()) {
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR && !sym.is_imported) {
|
||||
record();
|
||||
return true;
|
||||
}
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN) {
|
||||
record();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If a protected/hidden undefined symbol is resolved to other .so,
|
||||
@@ -549,7 +554,7 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template struct CieRecord<E>;
|
||||
template bool cie_equals(const CieRecord<E> &, const CieRecord<E> &);
|
||||
template class InputSection<E>;
|
||||
|
||||
} // namespace mold::elf
|
||||
+72
-51
@@ -10,9 +10,6 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static thread_local MappedFile<Context<E>> *current_file;
|
||||
|
||||
template <typename E>
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
|
||||
|
||||
@@ -37,7 +34,7 @@ template <typename E>
|
||||
class SyntaxError {
|
||||
public:
|
||||
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
|
||||
std::string_view contents = current_file<E>->get_contents();
|
||||
std::string_view contents = ctx.script_file->get_contents();
|
||||
std::string_view line = get_line(contents, errpos.data());
|
||||
|
||||
i64 lineno = 1;
|
||||
@@ -45,16 +42,16 @@ public:
|
||||
if (contents[i] == '\n')
|
||||
lineno++;
|
||||
|
||||
std::string label = ctx.script_file->name + ":" +
|
||||
std::to_string(lineno) + ": ";
|
||||
i64 indent = strlen("mold: fatal: ") + label.size();
|
||||
i64 column = errpos.data() - line.data();
|
||||
|
||||
std::stringstream ss;
|
||||
ss << current_file<E>->name << ":" << lineno << ": ";
|
||||
i64 indent = (i64)ss.tellp() + strlen("mold: ");
|
||||
ss << line << "\n" << std::setw(indent + column) << " " << "^ ";
|
||||
out << ss.str();
|
||||
out << label << line << "\n"
|
||||
<< std::string(indent + column, ' ') << "^ ";
|
||||
}
|
||||
|
||||
template <class T> SyntaxError &operator<<(T &&val) {
|
||||
template <typename T> SyntaxError &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
@@ -118,7 +115,7 @@ template <typename E>
|
||||
static std::span<std::string_view>
|
||||
skip(Context<E> &ctx, std::span<std::string_view> tok, std::string_view str) {
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected '" << str
|
||||
Fatal(ctx) << ctx.script_file->name << ": expected '" << str
|
||||
<< "', but got EOF";
|
||||
if (tok[0] != str)
|
||||
SyntaxError(ctx, tok[0]) << "expected '" << str << "'";
|
||||
@@ -140,26 +137,42 @@ read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
while (!tok.empty() && tok[0] != ")")
|
||||
tok = tok.subspan(1);
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
|
||||
Fatal(ctx) << ctx.script_file->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
|
||||
std::string rel = to_abs_path(path)
|
||||
.lexically_relative(to_abs_path(ctx.arg.sysroot))
|
||||
.string();
|
||||
std::filesystem::path sysroot = to_abs_path(ctx.arg.sysroot);
|
||||
std::string rel = to_abs_path(path).lexically_relative(sysroot).string();
|
||||
return rel != "." && !rel.starts_with("../");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view tok) {
|
||||
static MappedFile *
|
||||
resolve_path(Context<E> &ctx, std::string_view tok, bool check_target) {
|
||||
std::string str(unquote(tok));
|
||||
|
||||
auto open = [&](const std::string &path) -> MappedFile * {
|
||||
MappedFile *mf = open_file(ctx, path);
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
if (check_target) {
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
if (!target.empty() && target != E::target_name) {
|
||||
Warn(ctx) << path << ": skipping incompatible file: " << target
|
||||
<< " (e_machine " << (int)E::e_machine << ")";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return mf;
|
||||
};
|
||||
|
||||
// GNU ld prepends the sysroot if a pathname starts with '/' and the
|
||||
// script being processed is in the sysroot. We do the same.
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, current_file<E>->name))
|
||||
return MappedFile<Context<E>>::must_open(ctx, ctx.arg.sysroot + str);
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, ctx.script_file->name))
|
||||
return must_open_file(ctx, ctx.arg.sysroot + str);
|
||||
|
||||
if (str.starts_with('=')) {
|
||||
std::string path;
|
||||
@@ -167,18 +180,22 @@ static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view to
|
||||
path = str.substr(1);
|
||||
else
|
||||
path = ctx.arg.sysroot + str.substr(1);
|
||||
return MappedFile<Context<E>>::must_open(ctx, path);
|
||||
return must_open_file(ctx, path);
|
||||
}
|
||||
|
||||
if (str.starts_with("-l"))
|
||||
return find_library(ctx, str.substr(2));
|
||||
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, str))
|
||||
if (!str.starts_with('/'))
|
||||
if (MappedFile *mf = open(path_clean(ctx.script_file->name + "/../" + str)))
|
||||
return mf;
|
||||
|
||||
if (MappedFile *mf = open(str))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string path = std::string(dir) + "/" + str;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
if (MappedFile *mf = open(path))
|
||||
return mf;
|
||||
}
|
||||
|
||||
@@ -199,19 +216,19 @@ read_group(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MappedFile<Context<E>> *mf = resolve_path(ctx, tok[0]);
|
||||
MappedFile *mf = resolve_path(ctx, tok[0], true);
|
||||
read_file(ctx, mf);
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
|
||||
Fatal(ctx) << ctx.script_file->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile *mf) {
|
||||
ctx.script_file = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
@@ -240,8 +257,8 @@ void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
template <typename E>
|
||||
std::string_view
|
||||
get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
get_script_output_type(Context<E> &ctx, MappedFile *mf) {
|
||||
ctx.script_file = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
@@ -255,8 +272,7 @@ get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
|
||||
tok[1] == "(")
|
||||
if (MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::open(ctx, std::string(unquote(tok[2]))))
|
||||
if (MappedFile *mf = resolve_path(ctx, tok[2], false))
|
||||
return get_machine_type(ctx, mf);
|
||||
|
||||
return "";
|
||||
@@ -312,12 +328,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
|
||||
ctx.default_version_from_version_script = true;
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), ctx.script_file->name,
|
||||
ver_str, ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), ctx.script_file->name,
|
||||
ver_str, VER_NDX_LOCAL, is_cpp});
|
||||
}
|
||||
|
||||
@@ -357,8 +372,8 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
void parse_version_script(Context<E> &ctx, MappedFile *mf) {
|
||||
ctx.script_file = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
read_version_script(ctx, tok);
|
||||
@@ -367,7 +382,9 @@ void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
void read_dynamic_list_commands(Context<E> &ctx,
|
||||
std::vector<DynamicPattern> &result,
|
||||
std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (tok[0] == "extern") {
|
||||
@@ -376,11 +393,11 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
read_dynamic_list_commands(ctx, result, tok, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, true);
|
||||
read_dynamic_list_commands(ctx, result, tok, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
@@ -388,36 +405,40 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
"global", VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
result.push_back({unquote(tok[0]), "", is_cpp});
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path) {
|
||||
std::string_view contents =
|
||||
must_open_file(ctx, std::string(path))->get_contents();
|
||||
std::vector<std::string_view> vec = tokenize(ctx, contents);
|
||||
std::span<std::string_view> tok = vec;
|
||||
std::vector<DynamicPattern> result;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
read_dynamic_list_commands(ctx, result, tok, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
|
||||
if (!tok.empty())
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
|
||||
for (DynamicPattern &p : result)
|
||||
p.source = path;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_linker_script(Context<E> &, MappedFile *);
|
||||
template std::string_view get_script_output_type(Context<E> &, MappedFile *);
|
||||
template void parse_version_script(Context<E> &, MappedFile *);
|
||||
template std::vector<DynamicPattern> parse_dynamic_list(Context<E> &, std::string_view);
|
||||
|
||||
|
||||
} // namespace mold::elf
|
||||
+124
-108
@@ -126,7 +126,7 @@ static PluginStatus message(PluginLevel level, const char *fmt, ...) {
|
||||
|
||||
switch (level) {
|
||||
case LDPL_INFO:
|
||||
SyncOut(ctx) << buf;
|
||||
Out(ctx) << buf;
|
||||
break;
|
||||
case LDPL_WARNING:
|
||||
Warn(ctx) << buf;
|
||||
@@ -176,7 +176,7 @@ static PluginStatus add_input_file(const char *path) {
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
static i64 file_priority = 100;
|
||||
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
MappedFile *mf = must_open_file(ctx, path);
|
||||
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, "", false);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
@@ -427,7 +427,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 = mold_version + "\0"s;
|
||||
std::string version = get_mold_version() + "\0"s;
|
||||
|
||||
*linker_identifier = "mold";
|
||||
*linker_version = version.data();
|
||||
@@ -439,72 +439,77 @@ get_api_version(const char *plugin_identifier,
|
||||
return LAPI_V0;
|
||||
}
|
||||
|
||||
// dlopen the linker plugin file
|
||||
template <typename E>
|
||||
static void load_plugin(Context<E> &ctx) {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
static void load_lto_plugin(Context<E> &ctx) {
|
||||
static std::once_flag flag;
|
||||
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
std::call_once(flag, [&] {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -576,7 +581,28 @@ static bool supports_v3_api(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
static PluginInputFile
|
||||
create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
|
||||
PluginInputFile file;
|
||||
MappedFile *mf2 = mf->parent ? mf->parent : mf;
|
||||
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
|
||||
if (mf2->fd == -1)
|
||||
mf2->fd = open(file.name, O_RDONLY);
|
||||
file.fd = mf2->fd;
|
||||
|
||||
if (file.fd == -1)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
load_lto_plugin(ctx);
|
||||
|
||||
// V0 API's claim_file is not thread-safe.
|
||||
static std::mutex mu;
|
||||
std::unique_lock lock(mu, std::defer_lock);
|
||||
@@ -589,10 +615,6 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
<< "added -flto not only for creating object files but also for "
|
||||
<< "creating the final executable.";
|
||||
|
||||
// dlopen the linker plugin file
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&] { load_plugin(ctx); });
|
||||
|
||||
// Create mold's object instance
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
@@ -602,23 +624,10 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
obj->first_global = 1;
|
||||
obj->is_lto_obj = true;
|
||||
obj->mf = mf;
|
||||
obj->archive_name = mf->parent ? mf->parent->name : "";
|
||||
|
||||
// Create plugin's object instance
|
||||
PluginInputFile file = {};
|
||||
|
||||
MappedFile<Context<E>> *mf2 = mf->parent ? mf->parent : mf;
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
if (mf2->fd == -1)
|
||||
mf2->fd = open(file.name, O_RDONLY);
|
||||
file.fd = mf2->fd;
|
||||
if (file.fd == -1)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
|
||||
if (mf->parent)
|
||||
obj->archive_name = mf->parent->name;
|
||||
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
PluginInputFile file = create_plugin_input_file(ctx, mf);
|
||||
file.handle = (void *)obj;
|
||||
|
||||
LOG << "read_lto_symbols: "<< mf->name << "\n";
|
||||
@@ -635,40 +644,34 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
// LLVM needs it and takes the ownership of fd. To prevent "too many
|
||||
// open files" issue, we close fd only for GCC. This is ugly, though.
|
||||
if (!is_llvm(ctx)) {
|
||||
MappedFile *mf2 = mf->parent ? mf->parent : mf;
|
||||
close(mf2->fd);
|
||||
mf2->fd = -1;
|
||||
}
|
||||
|
||||
// Initialize object symbols
|
||||
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
|
||||
obj->has_symver.resize(plugin_symbols.size());
|
||||
obj->lto_symbol_versions.resize(plugin_symbols.size());
|
||||
// Create a symbol strtab
|
||||
i64 strtab_size = 1;
|
||||
for (PluginSymbol &psym : plugin_symbols)
|
||||
strtab_size += strlen(psym.name) + 1;
|
||||
std::string strtab(strtab_size, '\0');
|
||||
|
||||
// Initialize esyms
|
||||
obj->lto_elf_syms.resize(plugin_symbols.size() + 1);
|
||||
i64 strtab_offset = 1;
|
||||
|
||||
for (i64 i = 0; i < plugin_symbols.size(); i++) {
|
||||
PluginSymbol &psym = plugin_symbols[i];
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->lto_elf_syms[i + 1] = to_elf_sym<E>(psym);
|
||||
obj->lto_elf_syms[i + 1].st_name = strtab_offset;
|
||||
|
||||
std::string_view key = save_string(ctx, psym.name);
|
||||
std::string_view name = key;
|
||||
|
||||
// Parse symbol version after atsign
|
||||
if (i64 pos = name.find('@'); pos != name.npos) {
|
||||
std::string_view ver = name.substr(pos);
|
||||
name = name.substr(0, pos);
|
||||
|
||||
if (ver != "@" && ver != "@@") {
|
||||
if (ver.starts_with("@@"))
|
||||
key = name;
|
||||
obj->has_symver.set(i);
|
||||
obj->lto_symbol_versions[i] = ver.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
obj->symbols.push_back(get_symbol(ctx, key, name));
|
||||
i64 len = strlen(psym.name);
|
||||
memcpy(strtab.data() + strtab_offset, psym.name, len);
|
||||
strtab_offset += len + 1;
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->has_symver.resize(esyms->size());
|
||||
obj->symbol_strtab = save_string(ctx, strtab);
|
||||
obj->elf_syms = obj->lto_elf_syms;
|
||||
obj->initialize_symbols(ctx);
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
}
|
||||
@@ -677,6 +680,7 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_lto");
|
||||
load_lto_plugin(ctx);
|
||||
|
||||
if (!ctx.arg.lto_pass2 && !supports_v3_api(ctx))
|
||||
restart_process(ctx);
|
||||
@@ -709,8 +713,21 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
get_symbol(ctx, y)->referenced_by_regular_obj = true;
|
||||
}
|
||||
|
||||
// Handle --entry
|
||||
get_symbol(ctx, ctx.arg.entry)->referenced_by_regular_obj = true;
|
||||
// Keep some symbols
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
sym->referenced_by_regular_obj = true;
|
||||
|
||||
// Object files containing .gnu.offload_lto_.* sections need to be
|
||||
// given to the LTO backend. Such sections contains code and data for
|
||||
// peripherails (typically GPUs).
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_alive && !file->is_lto_obj && file->is_gcc_offload_obj) {
|
||||
PluginInputFile pfile = create_plugin_input_file(ctx, file->mf);
|
||||
int claimed = false;
|
||||
claim_file_hook(&pfile, &claimed);
|
||||
assert(!claimed);
|
||||
}
|
||||
}
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
@@ -723,14 +740,13 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {
|
||||
Timer t(ctx, "lto_cleanup");
|
||||
|
||||
if (cleanup_hook)
|
||||
cleanup_hook();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ void lto_cleanup(Context<E> &ctx) {}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
# include "lto-win32.cc"
|
||||
#else
|
||||
# include "lto-unix.cc"
|
||||
#endif
|
||||
+82
-69
@@ -1,6 +1,5 @@
|
||||
#include "mold.h"
|
||||
#include "../common/archive-file.h"
|
||||
#include "../common/cmdline.h"
|
||||
#include "../common/output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
@@ -17,17 +16,23 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <direct.h>
|
||||
# define _chdir chdir
|
||||
# define chdir _chdir
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
int main(int argc, char **argv) {
|
||||
return mold::elf::elf_main<mold::elf::X86_64>(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386).
|
||||
template <typename E>
|
||||
std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
std::string_view get_machine_type(Context<E> &ctx, MappedFile *mf) {
|
||||
auto get_elf_type = [&](u8 *buf) -> std::string_view {
|
||||
bool is_le = (((ElfEhdr<I386> *)buf)->e_ident[EI_DATA] == ELFDATA2LSB);
|
||||
bool is_64;
|
||||
@@ -83,13 +88,15 @@ std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
|
||||
for (MappedFile *child : read_fat_archive_members(ctx, mf))
|
||||
if (FileType ty = get_file_type(ctx, child);
|
||||
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
|
||||
for (MappedFile *child : read_thin_archive_members(ctx, mf))
|
||||
if (FileType ty = get_file_type(ctx, child);
|
||||
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
@@ -101,7 +108,7 @@ std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
check_file_compatibility(Context<E> &ctx, MappedFile *mf) {
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
if (target != ctx.arg.emulation)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: "
|
||||
@@ -109,7 +116,7 @@ check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
@@ -121,12 +128,12 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *m
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
Out(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_lto_objs");
|
||||
count++;
|
||||
@@ -139,27 +146,26 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
file->archive_name = archive_name;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
Out(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static SharedFile<E> *
|
||||
new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
new_shared_file(Context<E> &ctx, MappedFile *mf) {
|
||||
check_file_compatibility(ctx, mf);
|
||||
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
Out(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
void read_file(Context<E> &ctx, MappedFile *mf) {
|
||||
if (ctx.visited.contains(mf->name))
|
||||
return;
|
||||
|
||||
@@ -173,7 +179,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
for (MappedFile *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(ctx, child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
@@ -209,36 +215,47 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
template <typename E>
|
||||
static std::string_view
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
detect_machine_type(Context<E> &ctx, std::vector<std::string> paths) {
|
||||
std::erase(paths, "-");
|
||||
|
||||
for (const std::string &path : paths)
|
||||
if (auto *mf = open_file(ctx, path))
|
||||
if (get_file_type(ctx, mf) != FileType::TEXT)
|
||||
if (std::string_view target = get_machine_type(ctx, mf);
|
||||
!target.empty())
|
||||
return target;
|
||||
|
||||
for (const std::string &path : paths)
|
||||
if (auto *mf = open_file(ctx, path))
|
||||
if (get_file_type(ctx, mf) == FileType::TEXT)
|
||||
if (std::string_view target = get_script_output_type(ctx, mf);
|
||||
!target.empty())
|
||||
return target;
|
||||
|
||||
Fatal(ctx) << "-m option is missing";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
MappedFile *open_library(Context<E> &ctx, std::string path) {
|
||||
MappedFile *mf = open_file(ctx, path);
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
if (target.empty() || target == E::target_name)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << target
|
||||
<< " " << (int)E::e_machine;
|
||||
return nullptr;
|
||||
if (!target.empty() && target != E::target_name) {
|
||||
Warn(ctx) << path << ": skipping incompatible file: " << target
|
||||
<< " (e_machine " << (int)E::e_machine << ")";
|
||||
return nullptr;
|
||||
}
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
MappedFile *find_library(Context<E> &ctx, std::string name) {
|
||||
if (name.starts_with(':')) {
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string path = std::string(dir) + "/" + name.substr(1);
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
if (MappedFile *mf = open_library(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
@@ -247,22 +264,22 @@ MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string stem = std::string(dir) + "/lib" + name;
|
||||
if (!ctx.is_static)
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".so"))
|
||||
if (MappedFile *mf = open_library(ctx, stem + ".so"))
|
||||
return mf;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".a"))
|
||||
if (MappedFile *mf = open_library(ctx, stem + ".a"))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_from_search_paths(Context<E> &ctx, std::string name) {
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, name))
|
||||
MappedFile *find_from_search_paths(Context<E> &ctx, std::string name) {
|
||||
if (MappedFile *mf = open_file(ctx, name))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths)
|
||||
if (MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::open(ctx, std::string(dir) + "/" + name))
|
||||
if (MappedFile *mf =
|
||||
open_file(ctx, std::string(dir) + "/" + name))
|
||||
return mf;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -295,26 +312,10 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
} else if (arg == "--end-lib") {
|
||||
ctx.in_lib = false;
|
||||
} else if (remove_prefix(arg, "--version-script=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
MappedFile *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--version-script: file not found: " << arg;
|
||||
parse_version_script(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
|
||||
"global", VER_NDX_GLOBAL, false});
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
@@ -325,11 +326,11 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
state.back();
|
||||
state.pop_back();
|
||||
} else if (remove_prefix(arg, "-l")) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
|
||||
MappedFile *mf = find_library(ctx, std::string(arg));
|
||||
mf->given_fullpath = false;
|
||||
read_file(ctx, mf);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(arg)));
|
||||
read_file(ctx, must_open_file(ctx, std::string(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +358,7 @@ int elf_main(int argc, char **argv) {
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation.empty())
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
ctx.arg.emulation = detect_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if constexpr (is_x86_64<E>)
|
||||
@@ -381,7 +382,7 @@ int elf_main(int argc, char **argv) {
|
||||
on_complete = fork_child();
|
||||
#endif
|
||||
|
||||
acquire_global_lock(ctx);
|
||||
acquire_global_lock();
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
@@ -433,6 +434,9 @@ int elf_main(int argc, char **argv) {
|
||||
// "Kill" .eh_frame input sections after symbol resolution.
|
||||
kill_eh_frame_sections(ctx);
|
||||
|
||||
// Split mergeable section contents into section pieces.
|
||||
split_section_pieces(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
resolve_section_pieces(ctx);
|
||||
|
||||
@@ -457,7 +461,7 @@ int elf_main(int argc, char **argv) {
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Set "address-taken" bits for input sections.
|
||||
if (ctx.arg.icf || ctx.arg.z_rewrite_endbr)
|
||||
if (ctx.arg.icf)
|
||||
compute_address_significance(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
@@ -487,6 +491,10 @@ int elf_main(int argc, char **argv) {
|
||||
// Bin input sections into output sections.
|
||||
create_output_sections(ctx);
|
||||
|
||||
// Handle --section-align options.
|
||||
if (!ctx.arg.section_align.empty())
|
||||
apply_section_align(ctx);
|
||||
|
||||
// Add synthetic symbols such as __ehdr_start or __end.
|
||||
add_synthetic_symbols(ctx);
|
||||
|
||||
@@ -523,9 +531,9 @@ int elf_main(int argc, char **argv) {
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Handle --require-defined
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
if (!get_symbol(ctx, name)->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << name;
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
if (!sym->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << *sym;
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
@@ -535,6 +543,10 @@ int elf_main(int argc, char **argv) {
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// If .ctors/.dtors are to be placed to .init_array/.fini_array,
|
||||
// we need to reverse their contents.
|
||||
fixup_ctors_in_init_array(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
@@ -558,6 +570,9 @@ int elf_main(int argc, char **argv) {
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_relocations(ctx);
|
||||
|
||||
// Compute the is_weak bit for each imported symbol.
|
||||
compute_imported_symbol_weakness(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
@@ -642,6 +657,9 @@ int elf_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
if (ctx.arg.z_rewrite_endbr)
|
||||
rewrite_endbr(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
@@ -671,7 +689,7 @@ int elf_main(int argc, char **argv) {
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
if (!ctx.arg.plugin.empty())
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_all.stop();
|
||||
@@ -688,10 +706,11 @@ int elf_main(int argc, char **argv) {
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
release_global_lock(ctx);
|
||||
release_global_lock();
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
@@ -702,12 +721,6 @@ int elf_main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
+3
-2
@@ -54,6 +54,8 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
Timer t(ctx, "print_map");
|
||||
|
||||
std::ostream *out = &std::cout;
|
||||
std::unique_ptr<std::ofstream> file;
|
||||
|
||||
@@ -75,7 +77,7 @@ void print_map(Context<E> &ctx) {
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
|
||||
if (osec->kind() != OUTPUT_SECTION)
|
||||
if (!osec->to_osec())
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
|
||||
@@ -84,7 +86,6 @@ void print_map(Context<E> &ctx) {
|
||||
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
|
||||
InputSection<E> *mem = members[i];
|
||||
std::ostringstream ss;
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
u64 addr = osec->shdr.sh_addr + mem->offset;
|
||||
|
||||
ss << std::showbase
|
||||
|
||||
+15
-3
@@ -1,8 +1,5 @@
|
||||
#define _GNU_SOURCE 1
|
||||
|
||||
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
#include <dlfcn.h>
|
||||
#include <spawn.h>
|
||||
#include <stdarg.h>
|
||||
@@ -12,6 +9,10 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static char *get_mold_path() {
|
||||
@@ -132,3 +133,14 @@ int posix_spawn(pid_t *pid, const char *path,
|
||||
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
|
||||
return real(pid, path, file_actions, attrp, argv, envp);
|
||||
}
|
||||
|
||||
int posix_spawnp(pid_t *pid, const char *file,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *attrp,
|
||||
char *const *argv, char *const *envp) {
|
||||
debug_print("posix_spawnp %s\n", file);
|
||||
if (is_ld(file))
|
||||
file = get_mold_path();
|
||||
typeof(posix_spawnp) *real = dlsym(RTLD_NEXT, "posix_spawnp");
|
||||
return real(pid, file, file_actions, attrp, argv, envp);
|
||||
}
|
||||
+231
-203
File diff suppressed because it is too large.
Load diff
+336
-360
File diff suppressed because it is too large.
Load diff
+678
-314
File diff suppressed because it is too large.
Load diff
+21
-25
@@ -63,10 +63,10 @@ static consteval i64 max_distance() {
|
||||
// ARM64/ARM32/PPC, respectively.
|
||||
static constexpr i64 batch_size = max_distance() / 10;
|
||||
|
||||
// We assume that a single thunk group is smaller than 100 KiB.
|
||||
static constexpr i64 max_thunk_size = 102400;
|
||||
// We assume that a single thunk group is smaller than 900 KiB.
|
||||
static constexpr i64 max_thunk_size = 900 * 1024;
|
||||
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
static_assert(max_thunk_size / E::thunk_size < ThunkRef::MAX_SYM_IDX);
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
@@ -119,7 +119,7 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
return -max_distance() <= val && val < max_distance();
|
||||
}
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
static void reset_thunk(Thunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
@@ -129,10 +129,10 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
|
||||
// Scan relocations to collect symbols that need thunks.
|
||||
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
RangeExtensionThunk<E> &thunk, i64 thunk_idx) {
|
||||
Thunk<E> &thunk, i64 thunk_idx) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
std::vector<ThunkRef> &thunk_refs = isec.extra.thunk_refs;
|
||||
thunk_refs.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -151,15 +151,15 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
// This relocation needs a thunk. If the symbol is already in a
|
||||
// previous thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i].thunk_idx = sym.extra.thunk_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
thunk_refs[i].thunk_idx = sym.extra.thunk_idx;
|
||||
thunk_refs[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to the current thunk if it's not
|
||||
// added already.
|
||||
range_extn[i].thunk_idx = thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
thunk_refs[i].thunk_idx = thunk_idx;
|
||||
thunk_refs[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
@@ -170,8 +170,6 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
|
||||
template <>
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
using Thunk = RangeExtensionThunk<E>;
|
||||
|
||||
std::span<InputSection<E> *> m = members;
|
||||
if (m.empty())
|
||||
return;
|
||||
@@ -179,9 +177,8 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for_each(m, [](InputSection<E> *isec) {
|
||||
for (InputSection<E> *isec : m)
|
||||
isec->offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
@@ -215,7 +212,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// Move D foward as far as we can jump from B to a thunk at D.
|
||||
auto d_thunk_end = [&] {
|
||||
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
|
||||
return align_to(d_end, Thunk::alignment) + max_thunk_size;
|
||||
return align_to(d_end, Thunk<E>::alignment) + max_thunk_size;
|
||||
};
|
||||
|
||||
while (d < m.size() &&
|
||||
@@ -243,9 +240,9 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, Thunk::alignment);
|
||||
offset = align_to(offset, Thunk<E>::alignment);
|
||||
i64 thunk_idx = thunks.size();
|
||||
Thunk *thunk = new Thunk(*this, offset);
|
||||
Thunk<E> *thunk = new Thunk<E>(*this, offset);
|
||||
thunks.emplace_back(thunk);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
@@ -267,10 +264,9 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; i < thunk->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *thunk->symbols[i];
|
||||
sym.extra.thunk_idx = thunk_idx;
|
||||
sym.extra.thunk_sym_idx = i;
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk->symbols) {
|
||||
sym->extra.thunk_idx = thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
@@ -278,11 +274,11 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<Symbol<E> *> syms = isec->file.symbols;
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
|
||||
std::span<ThunkRef> thunk_refs = isec->extra.thunk_refs;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
if (thunk_refs[i].thunk_idx == thunk_idx)
|
||||
thunk_refs[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next batch.
|
||||
|
||||
+15
-21
@@ -1,54 +1,48 @@
|
||||
#!/bin/sh
|
||||
# This script installs binary packages needed to build mold.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# The first line for each distro installs a build dependency.
|
||||
# The second line installs extra packages for unittests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++ g++-10
|
||||
apt-get install -y file
|
||||
;;
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++
|
||||
apt-get install -y file
|
||||
;;
|
||||
fedora-*)
|
||||
dnf install -y gcc-g++ cmake
|
||||
dnf install -y glibc-static file libstdc++-static diffutils util-linux
|
||||
fedora-* | amzn-* | rhel-*)
|
||||
dnf install -y gcc-g++ cmake glibc-static libstdc++-static diffutils util-linux
|
||||
;;
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
zypper install -y make cmake gcc-c++ gcc11-c++ glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
opensuse-tumbleweed-*)
|
||||
zypper install -y make cmake gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo-*)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake
|
||||
emerge dev-build/cmake
|
||||
;;
|
||||
arch-* | archarm-* | artix-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel cmake util-linux
|
||||
arch-* | archarm-* | artix-* | endeavouros-*)
|
||||
pacman -Sy --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
void-*)
|
||||
xbps-install -Sy xbps
|
||||
xbps-install -Sy bash make cmake gcc
|
||||
xbps-install -Sy tar diffutils util-linux
|
||||
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
|
||||
;;
|
||||
alpine-*)
|
||||
apk update
|
||||
apk add bash make linux-headers cmake gcc g++
|
||||
;;
|
||||
clear-linux-*)
|
||||
swupd update
|
||||
swupd bundle-add c-basic diffutils
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
|
||||
+142
-52
@@ -1,6 +1,9 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" ON)
|
||||
set(MACHINE ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
if(EXISTS "/proc/cpuinfo")
|
||||
file(READ "/proc/cpuinfo" CPUINFO)
|
||||
endif()
|
||||
|
||||
# CMAKE_HOST_SYSTEM_PROCESSOR returns "aarch64" on ARM32 userland
|
||||
# with ARM64 kernel. Here, we run `cc -dumpmachine` to determine the
|
||||
@@ -10,82 +13,169 @@ execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
||||
OUTPUT_VARIABLE ARCH
|
||||
ERROR_QUIET)
|
||||
if(NOT EXIT_CODE AND ARCH MATCHES "([^-]+).*")
|
||||
set(HOST ${CMAKE_MATCH_1})
|
||||
set(MACHINE ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "i386")
|
||||
set(HOST i686)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "powerpc64")
|
||||
set(HOST ppc64)
|
||||
elseif(${HOST} STREQUAL "powerpc64le")
|
||||
set(HOST ppc64le)
|
||||
if(${MACHINE} MATCHES "amd64")
|
||||
set(MACHINE x86_64)
|
||||
elseif(${MACHINE} MATCHES "i386")
|
||||
set(MACHINE i686)
|
||||
elseif(${MACHINE} MATCHES "arm.*")
|
||||
set(MACHINE arm)
|
||||
elseif(${MACHINE} STREQUAL "powerpc64")
|
||||
set(MACHINE ppc64)
|
||||
elseif(${MACHINE} STREQUAL "powerpc64le")
|
||||
set(MACHINE ppc64le)
|
||||
endif()
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS)
|
||||
list(APPEND QEMU_ARCHS
|
||||
x86_64 i386 arm aarch64 ppc ppc64 ppc64le sparc64 sh4 s390x
|
||||
alpha riscv64 riscv32 m68k loongarch64)
|
||||
|
||||
LIST(APPEND TRIPLES
|
||||
x86_64-linux-gnu
|
||||
i686-linux-gnu
|
||||
aarch64-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
riscv64-linux-gnu
|
||||
powerpc-linux-gnu
|
||||
powerpc64-linux-gnu
|
||||
powerpc64le-linux-gnu
|
||||
sparc64-linux-gnu
|
||||
s390x-linux-gnu
|
||||
sh4-linux-gnu
|
||||
alpha-linux-gnu
|
||||
riscv32-linux-gnu
|
||||
m68k-linux-gnu
|
||||
loongarch64-linux-gnu)
|
||||
|
||||
foreach(ARCH IN LISTS QEMU_ARCHS)
|
||||
find_program(HAS_qemu-${ARCH} qemu-${ARCH})
|
||||
endforeach()
|
||||
|
||||
foreach(TRIPLE IN LISTS TRIPLES)
|
||||
find_program(HAS_${TRIPLE}-gcc ${TRIPLE}-gcc)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
function(add_target ARCH TRIPLE)
|
||||
if(${ARCH} STREQUAL ${HOST})
|
||||
set(CPU ${ARGV2})
|
||||
|
||||
if(${ARCH} STREQUAL ${MACHINE})
|
||||
set(IS_NATIVE 1)
|
||||
endif()
|
||||
|
||||
if(IS_NATIVE OR MOLD_ENABLE_QEMU_TESTS)
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "*.sh")
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "${ARCH}_*.sh")
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"${ARCH}_*.sh")
|
||||
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
if(CPU)
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}_${CPU}-${TEST}")
|
||||
else()
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}-${TEST}")
|
||||
endif()
|
||||
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT "HOST=${HOST}")
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT
|
||||
"MACHINE=${MACHINE};CPU=${CPU}")
|
||||
|
||||
if(IS_NATIVE)
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY SKIP_REGULAR_EXPRESSION "skipped")
|
||||
else()
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT "TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
if(IS_NATIVE)
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY SKIP_REGULAR_EXPRESSION
|
||||
"skipped")
|
||||
else()
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT
|
||||
"TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
add_target(x86_64 x86_64-linux-gnu)
|
||||
add_target(i686 i686-linux-gnu)
|
||||
add_target(aarch64 aarch64-linux-gnu)
|
||||
add_target(arm arm-linux-gnueabihf)
|
||||
add_target(riscv64 riscv64-linux-gnu)
|
||||
add_target(ppc powerpc-linux-gnu)
|
||||
add_target(ppc64 powerpc64-linux-gnu)
|
||||
add_target(ppc64le powerpc64le-linux-gnu)
|
||||
add_target(sparc64 sparc64-linux-gnu)
|
||||
add_target(s390x s390x-linux-gnu)
|
||||
add_target(sh4 sh4-linux-gnu)
|
||||
add_target(alpha alpha-linux-gnu)
|
||||
if(${MACHINE} STREQUAL "x86_64" OR (HAS_qemu-x86_64 AND HAS_x86_64-linux-gnu-gcc))
|
||||
add_target(x86_64 x86_64-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
if(${MACHINE} STREQUAL "i686" OR (HAS_qemu-i386 AND HAS_i686-linux-gnu-gcc))
|
||||
add_target(i686 i686-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "aarch64" OR (HAS_qemu-aarch64 AND HAS_aarch64-linux-gnu-gcc))
|
||||
add_target(aarch64 aarch64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
|
||||
add_target(arm arm-linux-gnueabihf)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "riscv64" OR (HAS_qemu-riscv64 AND HAS_riscv64-linux-gnu-gcc))
|
||||
add_target(riscv64 riscv64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "riscv32" OR (HAS_qemu-riscv32 AND HAS_riscv32-linux-gnu-gcc))
|
||||
add_target(riscv32 riscv32-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
|
||||
add_target(ppc64le powerpc64le_power10-linux-gnu)
|
||||
if(${MACHINE} STREQUAL "ppc" OR (HAS_qemu-ppc AND HAS_powerpc-linux-gnu-gcc))
|
||||
add_target(ppc powerpc-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_M68K "Enable tests for m68k" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_M68K)
|
||||
if(${MACHINE} STREQUAL "ppc64" OR (HAS_qemu-ppc64 AND HAS_powerpc64-linux-gnu-gcc))
|
||||
add_target(ppc64 powerpc64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "ppc64le" OR (HAS_qemu-ppc64le AND HAS_powerpc64le-linux-gnu-gcc))
|
||||
add_target(ppc64le powerpc64le-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "ppc64le" AND "${CPUINFO}" MATCHES "POWER10")
|
||||
add_target(ppc64le powerpc64le-linux-gnu power10)
|
||||
elseif(HAS_qemu-ppc64le AND HAS_powerpc64le-linux-gnu-gcc)
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/empty.c" "")
|
||||
|
||||
execute_process(
|
||||
COMMAND powerpc64le-linux-gnu-gcc -mcpu=power10 -E
|
||||
"${CMAKE_BINARY_DIR}/empty.c"
|
||||
RESULT_VARIABLE GCC_EXIT_CODE
|
||||
OUTPUT_QUIET ERROR_QUIET)
|
||||
|
||||
execute_process(
|
||||
COMMAND qemu-ppc64le -cpu help
|
||||
OUTPUT_VARIABLE QEMU_CPUS)
|
||||
|
||||
if("${GCC_EXIT_CODE}" EQUAL "0" AND "${QEMU_CPUS}" MATCHES power10_v2.0)
|
||||
add_target(ppc64le powerpc64le-linux-gnu power10)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "sparc64" OR (HAS_qemu-sparc64 AND HAS_sparc64-linux-gnu-gcc))
|
||||
add_target(sparc64 sparc64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "s390x" OR (HAS_qemu-s390x AND HAS_s390x-linux-gnu-gcc))
|
||||
add_target(s390x s390x-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "sh4" OR (HAS_qemu-sh4 AND HAS_sh4-linux-gnu-gcc))
|
||||
add_target(sh4 sh4-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "alpha" OR (HAS_qemu-alpha AND HAS_alpha-linux-gnu-gcc))
|
||||
add_target(alpha alpha-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "m68k" OR (HAS_qemu-m68k AND HAS_m68k-linux-gnu-gcc))
|
||||
add_target(m68k m68k-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_LOONGARCH "Enable tests for LoongArch" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH OR ${HOST} STREQUAL loongarch64)
|
||||
if(${MACHINE} STREQUAL "loongarch64" OR (HAS_qemu-loongarch64 AND HAS_loongarch64-linux-gnu-gcc))
|
||||
add_target(loongarch64 loongarch64-linux-gnu)
|
||||
endif()
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
|
||||
.global foo
|
||||
.type foo, %function
|
||||
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler - -mthumb
|
||||
.globl foo
|
||||
foo = 3;
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fno-PIC -c -o $t/b.o -xc - -mthumb 2> /dev/null || skip
|
||||
#include <stdio.h>
|
||||
extern char foo;
|
||||
int main() { printf("foo=%p\n", &foo); }
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe -pie $t/a.o $t/b.o >& $t/log
|
||||
grep -q 'recompile with -fPIC' $t/log
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] || skip
|
||||
|
||||
echo 'int main() {}' | $CC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|
||||
|| skip
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[[ $MACHINE == arm* ]] || skip
|
||||
|
||||
echo 'int foo() { return 0; }' | $CC -o /dev/null -c -xc - -mthumb 2> /dev/null || skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -mthumb
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] || skip
|
||||
is_musl && skip
|
||||
|
||||
echo 'int main() {}' | $GCC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# GCC produces buggy code for this test case on s390x.
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
|
||||
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
|
||||
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-4]\.' && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
|
||||
int foo = 4;
|
||||
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
|
||||
__attribute__((weak)) int foo = 4;
|
||||
int bar = 4;
|
||||
__attribute__((weak)) int baz = 4;
|
||||
|
||||
int get_foo1() { return foo; }
|
||||
int get_bar1() { return bar; }
|
||||
__attribute__((weak)) int get_baz1() { return baz; }
|
||||
|
||||
int get_foo2() { return get_foo1(); }
|
||||
int get_bar2() { return get_bar1(); }
|
||||
int get_baz2() { return get_baz1(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,-Bsymbolic-non-weak-functions
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
int foo = 3;
|
||||
int bar = 3;
|
||||
int baz = 3;
|
||||
|
||||
int get_foo1() { return 7; }
|
||||
int get_bar1() { return 7; }
|
||||
int get_baz1() { return 7; }
|
||||
|
||||
int get_foo2();
|
||||
int get_bar2();
|
||||
int get_baz2();
|
||||
|
||||
int main() {
|
||||
printf("%d %d %d %d %d %d\n", foo, bar, baz,
|
||||
get_foo2(), get_bar2(), get_baz2());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so
|
||||
$QEMU $t/exe | grep -q '^3 3 3 3 3 7$'
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
|
||||
__attribute__((weak)) int foo = 4;
|
||||
int bar = 4;
|
||||
__attribute__((weak)) int baz = 4;
|
||||
|
||||
int get_foo1() { return foo; }
|
||||
int get_bar1() { return bar; }
|
||||
__attribute__((weak)) int get_baz1() { return baz; }
|
||||
|
||||
int get_foo2() { return get_foo1(); }
|
||||
int get_bar2() { return get_bar1(); }
|
||||
int get_baz2() { return get_baz1(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,-Bsymbolic-non-weak
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
int foo = 3;
|
||||
int bar = 3;
|
||||
int baz = 3;
|
||||
|
||||
int get_foo1() { return 7; }
|
||||
int get_bar1() { return 7; }
|
||||
int get_baz1() { return 7; }
|
||||
|
||||
int get_foo2();
|
||||
int get_bar2();
|
||||
int get_baz2();
|
||||
|
||||
int main() {
|
||||
printf("%d %d %d %d %d %d\n", foo, bar, baz,
|
||||
get_foo2(), get_bar2(), get_baz2());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so
|
||||
$QEMU $t/exe | grep -q '^3 3 3 3 4 7$'
|
||||
@@ -7,8 +7,7 @@ $CC -B. -o $t/exe $t/a.c -Wl,-build-id
|
||||
readelf -n $t/exe | grep -qv 'GNU.*0x00000010.*NT_GNU_BUILD_ID'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=uuid
|
||||
readelf -nW $t/exe |
|
||||
grep -Eq 'GNU.*0x00000010.*NT_GNU_BUILD_ID.*Build ID: ............4...[89abcdef]'
|
||||
readelf -nW $t/exe | grep -Eq 'Build ID: ............4...[89abcdef]'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=md5
|
||||
readelf -n $t/exe | grep -q 'GNU.*0x00000010.*NT_GNU_BUILD_ID'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# GCC produces buggy code for this test case on s390x.
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
|
||||
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
|
||||
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-4]\.' && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.so -fPIC -shared -xc -
|
||||
void *foo() {
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@ int main() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
readelf -p .comment $t/exe | grep -q '[ms]old'
|
||||
readelf -p .comment $t/exe | grep -q mold
|
||||
readelf -SW $t/exe | grep -Eq '\.comment.*\bMS\b'
|
||||
+22
-21
@@ -5,7 +5,7 @@ export LC_ALL=C
|
||||
|
||||
canonical_name() {
|
||||
case $1 in
|
||||
i?86) echo i386 ;;
|
||||
i?86) echo i686 ;;
|
||||
arm*) echo arm ;;
|
||||
powerpc) echo ppc ;;
|
||||
powerpc64) echo ppc64 ;;
|
||||
@@ -14,14 +14,13 @@ canonical_name() {
|
||||
esac
|
||||
}
|
||||
|
||||
if [ -z "$HOST" ]; then
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
if [ -z "$MACHINE" ]; then
|
||||
MACHINE=$(canonical_name $(uname -m))
|
||||
fi
|
||||
|
||||
# Set tool names
|
||||
if [ -z "$TRIPLE" ]; then
|
||||
MACHINE=$HOST
|
||||
TESTDIR=out/test/elf/$HOST
|
||||
TESTDIR=out/test/elf/$MACHINE
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
@@ -30,8 +29,7 @@ if [ -z "$TRIPLE" ]; then
|
||||
OBJCOPY=objcopy
|
||||
STRIP=strip
|
||||
QEMU=
|
||||
elif [ "$TRIPLE" = powerpc64le_power10-linux-gnu ]; then
|
||||
TRIPLE=powerpc64le-linux-gnu
|
||||
elif [ "$TRIPLE" = powerpc64le-linux-gnu -a "$CPU" = power10 ]; then
|
||||
MACHINE=ppc64le
|
||||
TESTDIR=out/test/elf/ppc64le-power10
|
||||
CC="${TEST_CC:-$TRIPLE-gcc} -mcpu=power10"
|
||||
@@ -52,34 +50,33 @@ else
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
|
||||
if [ $MACHINE = i686 ]; then
|
||||
QEMU="qemu-i386 -L /usr/$TRIPLE"
|
||||
else
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i386 -o $MACHINE = arm ]; then
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i686 -o $MACHINE = arm ]; then
|
||||
tlsdesc_opt=-mtls-dialect=gnu2
|
||||
elif [ $MACHINE = aarch64 ]; then
|
||||
tlsdesc_opt=-mtls-dialect=desc
|
||||
fi
|
||||
|
||||
if [ $MACHINE = ppc64le -a -e /proc/cpuinfo ] && grep -q POWER10 /proc/cpuinfo; then
|
||||
CC="$CC -mcpu=power10"
|
||||
CXX="$CXX -mcpu=power10"
|
||||
GCC="$GCC -mcpu=power10"
|
||||
GXX="$GXX -mcpu=power10"
|
||||
fi
|
||||
|
||||
# Common functions
|
||||
test_cflags() {
|
||||
echo 'int main() {}' | $CC "$@" -o /dev/null -xc - >& /dev/null
|
||||
}
|
||||
|
||||
supports_ifunc() {
|
||||
echo 'void x() __attribute__((ifunc("y"))); void *y() { return 0; }' | \
|
||||
$CC -c -o /dev/null -xc - >& /dev/null
|
||||
is_musl() {
|
||||
ldd --version 2>&1 | grep -q musl
|
||||
}
|
||||
|
||||
is_musl() {
|
||||
ldd --help 2>&1 | grep -q musl
|
||||
supports_ifunc() {
|
||||
! is_musl && \
|
||||
echo 'void x() __attribute__((ifunc("y"))); void *y() { return 0; }' | \
|
||||
$CC -c -o /dev/null -xc - >& /dev/null
|
||||
}
|
||||
|
||||
supports_tlsdesc() {
|
||||
@@ -89,6 +86,10 @@ supports_tlsdesc() {
|
||||
[ -n "$tlsdesc_opt" ]
|
||||
}
|
||||
|
||||
on_qemu() {
|
||||
[ "$QEMU" != '' ] || grep -qw qemu /proc/cpuinfo 2> /dev/null
|
||||
}
|
||||
|
||||
skip() {
|
||||
echo skipped
|
||||
trap - EXIT
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[[ $MACHINE = ppc64* ]] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
#include <stdio.h>
|
||||
|
||||
extern char msg[100];
|
||||
|
||||
int main() {
|
||||
printf("%s\n", msg);
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -fPIC -shared -o $t/b.so -xc -
|
||||
__attribute__((section (".data.rel.ro"))) char msg[100] = "Hello world";
|
||||
EOF
|
||||
|
||||
$CC -B. $t/a.o $t/b.so -o $t/exe1 -no-pie -Wl,-z,relro
|
||||
readelf -W --sections $t/exe1 > $t/log1
|
||||
grep -Fq .copyrel.rel.ro $t/log1
|
||||
|
||||
$CC -B. $t/a.o $t/b.so -o $t/exe2 -no-pie -Wl,-z,norelro
|
||||
readelf -W --sections $t/exe2 > $t/log2
|
||||
! grep -Fq .copyrel.rel.ro $t/log2 || false
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
@@ -32,7 +31,7 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
|
||||
cat <<EOF | $CC -B. -fPIC -shared -o $t/b.so -xc -
|
||||
__attribute__((section (".data.rel.ro"))) char readonly[100] = "abc";
|
||||
char readwrite[100] = "abc";
|
||||
EOF
|
||||
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
static void ctor1() { printf("ctor1 "); }
|
||||
static void ctor2() { printf("ctor2 "); }
|
||||
static void ctor3() { printf("ctor3 "); }
|
||||
static void ctor4() { printf("ctor4 "); }
|
||||
|
||||
static void dtor1() { printf("dtor1 "); }
|
||||
static void dtor2() { printf("dtor2 "); }
|
||||
static void dtor3() { printf("dtor3 "); }
|
||||
static void dtor4() { printf("dtor4 "); }
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors.65435")))
|
||||
void (*ctors65435[])() = { ctor1 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors.65433")))
|
||||
void (*ctors65433[])() = { ctor2 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors")))
|
||||
void (*ctors[])() = { ctor4, ctor3 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors")))
|
||||
void (*dtors[])() = { dtor1, dtor2 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors.65433")))
|
||||
void (*dtors65433[])() = { dtor3 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors.65435")))
|
||||
void (*dtors65435[])() = { dtor4 };
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((constructor(101)))
|
||||
static void init1() { printf("init1 "); }
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init2() { printf("init2 "); }
|
||||
|
||||
__attribute__((destructor(101)))
|
||||
static void fini1() { printf("fini1 "); }
|
||||
|
||||
__attribute__((destructor))
|
||||
static void fini2() { printf("fini2 "); }
|
||||
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q 'ctor1 init1 ctor2 ctor3 ctor4 init2 fini2 dtor1 dtor2 dtor3 fini1 dtor4'
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o -Wl,-defsym=foo=bar 2> $t/log
|
||||
grep -q 'undefined symbol: bar' $t/log
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/a.o -xc -
|
||||
void _ZN2ns7versionEv();
|
||||
int main() { _ZN2ns7versionEv(); }
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe1 $t/a.o 2> $t/log || false
|
||||
grep -Fq 'ns::version()' $t/log
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/b.o -xc -
|
||||
void _ZN2ns7versionEv();
|
||||
int main() { _ZN2ns7versionEv(); }
|
||||
__attribute__((section(".comment"))) char str[] = "rustc version x.y.z\n";
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe2 $t/b.o 2> $t/log || false
|
||||
grep -Fq 'ns::versionv' $t/log
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "$t/a.o -Wl,-dependency-file=$t/dep" > $t/rsp
|
||||
|
||||
$CC -B. -o $t/exe @$t/rsp
|
||||
|
||||
grep -q '/exe:.*/a.o ' $t/dep
|
||||
grep -q '/a.o:$' $t/dep
|
||||
! grep -q '^/tmp' $t/dep || false
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
|
||||
void foo() { printf("foo1 "); }
|
||||
void bar() { printf("bar1 "); }
|
||||
void baz() { printf("baz1 "); }
|
||||
|
||||
void print() {
|
||||
foo();
|
||||
bar();
|
||||
baz();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; bar; };
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,--dynamic-list=$t/dyn
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
void foo() { printf("foo2 "); }
|
||||
void bar() { printf("bar2 "); }
|
||||
void baz() { printf("baz2 "); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/d.so $t/c.o
|
||||
|
||||
cat <<EOF | $CC -o $t/e.o -c -xc -
|
||||
#include <stdio.h>
|
||||
void print();
|
||||
int main() { print(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/e.o -Wl,-push-state,-no-as-needed $t/b.so -Wl,-pop-state
|
||||
$QEMU $t/exe1 | grep -q 'foo1 bar1 baz1'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/e.o -Wl,-push-state,-no-as-needed $t/d.so $t/b.so -Wl,-pop-state
|
||||
$QEMU $t/exe2 | grep -q 'foo2 bar2 baz1'
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -g -ffunction-sections -
|
||||
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
command -v perl > /dev/null || skip
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -xc++ -
|
||||
int foo() {
|
||||
try {
|
||||
throw 1;
|
||||
} catch (int x) {
|
||||
return x;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -o $t/b.o -c -xc++ -
|
||||
int bar() {
|
||||
try {
|
||||
throw 3;
|
||||
} catch (int x) {
|
||||
return x;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
EOF
|
||||
|
||||
perl -i -0777 -pe 's/\.eh_frame/.EH_FRAME/g' $t/a.o
|
||||
./mold -r -o $t/c.o $t/a.o $t/b.o
|
||||
perl -i -0777 -pe 's/\.EH_FRAME/.eh_frame/g' $t/c.o
|
||||
|
||||
cat <<EOF | $CXX -o $t/d.o -c -xc++ -
|
||||
#include <stdio.h>
|
||||
|
||||
int foo();
|
||||
int bar();
|
||||
|
||||
int main() {
|
||||
printf("%d %d\n", foo(), bar());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CXX -B. -o $t/exe1 $t/d.o $t/c.o
|
||||
$QEMU $t/exe1 | grep -q '^1 3$'
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
static=
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
# GCC emits data to .text for PPC64, so PPC64 is not compatible with -execute-only
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
Loaded 100 of 656 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user