mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 17:52:02 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f86868cb38 | ||
|
|
421289445a | ||
|
|
100922b650 | ||
|
|
0daf6233fb | ||
|
|
d0e4eee204 | ||
|
|
a7475dd5e3 | ||
|
|
c4af13889d | ||
|
|
7aaa4afce7 | ||
|
|
2fb96ad83f | ||
|
|
a2fa4e5ecc | ||
|
|
68877ab6ca | ||
|
|
f62777e606 | ||
|
|
c80e316649 | ||
|
|
d1b9549817 | ||
|
|
01d2dd8dd9 | ||
|
|
6109154a07 | ||
|
|
49d4dac7e6 | ||
|
|
87a5f238dd | ||
|
|
e448e0eb2b | ||
|
|
4df5d6497a | ||
|
|
e90ad9be16 | ||
|
|
b5788e0caa | ||
|
|
9178e43681 | ||
|
|
5feab826de | ||
|
|
2fc0231348 | ||
|
|
804ae412ed | ||
|
|
f0a030cbcd | ||
|
|
ba6347996b | ||
|
|
a151f4180f | ||
|
|
2c947ce2a9 | ||
|
|
63b7102a4c | ||
|
|
731dd0ed25 | ||
|
|
3105195364 | ||
|
|
23045c6f95 | ||
|
|
2d29f234e2 | ||
|
|
4edaf82691 | ||
|
|
49aa10318c | ||
|
|
2c03ed5673 | ||
|
|
e0bc74ad8a | ||
|
|
3768ca612e | ||
|
|
86d59c29eb | ||
|
|
96a4a1b635 | ||
|
|
006b13aec1 | ||
|
|
91947405fe | ||
|
|
b7123bded5 | ||
|
|
0884f279e0 | ||
|
|
615a24d1dd | ||
|
|
e1e4e9fd9e | ||
|
|
ceed69ff49 | ||
|
|
d90f7e162b | ||
|
|
340528bdc3 | ||
|
|
392781a0e2 | ||
|
|
4e10ee7e50 | ||
|
|
8d94fd6ca3 | ||
|
|
358f244247 | ||
|
|
7a1cbb9b34 | ||
|
|
e6d4154688 | ||
|
|
60eae9ed20 | ||
|
|
7fb1b1b9ec | ||
|
|
0ecd72900e | ||
|
|
9eb4e76daf | ||
|
|
b6876b5640 | ||
|
|
f4753b34bf | ||
|
|
8196199cac | ||
|
|
2a3ee11b7d | ||
|
|
68f2a26987 | ||
|
|
227a6c8a88 | ||
|
|
fe617c3618 | ||
|
|
e3a7c9e017 | ||
|
|
5ee6f89544 | ||
|
|
40c2ad7839 | ||
|
|
5edd07efd9 | ||
|
|
8e5def28ce | ||
|
|
b534d5af27 | ||
|
|
673f531adb | ||
|
|
66d3de438c | ||
|
|
d9ea875677 | ||
|
|
577361478a | ||
|
|
408e2bfee8 | ||
|
|
9ac23730b7 | ||
|
|
bf26753bbd | ||
|
|
8e3cee958b | ||
|
|
05f4a43c8f | ||
|
|
ca227fb8da | ||
|
|
78e4b06cb8 | ||
|
|
94cace7fc1 | ||
|
|
812c59fd5c | ||
|
|
8770bd775b | ||
|
|
89d43f2673 | ||
|
|
632a2ec1b4 | ||
|
|
6af78bf17b | ||
|
|
9360267cc3 | ||
|
|
66b995b5a9 | ||
|
|
94e2d7d6dc | ||
|
|
759aa95618 | ||
|
|
575a604d5d | ||
|
|
ac9a1c4048 | ||
|
|
d5cf590498 | ||
|
|
18656091f6 | ||
|
|
ead32811b3 | ||
|
|
7cdfa31e6c | ||
|
|
a6e4676a71 | ||
|
|
0c88d323c8 | ||
|
|
326150a182 | ||
|
|
339816702d | ||
|
|
146ddd711c | ||
|
|
32d1006e29 | ||
|
|
66a057cce7 | ||
|
|
619ff869e4 | ||
|
|
e7c071ea36 | ||
|
|
3d68824dea | ||
|
|
c98d276916 | ||
|
|
446c461201 | ||
|
|
98edd441ce | ||
|
|
37f6ef2081 | ||
|
|
41cf6d7814 | ||
|
|
bb5a596bc5 | ||
|
|
1b6f301644 | ||
|
|
8a089fee45 | ||
|
|
82e80721ac | ||
|
|
d3dea2f435 | ||
|
|
a20e3b8004 | ||
|
|
c1af9c97e7 | ||
|
|
3999aa8b18 | ||
|
|
b4d9db9db8 | ||
|
|
95c7871c6e | ||
|
|
3f7bffe93c | ||
|
|
ec473c74d7 | ||
|
|
9e11b40a99 | ||
|
|
a4fd7e3819 | ||
|
|
aae3dc3c1e | ||
|
|
ba7967152d | ||
|
|
d6dae3be6d | ||
|
|
83aaa63a49 | ||
|
|
b0d91b9967 | ||
|
|
4ea052591a | ||
|
|
2f03e46bb3 | ||
|
|
6205a1edc3 | ||
|
|
4dea134949 | ||
|
|
eb79859ea2 | ||
|
|
8714b78d48 | ||
|
|
86b142e555 | ||
|
|
e5549c73f0 | ||
|
|
4dae896726 | ||
|
|
83e05dae28 | ||
|
|
99a0aaf133 | ||
|
|
7068c0cd0f | ||
|
|
f49e4024e9 | ||
|
|
567a78dbd5 | ||
|
|
f2d27d8d24 | ||
|
|
dc9573989c | ||
|
|
e469b04f0d | ||
|
|
32e158d340 | ||
|
|
18b6f96756 | ||
|
|
2b7dbb01c4 | ||
|
|
c1c9bdf363 | ||
|
|
d4bbf31db2 | ||
|
|
9b5d404d1e | ||
|
|
4caadefac6 | ||
|
|
8f21cc386e | ||
|
|
f7de9571f8 | ||
|
|
e413433752 | ||
|
|
e67f460bfd | ||
|
|
e281ed88bb | ||
|
|
41986279de | ||
|
|
480efdedf0 | ||
|
|
99e3f959fc | ||
|
|
4ec0bbf048 | ||
|
|
f4c0e321db | ||
|
|
31a5cdf86f | ||
|
|
a44de89991 | ||
|
|
30683645c2 | ||
|
|
ac3ae60184 | ||
|
|
9f34830882 | ||
|
|
4aa4bfa4a6 | ||
|
|
fb6967c0cf | ||
|
|
385c1c494d | ||
|
|
4667be6134 | ||
|
|
3f9868238b | ||
|
|
3e8c519d4e | ||
|
|
aa27d674c1 | ||
|
|
3cbc57f9af | ||
|
|
a1e6d8e8df | ||
|
|
a054bcd4ff | ||
|
|
e47c67727c | ||
|
|
16bdf4009a | ||
|
|
c173f9c0ab | ||
|
|
89612b7096 | ||
|
|
d258140d54 | ||
|
|
6ce142d73a | ||
|
|
3a15c54f10 | ||
|
|
4a6e71096c | ||
|
|
ec9ac181f8 | ||
|
|
ccd475e41d | ||
|
|
7dc6ce72dd | ||
|
|
064be343f5 | ||
|
|
c8853223a3 | ||
|
|
0b7d210f99 | ||
|
|
b22e6f80fc | ||
|
|
15fa05eff6 | ||
|
|
61e0311889 | ||
|
|
825b918146 | ||
|
|
19587d43ee | ||
|
|
f4a9b05114 | ||
|
|
7735cf5b95 | ||
|
|
fcb28161b1 | ||
|
|
b57da5413c | ||
|
|
630b262d2b | ||
|
|
8f0acbf413 | ||
|
|
a3f2a5c7ec | ||
|
|
b5db63f4bb | ||
|
|
c325cc039a | ||
|
|
0b7edc07f4 | ||
|
|
da48efe63b | ||
|
|
3f1b071ae5 | ||
|
|
444cb7a566 | ||
|
|
889f9f3a36 | ||
|
|
e4c4ab053e | ||
|
|
10ff2d705c | ||
|
|
31f7427859 | ||
|
|
c338540885 | ||
|
|
d908d4c61c | ||
|
|
b94dd6dbeb | ||
|
|
a7333bdb4e | ||
|
|
881b55d691 | ||
|
|
28236b1b34 | ||
|
|
7e62ce4bd8 | ||
|
|
b0d75f8957 | ||
|
|
68a35bfa03 | ||
|
|
70ec56f8bb | ||
|
|
e18eaac3eb | ||
|
|
5f17da80d0 | ||
|
|
1663bcf932 | ||
|
|
16a3c9491f | ||
|
|
bab74c0c7e | ||
|
|
5c5d50929e | ||
|
|
ce744aeca4 | ||
|
|
0222dc6f39 | ||
|
|
46f954d23d | ||
|
|
42b3038463 | ||
|
|
c9612d3120 | ||
|
|
60d0900496 | ||
|
|
c1963ce2ac | ||
|
|
1dcd20b401 | ||
|
|
7e91897154 | ||
|
|
61eb265cf4 | ||
|
|
46995bcfc3 | ||
|
|
dd8002f092 | ||
|
|
6ee5aea1bf | ||
|
|
81cf1171a9 | ||
|
|
955c6d1fe4 | ||
|
|
7546b44111 | ||
|
|
d4679063fc | ||
|
|
e8c72767fa | ||
|
|
440e4c4ade | ||
|
|
70afbd1ca7 | ||
|
|
5e9238e3f0 | ||
|
|
7f6bfc1bdb | ||
|
|
ebc2bfafdd | ||
|
|
249c331117 | ||
|
|
818c59e4dd | ||
|
|
ddcb7b4197 | ||
|
|
4a0eef3b14 | ||
|
|
5850f237fe | ||
|
|
0f8bf23bcf | ||
|
|
a2fa0934d0 | ||
|
|
214c35e05d | ||
|
|
e5f4b967ed | ||
|
|
73159e241d | ||
|
|
a1287c26b9 | ||
|
|
36fe7511f1 | ||
|
|
4551fffe69 | ||
|
|
bef545bdb9 | ||
|
|
ab2db49fbe | ||
|
|
e65c5d2a1c | ||
|
|
27c50e8158 | ||
|
|
07a480c43c | ||
|
|
6ffcae4286 | ||
|
|
03e0cef726 | ||
|
|
a4d9bc7550 | ||
|
|
f24b997501 | ||
|
|
f72999a1df | ||
|
|
b02c8cb8f5 | ||
|
|
579fafc04b | ||
|
|
77716836be | ||
|
|
017082f22f | ||
|
|
1abc1f9275 | ||
|
|
12df85ad8c | ||
|
|
1b2b20423e | ||
|
|
afca9d55aa | ||
|
|
043449125d | ||
|
|
26fe71d64c | ||
|
|
354d1b7d2f | ||
|
|
dca8d58b51 | ||
|
|
1550b5afff | ||
|
|
e4c03c238e | ||
|
|
0542a2254b | ||
|
|
d0ab7d9e17 | ||
|
|
dbe99c2884 | ||
|
|
c6ddcc4344 | ||
|
|
bfa4ac2ef8 | ||
|
|
818971841f | ||
|
|
5c123e2c68 | ||
|
|
dc7eedee74 | ||
|
|
2359222723 | ||
|
|
9b90f642a1 | ||
|
|
0acd4b9d2f | ||
|
|
7fcff98f29 | ||
|
|
3c25434202 | ||
|
|
e98aab7ea9 | ||
|
|
a380c1675f | ||
|
|
3232c1f884 | ||
|
|
c6b34c2b77 | ||
|
|
d3140a25f6 | ||
|
|
5859be88b9 | ||
|
|
35c0d4ae9a | ||
|
|
7aa35d12c3 | ||
|
|
8465438051 | ||
|
|
d301b8a3fe | ||
|
|
3f442402f8 | ||
|
|
705451d21e | ||
|
|
f7b2ce278b | ||
|
|
c6e4fb56b4 | ||
|
|
e630dea2ab | ||
|
|
2afe571c07 | ||
|
|
1de4874478 | ||
|
|
00203dbc7c | ||
|
|
190003dac7 | ||
|
|
dcdffbdab7 | ||
|
|
80008796de | ||
|
|
5c89a6d08c | ||
|
|
0c4f8bed1b | ||
|
|
8a0bf96f6d | ||
|
|
7718000dba | ||
|
|
e061e3ca70 | ||
|
|
e06396dd79 | ||
|
|
77d979f112 | ||
|
|
57f996f806 | ||
|
|
81e6a88d58 | ||
|
|
e76f7c0d47 | ||
|
|
32a7e7cb17 | ||
|
|
379ee22af3 | ||
|
|
b879bade6a | ||
|
|
bc66d9a684 | ||
|
|
59826c197b | ||
|
|
33a5888725 | ||
|
|
0d542fb3bc | ||
|
|
229ee85890 | ||
|
|
110a29552a | ||
|
|
6c9bb0d7ff | ||
|
|
6a86370b0e | ||
|
|
cb94a4ab1b | ||
|
|
76b127e824 | ||
|
|
51f7caf263 | ||
|
|
e74760e780 | ||
|
|
614b6c0e38 | ||
|
|
df75bbee0e | ||
|
|
220161b6c6 | ||
|
|
b23f8e067c | ||
|
|
a38111d6bb | ||
|
|
daccce7bb8 | ||
|
|
4fc27d2195 | ||
|
|
708112cd16 | ||
|
|
3322ff6e7c | ||
|
|
42b7eb87a1 | ||
|
|
b4fa51e981 | ||
|
|
dc7432a6dd | ||
|
|
0a0f9b3ad5 | ||
|
|
b2ef045c03 | ||
|
|
e83d1616c8 | ||
|
|
d196ec43e6 | ||
|
|
03ba8f9325 | ||
|
|
79f83561bd | ||
|
|
0653716408 |
No files matched your search
+21
-21
@@ -9,35 +9,39 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: rui314/setup-mold@v1
|
||||
with:
|
||||
make-default: false
|
||||
- name: apt-get
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install git build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git bsdmainutils dwarfdump pkg-config
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make CC=clang CXX=clang++ DEBUG=1 -j$(nproc)
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=clang CXX=clang++ LDFLAGS=-fuse-ld=mold -j$(nproc)
|
||||
- name: make test
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make test -j$(nproc)
|
||||
build-gcc11:
|
||||
run: make -j$(nproc) test
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: apt-get
|
||||
run: dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang dwarfdump bsdextrautils libgcc-9-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev:i386 lib32gcc-10-dev qemu-user
|
||||
run: |
|
||||
dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang bsdmainutils dwarfdump qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make CC=gcc CXX=g++ DEBUG=1 -j$(nproc)
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=gcc CXX=g++ -j$(nproc)
|
||||
- name: make test
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make test -j$(nproc)
|
||||
run: make -j$(nproc) test-all
|
||||
build-macos:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
@@ -47,10 +51,6 @@ jobs:
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make DEBUG=1 -j$(sysctl -n hw.logicalcpu)
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu)
|
||||
- name: make test
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make test -j$(sysctl -n hw.logicalcpu)
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu) test
|
||||
@@ -11,3 +11,4 @@ ld
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ docker build -t rui314/mold-builder:v1-$(uname -m) .
|
||||
# $ docker push rui314/mold-builder:v1-$(uname -m)
|
||||
|
||||
FROM ubuntu:18.04
|
||||
RUN apt-get update && \
|
||||
TZ=Europe/London apt-get install -y tzdata && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get install -y --no-install-recommends build-essential git \
|
||||
wget pkg-config cmake libstdc++-11-dev zlib1g-dev gpg gpg-agent && \
|
||||
bash -c "$(wget -O- https://apt.llvm.org/llvm.sh)" && \
|
||||
apt-get install -y --no-install-recommends clang-14 && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
cd / && \
|
||||
wget -O- https://www.openssl.org/source/openssl-3.0.2.tar.gz | tar xzf - && \
|
||||
mv openssl-3.0.2 openssl && \
|
||||
cd openssl && \
|
||||
./Configure --prefix=/openssl && \
|
||||
make -j$(nproc)
|
||||
@@ -1,3 +1,9 @@
|
||||
# If you want to enable ASAN, run `make` with the following options:
|
||||
#
|
||||
# make CXXFLAGS='-fsanitize=address -g' LDFLAGS=-fsanitize=address USE_MIMALLOC=0
|
||||
|
||||
VERSION = 1.2.0
|
||||
|
||||
PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
LIBDIR = $(PREFIX)/lib
|
||||
@@ -16,16 +22,18 @@ ifeq ($(origin CXX), default)
|
||||
CXX = c++
|
||||
endif
|
||||
|
||||
# Allow overriding pkg-config binary
|
||||
PKG_CONFIG = pkg-config
|
||||
|
||||
# If you want to keep symbols in the installed binary, run make with
|
||||
# `STRIP=true` to run /bin/true instead of the strip command.
|
||||
STRIP = strip
|
||||
|
||||
SRCS=$(wildcard *.cc elf/*.cc macho/*.cc)
|
||||
HEADERS=$(wildcard *.h elf/*.h macho/*.h)
|
||||
OBJS=$(SRCS:%.cc=out/%.o)
|
||||
SRCS = $(wildcard *.cc elf/*.cc macho/*.cc)
|
||||
OBJS = $(SRCS:%.cc=out/%.o)
|
||||
|
||||
OS = $(shell uname -s)
|
||||
ARCH = $(shell uname -m)
|
||||
OS := $(shell uname -s)
|
||||
ARCH := $(shell uname -m)
|
||||
|
||||
IS_ANDROID = 0
|
||||
ifneq ($(findstring -android,$(shell $(CC) -dumpmachine)),)
|
||||
@@ -37,19 +45,18 @@ endif
|
||||
CFLAGS = -O2
|
||||
CXXFLAGS = -O2
|
||||
|
||||
MOLD_CXXFLAGS = -std=c++20 -fno-exceptions -fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables -DMOLD_VERSION=\"1.0.2\" \
|
||||
-DLIBDIR="\"$(LIBDIR)\""
|
||||
MOLD_CXXFLAGS := -std=c++20 -fno-exceptions -fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables -Ithird-party/xxhash \
|
||||
-DMOLD_VERSION=\"$(VERSION)\" -DLIBDIR="\"$(LIBDIR)\""
|
||||
|
||||
MOLD_LDFLAGS = -pthread -lz -lm
|
||||
MOLD_LDFLAGS := -pthread -lz -lm -ldl
|
||||
|
||||
GIT_HASH = $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
GIT_HASH := $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
ifneq ($(GIT_HASH),)
|
||||
MOLD_CXXFLAGS += -DGIT_HASH=\"$(GIT_HASH)\"
|
||||
endif
|
||||
|
||||
LTO = 0
|
||||
|
||||
ifeq ($(LTO), 1)
|
||||
CXXFLAGS += -flto -O3
|
||||
LDFLAGS += -flto
|
||||
@@ -66,6 +73,7 @@ endif
|
||||
|
||||
ifeq ($(USE_MIMALLOC), 1)
|
||||
ifdef SYSTEM_MIMALLOC
|
||||
MOLD_CXXFLAGS += -DUSE_SYSTEM_MIMALLOC
|
||||
MOLD_LDFLAGS += -lmimalloc
|
||||
else
|
||||
MIMALLOC_LIB = out/mimalloc/libmimalloc.a
|
||||
@@ -82,25 +90,21 @@ else
|
||||
MOLD_CXXFLAGS += -Ithird-party/tbb/include
|
||||
endif
|
||||
|
||||
ifdef SYSTEM_XXHASH
|
||||
MOLD_LDFLAGS += -lxxhash
|
||||
else
|
||||
XXHASH_LIB = third-party/xxhash/libxxhash.a
|
||||
MOLD_LDFLAGS += $(XXHASH_LIB)
|
||||
MOLD_CXXFLAGS += -Ithird-party/xxhash
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
ifeq ($(IS_ANDROID), 0)
|
||||
# glibc before 2.17 need librt for clock_gettime
|
||||
MOLD_LDFLAGS += -lrt
|
||||
MOLD_LDFLAGS += -Wl,-push-state -Wl,-as-needed -lrt -Wl,-pop-state
|
||||
endif
|
||||
endif
|
||||
|
||||
# Use pkg-config to know where libcrypto resides.
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
ifneq ($(OS), Darwin)
|
||||
MOLD_CXXFLAGS += $(shell pkg-config --cflags-only-I openssl)
|
||||
MOLD_LDFLAGS += $(shell pkg-config --libs-only-L openssl) -lcrypto
|
||||
NEEDS_LIBCRYPTO = 1
|
||||
endif
|
||||
|
||||
ifeq ($(NEEDS_LIBCRYPTO), 1)
|
||||
MOLD_CXXFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I openssl)
|
||||
MOLD_LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-L openssl) -lcrypto
|
||||
endif
|
||||
|
||||
# '-latomic' flag is needed building on riscv64 system
|
||||
@@ -119,25 +123,25 @@ ifeq ($(OS), Linux)
|
||||
MOLD_WRAPPER_LDFLAGS = -Wl,-push-state -Wl,-no-as-needed -ldl -Wl,-pop-state
|
||||
endif
|
||||
|
||||
DEPFLAGS = -MT $@ -MMD -MP -MF out/$*.d
|
||||
|
||||
all: mold mold-wrapper.so
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB) $(XXHASH_LIB)
|
||||
-include $(SRCS:%.cc=out/%.d)
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB)
|
||||
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
|
||||
ln -sf mold ld
|
||||
ln -sf mold ld64.mold
|
||||
|
||||
mold-wrapper.so: elf/mold-wrapper.c Makefile
|
||||
$(CC) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
mold-wrapper.so: elf/mold-wrapper.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
|
||||
out/%.o: %.cc $(HEADERS) Makefile out/elf/.keep out/macho/.keep
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
out/%.o: %.cc out/elf/.keep out/macho/.keep
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
out/elf/.keep:
|
||||
mkdir -p out/elf
|
||||
touch $@
|
||||
|
||||
out/macho/.keep:
|
||||
mkdir -p out/macho
|
||||
out/elf/.keep out/macho/.keep:
|
||||
mkdir -p $(@D)
|
||||
touch $@
|
||||
|
||||
$(MIMALLOC_LIB):
|
||||
@@ -147,26 +151,50 @@ $(MIMALLOC_LIB):
|
||||
|
||||
$(TBB_LIB):
|
||||
mkdir -p out/tbb
|
||||
(cd out/tbb; cmake -G'Unix Makefiles' -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS=-D__TBB_DYNAMIC_LOAD_ENABLED=0 -DTBB_STRICT=OFF ../../third-party/tbb)
|
||||
(cd out/tbb; cmake -G'Unix Makefiles' -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS="$(CXXFLAGS) -D__TBB_DYNAMIC_LOAD_ENABLED=0" -DTBB_STRICT=OFF ../../third-party/tbb)
|
||||
$(MAKE) -C out/tbb tbb
|
||||
(cd out/tbb; ln -sf *_relwithdebinfo libs)
|
||||
|
||||
$(XXHASH_LIB):
|
||||
$(MAKE) -C third-party/xxhash libxxhash.a
|
||||
|
||||
test tests check: all
|
||||
ifeq ($(OS), Darwin)
|
||||
$(MAKE) -C test -f Makefile.darwin --no-print-directory
|
||||
else
|
||||
$(MAKE) -C test -f Makefile.linux --no-print-directory --output-sync
|
||||
endif
|
||||
|
||||
@if test -t 1; then \
|
||||
printf '\e[32mPassed all tests\e[0m\n'; \
|
||||
else \
|
||||
echo 'Passed all tests'; \
|
||||
fi
|
||||
|
||||
test-x86-64: all
|
||||
@echo x86_64
|
||||
CC=x86_64-linux-gnu-gcc CXX=x86_64-linux-gnu-g++ GCC=x86_64-linux-gnu-gcc GXX=x86_64-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=x86_64 QEMU='qemu-x86_64 -L /usr/x86_64-linux-gnu' $(MAKE) test
|
||||
|
||||
test-i386: all
|
||||
@echo i386
|
||||
CC=i686-linux-gnu-gcc CXX=i686-linux-gnu-g++ GCC=i686-linux-gnu-gcc GXX=i686-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=i386 QEMU='qemu-i386 -L /usr/i686-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm64: all
|
||||
@echo arm64
|
||||
CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ GCC=aarch64-linux-gnu-gcc GXX=aarch64-linux-gnu-g++ OBJDUMP=aarch64-linux-gnu-objdump MACHINE=aarch64 QEMU='qemu-aarch64 -L /usr/aarch64-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm32: all
|
||||
@echo arm
|
||||
CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ GCC=arm-linux-gnueabihf-gcc GXX=arm-linux-gnueabihf-g++ OBJDUMP=arm-linux-gnueabihf-objdump MACHINE=arm QEMU='qemu-arm -L /usr/arm-linux-gnueabihf' $(MAKE) test
|
||||
|
||||
test-riscv64: all
|
||||
@echo riscv64
|
||||
CC=riscv64-linux-gnu-gcc CXX=riscv64-linux-gnu-g++ GCC=riscv64-linux-gnu-gcc GXX=riscv64-linux-gnu-g++ OBJDUMP=riscv64-linux-gnu-objdump MACHINE=riscv64 QEMU='qemu-riscv64 -L /usr/riscv64-linux-gnu' $(MAKE) test
|
||||
|
||||
|
||||
test-all:
|
||||
$(MAKE) test-x86-64
|
||||
$(MAKE) test-i386
|
||||
$(MAKE) test-arm64
|
||||
$(MAKE) test-arm32
|
||||
$(MAKE) test-riscv64
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $D$(BINDIR)
|
||||
$(INSTALL_PROGRAM) mold $D$(BINDIR)
|
||||
@@ -191,7 +219,6 @@ uninstall:
|
||||
rm -rf $D$(LIBDIR)/mold
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64.mold
|
||||
$(MAKE) -C third-party/xxhash clean
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64.mold mold-*-linux.tar.gz
|
||||
|
||||
.PHONY: all test tests check clean
|
||||
.PHONY: all test tests check clean test-x86-64 test-i386 test-arm64 test-arm32 test-riscv64 test-all
|
||||
@@ -1,10 +1,10 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than LLVM lld linker, the second-fastest
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is created for increasing developer productivity by reducing
|
||||
build time especially in rapid debug-edit-rebuild cycles.
|
||||
mold is designed to increase developer productivity by reducing
|
||||
build time, especially in rapid debug-edit-rebuild cycles.
|
||||
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold for
|
||||
linking final debuginfo-enabled executables of major large programs
|
||||
@@ -22,7 +22,7 @@ mold is so fast that it is only 2x _slower_ than `cp` on the same
|
||||
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
|
||||
if you find mold is not faster than other linkers.
|
||||
|
||||
mold currently supports x86-64, i386 and ARM64.
|
||||
mold currently supports x86-64, i386, ARM32, ARM64 and 64-bit RISC-V.
|
||||
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
@@ -72,7 +72,7 @@ sudo dnf install -y git clang cmake openssl-devel xxhash-devel zlib-devel libstd
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
git checkout v1.0.2
|
||||
git checkout v1.2.0
|
||||
make -j$(nproc) CXX=clang++
|
||||
sudo make install
|
||||
```
|
||||
@@ -83,12 +83,14 @@ try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`.
|
||||
|
||||
If you don't use a recent enough Linux distribution, or if for any reason `make`
|
||||
in the above commands doesn't work for you, you can use Docker to build it in
|
||||
a Docker environment. To do so, just run `./build-static.sh` in this
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`make` in the above commands doesn't work for you, you can use Docker to
|
||||
build it in a Docker environment. To do so, just run `./dist.sh` in this
|
||||
directory instead of running `make -j$(nproc)`. The shell script creates a
|
||||
Ubuntu 20.04 Docker image, installs necessary tools and libraries to it,
|
||||
and builds mold as a statically-linked executable.
|
||||
Ubuntu 18.04 Docker image, installs necessary tools and libraries to it,
|
||||
builds mold and auxiliary files, and packs them into a single tar file
|
||||
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use `mold` executable in it.
|
||||
|
||||
`make test` depends on a few more packages. To install, run the following commands:
|
||||
|
||||
@@ -142,6 +144,7 @@ rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable.
|
||||
Please make sure you have installed `clang`.
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to
|
||||
`~/.cargo/config.toml`.
|
||||
@@ -160,13 +163,6 @@ mold has a feature to intercept all invocations of `ld`, `ld.lld` or
|
||||
mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
Here's an example showing how to link Rust code when using the
|
||||
cargo package manager:
|
||||
|
||||
```shell
|
||||
mold -run cargo build
|
||||
```
|
||||
|
||||
Internally, mold invokes a given command with `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object
|
||||
file intercepts all function calls to `exec(3)`-family functions to
|
||||
@@ -174,6 +170,16 @@ replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Action</summary>
|
||||
If you want to use mold in your GitHub-hosted CI to speed up continuous
|
||||
build, you can use <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action. GitHub runs a CI on a two-core machine, but mold is
|
||||
still significantly faster than the default GNU linker there
|
||||
especially when a program being linked is large.
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
|
||||
mold leaves its identification string in `.comment` section in an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
@@ -187,6 +193,8 @@ String dump of section '.comment':
|
||||
|
||||
If `mold` is in `.comment`, the file is created by mold.
|
||||
|
||||
</details>
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
One reason is because it simply uses faster algorithms and efficient
|
||||
|
||||
+20
-14
@@ -1,31 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() : BigEndian(0) {}
|
||||
|
||||
BigEndian(T x) {
|
||||
*this = x;
|
||||
}
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
// We don't need to optimize this code because compilers are
|
||||
// usually smart enough to compile this loop into a single
|
||||
// byte-swap instruction such as x86's bswap.
|
||||
T ret = 0;
|
||||
for (int i = 0; i < sizeof(T); i++)
|
||||
ret = (ret << 8) | val[i];
|
||||
return ret;
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
for (int i = 0; i < sizeof(T); i++)
|
||||
val[sizeof(T) - 1 - i] = x >> (i * 8);
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -63,6 +60,15 @@ public:
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
if constexpr (sizeof(T) == 2)
|
||||
return __builtin_bswap16(x);
|
||||
else if constexpr (sizeof(T) == 4)
|
||||
return __builtin_bswap32(x);
|
||||
else
|
||||
return __builtin_bswap64(x);
|
||||
}
|
||||
};
|
||||
|
||||
using ibig16 = BigEndian<int16_t>;
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
# This is a shell script to build a statically-linked mold
|
||||
# executable using Docker, so that it is easy to build mold
|
||||
# on non-Ubuntu 20.04 machines.
|
||||
#
|
||||
# A docker image used for building mold is persistent.
|
||||
# Run `docker image rm mold-build-ubuntu20` to remove the image
|
||||
# from disk.
|
||||
|
||||
set -e
|
||||
|
||||
# If the existing file is not statically-linked, remove it.
|
||||
[ -f mold ] && ! ldd mold 2>&1 | grep -q 'not a dynamic executable' && \
|
||||
rm mold
|
||||
|
||||
cat <<EOF | docker build -t mold-build-ubuntu20 -
|
||||
FROM ubuntu:20.04
|
||||
RUN apt-get update && \
|
||||
TZ=Europe/London apt-get install -y tzdata && \
|
||||
apt-get install -y --no-install-recommends build-essential clang lld \
|
||||
bsdmainutils file gcc-multilib git pkg-config \
|
||||
cmake libstdc++-10-dev zlib1g-dev libssl-dev && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
EXTRA_LDFLAGS='-fuse-ld=lld -static'
|
||||
|
||||
# libstdc++'s `std::__glibcxx_rwlock_rdlock` refers these symbols
|
||||
# as weak symbols, although they need to be defined. Otherwise,
|
||||
# the program crashes after juping to address 0.
|
||||
# So, we force loading symbols as a workaround.
|
||||
EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-u,pthread_rwlock_rdlock"
|
||||
EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-u,pthread_rwlock_unlock"
|
||||
EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-u,pthread_rwlock_wrlock"
|
||||
|
||||
docker_args=(-v "`pwd`:/mold:Z" -u "$(id -u)":"$(id -g)")
|
||||
if docker --version | grep -q podman; then
|
||||
docker_args+=(--userns=keep-id)
|
||||
fi
|
||||
|
||||
docker run -it --rm "${docker_args[@]}" \
|
||||
mold-build-ubuntu20 \
|
||||
make -C /mold -j"$(nproc)" CC=clang CXX=clang++ \
|
||||
EXTRA_LDFLAGS="$EXTRA_LDFLAGS" "$@"
|
||||
+32
-6
@@ -17,6 +17,8 @@
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#define CHECK(fn) do { int r = (fn); assert(r == Z_OK); } while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
@@ -35,13 +37,14 @@ static std::vector<std::string_view> split(std::string_view input) {
|
||||
|
||||
static std::vector<u8> do_compress(std::string_view input) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well, we chose compression level 3.
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
int r = deflateInit2(&strm, 3, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
|
||||
assert(r == Z_OK);
|
||||
|
||||
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
@@ -51,13 +54,36 @@ static std::vector<u8> do_compress(std::string_view input) {
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits can be held to be written to the
|
||||
// buffer.
|
||||
strm.avail_out = buf.size();
|
||||
strm.next_out = buf.data();
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three bit value indicating the start of an uncompressed data
|
||||
// block followed by four byte data 00 00 ff ff which indicates that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug that if that particular three
|
||||
// bit value happens to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
int nbits;
|
||||
deflatePending(&strm, Z_NULL, &nbits);
|
||||
if (nbits == 5)
|
||||
CHECK(deflatePrime(&strm, 10, 2));
|
||||
CHECK(deflate(&strm, Z_SYNC_FLUSH));
|
||||
|
||||
r = deflate(&strm, Z_SYNC_FLUSH);
|
||||
assert(r == Z_OK);
|
||||
assert(strm.avail_out > 0);
|
||||
|
||||
buf.resize(buf.size() - strm.avail_out);
|
||||
deflateEnd(&strm);
|
||||
return buf;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash -x
|
||||
# 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.
|
||||
|
||||
set -e
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
arch=$(uname -m)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
if [ $arch = x86_64 ]; then
|
||||
image=rui314/mold-builder:v1-x86_64
|
||||
elif [ $arch = aarch64 -o $arch = arm64 ]; then
|
||||
image=rui314/mold-builder:v1-aarch64
|
||||
else
|
||||
echo "Error: no docker image for $arch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker images -q $image 2> /dev/null || docker pull $image
|
||||
|
||||
docker run -it --rm -v "$(pwd):/mold:Z" -u "$(id -u):$(id -g)" $image \
|
||||
bash -c "cp -r /mold /tmp/mold &&
|
||||
cd /tmp/mold &&
|
||||
make clean &&
|
||||
make -j\$(nproc) CXX=clang++-14 CXXFLAGS='-I/openssl/include -O2 $CXXFLAGS' LDFLAGS='-static-libstdc++ /openssl/libcrypto.a' NEEDS_LIBCRYPTO=0 LTO=${LTO:-0} &&
|
||||
make install PREFIX=/ DESTDIR=$dest &&
|
||||
ln -sfr $dest/bin/mold $dest/libexec/mold/ld &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold /mold"
|
||||
@@ -0,0 +1,46 @@
|
||||
# Coding Guidelines
|
||||
|
||||
mold is written in C++20, but as is the case with every C++ project,
|
||||
it has local coding rules. In this document, I'll explain some of them
|
||||
and try to give justifications for why I chose such rules.
|
||||
|
||||
## DOs
|
||||
|
||||
- Always use `i64` (which is a type alias for `int64_t` in mold) for
|
||||
integers unless you have a reason not to. For example, even if you know
|
||||
that a loop counter won't exceed 100, you should stop thinking about it
|
||||
and just use `i64`.
|
||||
|
||||
Justification: Local variables are usually on CPU registers, so on
|
||||
64-bit CPUs, there's no performance peanlty on choosing `i64` over
|
||||
`i32`. Even if a compiler has to spill register values to the stack,
|
||||
I don't think there's an observable difference between `i32` and
|
||||
`i64`. Therefore, extra 32 bits are essentially free. On 32-bit CPUs,
|
||||
they are not free, but that's OK because we are writing mold for modern
|
||||
computers. mold will still run on 32-bit computers but a bit slowly.
|
||||
|
||||
By always using `i64`, we can eliminate the need to think about the
|
||||
"right" size for each variable. It also reduces the risk of integer
|
||||
overflow.
|
||||
|
||||
Exceptions: If you have to allocate a very large number (e.g. millions)
|
||||
of the same object, its size matters. In that case, use a smaller type.
|
||||
|
||||
## DON'Ts
|
||||
|
||||
- Don't use `auto` unless its actual type is obvious in the very narrow
|
||||
context. Currently, we use `auto` only for lambdas.
|
||||
|
||||
Justification: I think `auto` makes code writing easier but code reading
|
||||
harder, because readers have to make a guess as to what is the actual
|
||||
type of `auto`. If you are already familiar with the existing codebase,
|
||||
you may be able to guess it easily, but that's not always the case.
|
||||
I want to keep the mold codebase friendly to first-time readers.
|
||||
|
||||
- Don't over-use inheritance. In mold, most classes don't have parents,
|
||||
and even if they do, their class hierarchy is very shallow. Currently
|
||||
its height is just two (i.e. abstract classes and their implementations).
|
||||
|
||||
Justification: Designing class hierarchies is fun as it feels like
|
||||
taxonomy, but I don't think that always help writing code. It looks like
|
||||
simpler class hierarchy makes its code simpler.
|
||||
@@ -0,0 +1,55 @@
|
||||
This page explains the following warning message and how to fix it.
|
||||
mold emits this message when it sees an object file that may not be
|
||||
compatible with mold.
|
||||
|
||||
|
||||
```
|
||||
mold: warning: foo.o: this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.
|
||||
```
|
||||
|
||||
# Background
|
||||
|
||||
On modern computers, the stack area (to which local variables are
|
||||
stored) cannot contain executable code. If the control reaches the
|
||||
stack area, the CPU refuses to execute any code there and the program
|
||||
is usually terminated due to segmentation fault.
|
||||
|
||||
This is a security measure. The stack area used to be executable (old
|
||||
CPUs generally execute any code as long as it is in a readable memory
|
||||
region), but that provided an easy attack vector to a malicious user.
|
||||
They wrote executable code to the stack area using some buffer
|
||||
overflow bug and jumped there to run arbitrary code in a remote server
|
||||
process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respect that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
GCC had (and still have) a feature that depends on the executable
|
||||
stack, so they invented a way to tell the linker to mark the stack
|
||||
executable. Specifically, if an object file contains a
|
||||
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
|
||||
silently makes the stack of an output file executable.
|
||||
|
||||
But the GNU linker's behavior is dangerous. If you accidentally link
|
||||
an object file that has that marker section, the entire stack area
|
||||
silently becomes executable, disabling the security mechanism.
|
||||
|
||||
Therefore, mold simply ignores that marker section. If you are using
|
||||
mold, you need to explicitly pass `-z execstack` to the linker to make
|
||||
the stack executable.
|
||||
|
||||
# What caused this issue?
|
||||
|
||||
You are likely to use GCC's [Nested
|
||||
Functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)
|
||||
feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beaware
|
||||
that this will significantly weaken your program's security.
|
||||
|
||||
If you don't want to pass `-z execstack`, rewrite your code so that
|
||||
your code does not depends on the executable stack.
|
||||
@@ -0,0 +1,158 @@
|
||||
The very concept of linking is simple: a compiler compiles a piece of
|
||||
source code into an object file (a file containing machine code), and
|
||||
a linker combines object files into a single executable or a shared
|
||||
library file. However, the actual implementation of the linker for
|
||||
modern systems is much more complicated because hardware, operating
|
||||
system, compiler and linker all have many more features.
|
||||
|
||||
In this file, I'll explain random topics in the glossary format that
|
||||
you need to understand to read mold code.
|
||||
|
||||
## DSO
|
||||
|
||||
A .so file. Short for Dynamic Shared Object. Often called as a
|
||||
shared library, a dynamic libray or a shared object as well.
|
||||
|
||||
An DSO contains common functions and data that are used by multiple
|
||||
executables and/or other DSOs. At runtime, a DSO is loaded to a
|
||||
contiguous region in the virtual address.
|
||||
|
||||
## Object file
|
||||
|
||||
A .o file. An object file contains machine code and data, but it
|
||||
cannot be executed because it's not self-contained. For example,
|
||||
if you compile a C source file containing a call of `printf`,
|
||||
the actual function code of `printf` is not included in the resulting
|
||||
object file. You include `stdio.h`, but that teaches the compiler
|
||||
only about `printf`'s type, and the compiler still don't know what
|
||||
`printf` actually does. Therefore, it cannot emit code for `printf`.
|
||||
|
||||
You need to link an object file with other object file or a shared
|
||||
library to make it exectuable.
|
||||
|
||||
## Virtual address space
|
||||
|
||||
A pointer has a value like 0x803020 which is an address of the
|
||||
pointee. But it doesn't mean that the pointee resides at the
|
||||
physical memory address 0x803020 on the computer. Modern CPUs
|
||||
contains so-called Mmeory Management Unit (MMU), and all access to
|
||||
the memory are first translated by MMU to the physical address.
|
||||
The address before translation is called the "virtual address".
|
||||
Unless you are doing the kernel programming, all addresses you
|
||||
handle are virtual addresses.
|
||||
|
||||
The OS kernel controls the MMU so that each process owns the entire
|
||||
virtual address space. So, even if two process uses the same virtual
|
||||
address, they don't conflict. They are mapped to different physical
|
||||
addresses.
|
||||
|
||||
The existence of MMU has several implications to the linker. First,
|
||||
we can link the main executable to a specific address. On process
|
||||
startup, there's no code or data in the virtual address space, so
|
||||
the mapping of the main executable always succeed. However, it's not
|
||||
true to DSOs because they are loaded after the main executable and
|
||||
possibly other DSOs. Therefore, shared libraries must be linked in a
|
||||
way that they can be loaded to any address in the virtual address
|
||||
space.
|
||||
|
||||
## Relocation
|
||||
|
||||
A piece of information for the linker as to how to link object files
|
||||
or a dynamic objects.
|
||||
|
||||
Object files can refer functions or data in other object files. For
|
||||
example, if you compile a function which calls a non-local function
|
||||
`foo`, the resulting code contains something like this:
|
||||
|
||||
```
|
||||
26: e8 00 00 00 00 callq 2b <bar+0xb>
|
||||
27: R_X86_64_PLT32 foo-0x4
|
||||
```
|
||||
|
||||
The above `callq` is the instruction to call a function at the
|
||||
machine code level. It's opcode is `0xe8` in x86-64, so the
|
||||
instruction begins with `0xe8`. The following four bytes are
|
||||
displacement; that is, the address of the branch target relative to
|
||||
the end of this `callq` instruction. Notice that the displacement is
|
||||
0. The compiler couldn't fill the displacement because it has no
|
||||
idea as to where `foo` will be at runtime. So, the compiler write 0
|
||||
as a placeholder and instead write a relocation `R_X86_64_PLT32`
|
||||
with `foo` as its associated symbol. The linker reads this
|
||||
relocation, computes the offsets between this call instruction and
|
||||
function `foo` and overwrite the placeholder value 0 with an actual
|
||||
displacement.
|
||||
|
||||
There are many different types of relocations. For example, if you
|
||||
want to fix up not with a displacement but with an absolute address
|
||||
of a symbol, you need to use `R_X86_64_ABS64` instead.
|
||||
|
||||
## Static library
|
||||
|
||||
A .a file. Often called as an archive file or just archive as well.
|
||||
|
||||
A static library is a container just like tar or zip. Actually,
|
||||
there's no technical reason to not use tar or (uncompressed) zip,
|
||||
but traditionally the .a file format is used by the linker.
|
||||
|
||||
A static library contains object files and can be passed to the
|
||||
linker along with other object files and/or archives.
|
||||
|
||||
A linker pulls out object files from an archive only if it is needed
|
||||
to resolve undefined symbols. In other words, object files in an
|
||||
archive are not linked by default and used as a complement to supply
|
||||
missing definitions. This is ideal for a library because you don't
|
||||
want to link library functions unless you are actually using them.
|
||||
|
||||
Contrary to archive files, object files directly given to a linker
|
||||
are always linked to the output.
|
||||
|
||||
To maximize the benefit of archive files, a library often used as a
|
||||
static library is broken down to small files to separate each
|
||||
function individually (for example, look at
|
||||
https://git.musl-libc.org/cgit/musl/tree/src/stdio). By doing this,
|
||||
you import only used functions.
|
||||
|
||||
A static file is created by `ar`, whose command line arguments are
|
||||
similar to `tar`. A static library contains the symbol table which
|
||||
offers a quick way to look up an object file for a defined symbol,
|
||||
but mold does not use the static library's symbol table. mold
|
||||
doesdn't need a symbol table to exist in an archive, and if exists,
|
||||
mold just ignores it.
|
||||
|
||||
See also: DSO (dynamic library)
|
||||
|
||||
## Symbol
|
||||
|
||||
A symbol is a label assigned to a specific location in an input file
|
||||
or an output file. For example, if you define function `foo` and
|
||||
compile it, the resulting object file contains a symbol `foo`
|
||||
pointing to the beginning of the machine code for `foo`.
|
||||
|
||||
Usually, a symbol name is a function or a variable name. If an
|
||||
object is anonymous (such the one for a string literal), a compiler
|
||||
generated a unique symbol, which often starts with `.` to avoid
|
||||
conflict with user-defined symbols.
|
||||
|
||||
For C++, symbol name is a complex "mangled" name. We need to mangle
|
||||
identifiers because a simple name such as `foo` cannot be uniquely
|
||||
identify a function or a data in C++, because for example `foo` may
|
||||
be in a namespace or defined as a static member in some class. If
|
||||
`foo` is an overloaded function, we need to distinguish different
|
||||
`foo`s by its type. Therefore, C++ compiler mangles an identifier by
|
||||
appending nmaepsace names, type information and such so that
|
||||
different things get different names.
|
||||
|
||||
For example, a function `int foo(int)` in a namespace `bar` is
|
||||
mangled as `_ZN3bar3fooEi`.
|
||||
|
||||
A symbol can be either defined or undefined. A defined symbol points
|
||||
to some location in a file which may contain the function's machine
|
||||
code or the variable's initial value. An undefined symbol does not
|
||||
point to anywhere. It needs to be merged with a defined symbol with
|
||||
the same name at link-time. This merging process is called "name
|
||||
resolution".
|
||||
|
||||
For example, if your program is using `printf`, it usually contains
|
||||
`printf` as an undefined symbol. You need to link it with `libc.a`
|
||||
or `libc.so`, which contain a defined symbol of `printf`, to make a
|
||||
complete program.
|
||||
+279
-139
@@ -230,22 +230,23 @@ depends only on its command-line arguments.
|
||||
.
|
||||
.\"=============================================================================
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width 6n
|
||||
.Bl -tag -width 6n -compact
|
||||
.It Fl -help
|
||||
Report usage information to stdout and exit.
|
||||
.
|
||||
.Pp
|
||||
.It Fl v , Fl -version
|
||||
Report version information to stdout.
|
||||
.
|
||||
.Pp
|
||||
.It Fl V
|
||||
Report version and target information to stdout.
|
||||
.
|
||||
.Pp
|
||||
.It Fl C Ar dir , Fl -directory Ar dir
|
||||
Change to
|
||||
.Ar dir
|
||||
before doing anything.
|
||||
.
|
||||
.It Fl E , Fl -export-dynamic , Fl -no-export-dynamic
|
||||
.Pp
|
||||
.It Fl E , Fl -export-dynamic
|
||||
.It Fl -no-export-dynamic
|
||||
When creating an executable, using the
|
||||
.Fl E
|
||||
option causes all global symbols to be put into the dynamic symbol table,
|
||||
@@ -255,14 +256,15 @@ By default, or if
|
||||
.Fl -no-export-dynamic
|
||||
is given, only symbols
|
||||
that are referenced by DSOs at link-time are exported from an executable.
|
||||
.
|
||||
.Pp
|
||||
.It Fl F Ar libname , Fl -filter Ns = Ns Ar libname
|
||||
Set the
|
||||
.Dv DT_FILTER
|
||||
dynamic section field to
|
||||
.Ar libname .
|
||||
.
|
||||
.It Fl I Ns Ar file , Fl -dynamic-linker Ns = Ns Ar file , Fl -no-dynamic-linker
|
||||
.Pp
|
||||
.It Fl I Ns Ar file , Fl -dynamic-linker Ns = Ns Ar file
|
||||
.It Fl -no-dynamic-linker
|
||||
Set the dynamic linker path to
|
||||
.Ar file .
|
||||
If no
|
||||
@@ -276,7 +278,7 @@ However, this difference doesn't usually make any difference because the \
|
||||
compiler driver always passes
|
||||
.Fl I
|
||||
to a linker.
|
||||
.
|
||||
.Pp
|
||||
.It Fl L Ns Ar dir , Fl -library-path Ns = Ns Ar dir
|
||||
Add
|
||||
.Ar dir
|
||||
@@ -289,26 +291,27 @@ Unlike the GNU linkers,
|
||||
does not have the default search paths.
|
||||
This difference doesn't usually make any difference because the
|
||||
compiler driver always passes all necessary search paths to a linker.
|
||||
.
|
||||
.Pp
|
||||
.It Fl M , Fl -print-map
|
||||
Write a map file to stdout.
|
||||
.
|
||||
.It Fl N , Fl -omagic , Fl -no-omagic
|
||||
.Pp
|
||||
.It Fl N , Fl -omagic
|
||||
.It Fl -no-omagic
|
||||
Force
|
||||
.Nm
|
||||
to emit an output file with an old-fashioned memory layout.
|
||||
First, it makes the first data segment to not be aligned to a page boundary.
|
||||
Second, text segments are marked as writable if the option is given.
|
||||
.
|
||||
.Pp
|
||||
.It Fl S , Fl -strip-debug
|
||||
Omit
|
||||
.Li \.debug_*
|
||||
sections from the output file.
|
||||
.
|
||||
.Pp
|
||||
.It Fl T Ar file , Fl -script Ns = Ns Ar file
|
||||
Read linker script from
|
||||
.Ar file .
|
||||
.
|
||||
.Pp
|
||||
.It Fl X , Fl -discard-locals
|
||||
Discard temporary local symbols to reduce the sizes of the \
|
||||
symbol table and the string table.
|
||||
@@ -316,58 +319,58 @@ Temporary local symbols are local symbols starting with
|
||||
.Li \.L .
|
||||
Compilers usually generate such symbols for unnamed program elements such as \
|
||||
string literals or floating-point literals.
|
||||
.
|
||||
.Pp
|
||||
.It Fl e Ar symbol , Fl -entry Ns = Ns Ar symbol
|
||||
Use
|
||||
.Ar symbol
|
||||
as the entry point symbol instead of the default
|
||||
entry point symbol
|
||||
.Sy _start .
|
||||
.
|
||||
.Pp
|
||||
.It Fl f Ar shlib , Fl -auxiliary Ns = Ns Ar shlib
|
||||
Set the
|
||||
.Dv DT_AUXILIARY
|
||||
dynamic section field to
|
||||
.Ar shlib .
|
||||
.
|
||||
.It Fl h Ar libname , Fl -soname Ns Ar libname
|
||||
.Pp
|
||||
.It Fl h Ar libname , Fl -soname Ns = Ns Ar libname
|
||||
Set the
|
||||
.Dv DT_SONAME
|
||||
dynamic section field to
|
||||
.Ar libname .
|
||||
This option is used when creating a shared object file.
|
||||
Typically, when you create
|
||||
.Pf Sy lib Ar foo Ns Sy .so ,
|
||||
.Pf Sy XXX lib Ar foo Ns Sy .so ,
|
||||
you want to pass
|
||||
.Fl -soname Ns = Ns Ar foo
|
||||
to a linker.
|
||||
.
|
||||
.Pp
|
||||
.It Fl l Ns Ar libname
|
||||
Search for
|
||||
.Pf Sy lib Ar libname Ns Sy \.so
|
||||
or
|
||||
.Pf Sy lib Ar libname Ns Sy \.a
|
||||
from library search paths.
|
||||
.
|
||||
.It Fl m Op Sy elf_x86_64 | elf_i386
|
||||
.Pp
|
||||
.It Fl m Op Sy elf_x86_64 | elf_i386 | aarch64linux
|
||||
Choose a target.
|
||||
.
|
||||
.Pp
|
||||
.It Fl o Ar file , Fl -output Ns = Ns Ar file
|
||||
Use
|
||||
.Ar file
|
||||
as the output file name instead of the default name
|
||||
.Sy a.out .
|
||||
.
|
||||
.Pp
|
||||
.It Fl r , Fl -relocatable
|
||||
Instead of generating an executable or a shared object file, combine
|
||||
input object files to generate another object file that can be used as
|
||||
an input to a linker.
|
||||
.
|
||||
.It Fl s , FL -strip-all
|
||||
.Pp
|
||||
.It Fl s , Fl -strip-all
|
||||
Omit
|
||||
.Li \.symtab
|
||||
section from the output file.
|
||||
.
|
||||
.Pp
|
||||
.It Fl u Ar symbol , Fl -undefined Ns = Ns Ar symbol
|
||||
If
|
||||
.Ar symbol
|
||||
@@ -377,13 +380,13 @@ and if there is an static archive that contains an object file defining
|
||||
pull out the object file and link it so that the \
|
||||
output file contains a definition of
|
||||
.Ar symbol .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -Bdynamic
|
||||
Link against shared libraries.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -Bstatic
|
||||
Do not link against shared libraries.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -Bsymbolic
|
||||
When creating a shared library, make global symbols export-only
|
||||
(i.e. do not import the same symbol).
|
||||
@@ -392,30 +395,43 @@ negating symbol override at runtime.
|
||||
See
|
||||
.Sx Dynamic symbol resolution
|
||||
for more information about symbol imports and exports.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -Bsymbolic-functions
|
||||
Have the same effect as
|
||||
.Fl -Bsymbolic
|
||||
but works only for function symbols.
|
||||
Data symbols remains both imported and exported.
|
||||
.
|
||||
Data symbols remains being both imported and exported.
|
||||
.Pp
|
||||
.It Fl -Bno-symbolic
|
||||
Cancel
|
||||
.Fl -Bsymbolic
|
||||
and
|
||||
.Fl -Bsymbolic-functions .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -Map Ns = Ns Ar file
|
||||
Write map file to
|
||||
.Ar file .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -Tbss Ns = Ns Ar address
|
||||
Alias for
|
||||
.Fl -section-start=.bss Ns = Ns Ar address .
|
||||
.Pp
|
||||
.It Fl -Tdata Ns = Ns Ar address
|
||||
Alias for
|
||||
.Fl -section-start=.data Ns = Ns Ar address .
|
||||
.Pp
|
||||
.It Fl -Ttext Ns = Ns Ar address
|
||||
Alias for
|
||||
.Fl -section-start=.text Ns = Ns Ar address .
|
||||
.Pp
|
||||
.It Fl -allow-multiple-definition
|
||||
Normally, the linker reports an error if there are more than one \
|
||||
definition of a symbol.
|
||||
This option changes the default behavior so that it doesn't report an error \
|
||||
for duplicate definitions and instead use the first definition.
|
||||
.
|
||||
.It Fl -as-needed , -no-as-needed
|
||||
.Pp
|
||||
.It Fl -as-needed
|
||||
.It Fl -no-as-needed
|
||||
By default, shared libraries given to a linker are unconditionally added to \
|
||||
the list of required libraries in an output file.
|
||||
However, shared libraries after
|
||||
@@ -424,13 +440,15 @@ are added to the list only when at least one symbol is actually used by an \
|
||||
object file.
|
||||
In other words, shared libraries after
|
||||
.Fl -as-needed
|
||||
are not added to the list if they are not needed by a program.
|
||||
are not added to the list of needed libraries if they are not needed by a program.
|
||||
.Pp
|
||||
The
|
||||
.Fl -no-as-needed
|
||||
option restores the default behavior for subsequent files.
|
||||
.
|
||||
.It Fl -build-id , Fl -no-build-id , Fl -build-id Ns = Ns Op Sy none | md5 | sha1 | sha256 | uuid | 0x Ns Ar hexstring
|
||||
.Pp
|
||||
.It Fl -build-id
|
||||
.It Fl -build-id Ns = Ns Op Sy none | md5 | sha1 | sha256 | uuid | 0x Ns Ar hexstring
|
||||
.It Fl -no-build-id
|
||||
Create a
|
||||
.Li .note.gnu.build-id
|
||||
section containing a byte string to
|
||||
@@ -449,17 +467,17 @@ sets a random 128-bit UUID.
|
||||
.Sy 0x Ns Ar hexstring
|
||||
sets
|
||||
.Ar hexstring .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -chroot Ns = Ns Ar dir
|
||||
Set
|
||||
.Ar dir
|
||||
to root directory.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -compress-debug-sections Ns = Ns Op Sy none | zlib | zlib-gabi | zlib-gnu
|
||||
Compress DWARF debug info
|
||||
.Pf ( Sy .debug_*
|
||||
sections) using the zlib compression algorithm.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -defsym Ns = Ns Ar symbol Ns = Ns Ar value
|
||||
Define
|
||||
.Ar symbol
|
||||
@@ -474,32 +492,76 @@ prefix) or a symbol name.
|
||||
If an integer is given as a value,
|
||||
.Ar symbol
|
||||
is defined as an absolute symbol with the given value.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -default-symver
|
||||
Use soname as a symbol version and append that version to all symbols.
|
||||
.It Fl -demangle , -no-demangle
|
||||
.Pp
|
||||
.It Fl -demangle
|
||||
.It Fl -no-demangle
|
||||
Demangle C++ symbols in log messages.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -dependency-file Ns = Ns Ar file
|
||||
Write a dependency file to
|
||||
.Ar file .
|
||||
The contents of the written file is readable by
|
||||
.Cm make ,
|
||||
which defines only one rule with the linker's output file as a target \
|
||||
and all input fiels as its prerequisite. Users are expected to include \
|
||||
the generated dependency file into a Makefile to automate the \
|
||||
dependency management. This option is analogous to the compiler's
|
||||
.Fl MM Fl MF
|
||||
options.
|
||||
.Pp
|
||||
.It Fl -dynamic-list Ns = Ns Ar file
|
||||
Read a list of dynamic symbols from
|
||||
.Ar file .
|
||||
.
|
||||
.It Fl -eh-frame-hdr , -no-eh-frame-hdr
|
||||
.Pp
|
||||
.It Fl -eh-frame-hdr
|
||||
.It Fl -no-eh-frame-hdr
|
||||
Create
|
||||
.Li .eh_frame_hdr
|
||||
section.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -emit-relocs
|
||||
A linker usually "consumes" relocation sections. That is, a linker \
|
||||
applies relocations to other sections, and relocation sections themselves \
|
||||
are discarded.
|
||||
.Pp
|
||||
The
|
||||
.Fl -emit-relocs
|
||||
instructs the linker to leave relocation sections in the output file. \
|
||||
Some post-link binary analysis or optimization tools such as LLVM Bolt \
|
||||
need them.
|
||||
.Pp
|
||||
.Nm
|
||||
always creates RELA-type relocation sections even if the native \
|
||||
ELF format is REL-type so that it is easy to read addends.
|
||||
.Pp
|
||||
.It Fl -enable-new-dtags
|
||||
.It Fl -disable-new-dtags
|
||||
By default,
|
||||
.Nm
|
||||
emits DT_RUNPATH for
|
||||
.Fl -rpath .
|
||||
If you pass
|
||||
.Fl -disable-new-dtags,
|
||||
mold emits DT_RPATH for
|
||||
.Fl -rpath
|
||||
instead.
|
||||
.Pp
|
||||
.Pp
|
||||
.It Fl -exclude-libs Ns = Ns Ar libraries Ns ...
|
||||
Mark all symbols in the given
|
||||
.Ar libraries
|
||||
hidden.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -fini Ns = Ns Ar symbol
|
||||
Call
|
||||
.Ar symbol
|
||||
at unload-time.
|
||||
.
|
||||
.It Fl -fork , -no-fork
|
||||
.Pp
|
||||
.It Fl -fork
|
||||
.It Fl -no-fork
|
||||
Spawn a child process and let it do the actual linking.
|
||||
When linking a large program, the OS kernel can take a few hundred \
|
||||
milliseconds to terminate a
|
||||
@@ -507,29 +569,34 @@ milliseconds to terminate a
|
||||
process.
|
||||
.Fl -fork
|
||||
hides that latency.
|
||||
.
|
||||
.It Fl -gc-sections , -no-gc-sections
|
||||
.Pp
|
||||
.It Fl -gc-sections
|
||||
.It Fl -no-gc-sections
|
||||
Remove unreferenced sections.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -hash-style Ns = Ns Op Sy sysv | gnu | both
|
||||
Set hash style.
|
||||
.
|
||||
.It Fl -icf Ns = Ns Sy all , Fl -no-icf
|
||||
.Pp
|
||||
.It Fl -icf Ns = Ns Sy all
|
||||
.It Fl -no-icf
|
||||
Fold identical code.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -image-base Ns = Ns Ar addr
|
||||
Set the base address to
|
||||
.Ar addr .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -init Ns = Ns Ar symbol
|
||||
Call
|
||||
.Ar symbol
|
||||
at load-time.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -no-undefined
|
||||
Report undefined symbols (even with
|
||||
.Fl -shared ) .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -noinhibit-exec
|
||||
Create an output file even if errors occur.
|
||||
.Pp
|
||||
.It Fl -pack-dyn-relocs Ns = Ns Op Sy none | relr
|
||||
If
|
||||
.Sy relr
|
||||
@@ -543,55 +610,80 @@ or
|
||||
.Li .rela.dyn
|
||||
section. Since
|
||||
.Li .relr.dyn
|
||||
section uses a space-efficient encoding scheme, specifying that flag \
|
||||
section uses a space-efficient encoding scheme, specifying this flag \
|
||||
can reduce the size of the output. This is typically most effective \
|
||||
for position-independent executable. Note that a runtime loader has to \
|
||||
support
|
||||
for position-independent executable.
|
||||
.Pp
|
||||
Note that a runtime loader has to support
|
||||
.Li .relr.dyn
|
||||
to run executables or shared libraries linked with
|
||||
.Fl -pack-dyn-relocs=relr ,
|
||||
and only ChromeOS, Android and Fuchsia support it as of now in 2022.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -perf
|
||||
Print performance statistics.
|
||||
.
|
||||
.It Fl -pie , -pic-executable , -no-pie , -no-pic-executable
|
||||
.Pp
|
||||
.It Fl -pie , -pic-executable
|
||||
.It Fl -no-pie , -no-pic-executable
|
||||
Create a position-independent executable.
|
||||
.
|
||||
.It Fl -pop-state
|
||||
Pop state of flags governing input file handling.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -preload
|
||||
Preload object files.
|
||||
.
|
||||
.It Fl -print-gc-sections , -no-print-gc-sections
|
||||
.Pp
|
||||
.It Fl -print-gc-sections
|
||||
.It Fl -no-print-gc-sections
|
||||
Print removed unreferenced sections.
|
||||
.
|
||||
.It Fl -print-icf-sections , -no-print-icf-sections
|
||||
.Pp
|
||||
.It Fl -print-icf-sections
|
||||
.It Fl -no-print-icf-sections
|
||||
Print folded identical sections.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -push-state
|
||||
Pop state of flags governing input file handling
|
||||
.
|
||||
.It Fl -quick-exit , -no-quick-exit
|
||||
.It Fl -pop-state
|
||||
.Fl -push-state
|
||||
saves the current values of
|
||||
.Fl -as-needed ,
|
||||
.Fl -whole-archive ,
|
||||
.Fl -static ,
|
||||
and
|
||||
.Fl -start-lib .
|
||||
The saved values can be restored by
|
||||
.Fl -pop-state .
|
||||
.Pp
|
||||
.Fl -push-state
|
||||
and
|
||||
.Fl -pop-state
|
||||
pairs can nest.
|
||||
.Pp
|
||||
These options are useful when you want to construct linker command line \
|
||||
options programmatically. For example, if you want to link
|
||||
.Ar libfoo.so
|
||||
by as-needed basis but don't want to change the global state of
|
||||
.Fl -as-needed ,
|
||||
you can append "--push-state --as-needed -lfoo --pop-state" to the \
|
||||
linker command line options.
|
||||
.Pp
|
||||
.It Fl -quick-exit
|
||||
.It Fl -no-quick-exit
|
||||
Use
|
||||
.Dv quick_exit
|
||||
to exit.
|
||||
.
|
||||
.It Fl -relax , -no-relax
|
||||
.Pp
|
||||
.It Fl -relax
|
||||
.It Fl -no-relax
|
||||
Rewrite machine instructions with more efficient ones for some relocations.
|
||||
The feature is enabled by default.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -require-defined Ns = Ns Ar symbol
|
||||
Like
|
||||
.Fl -undefined ,
|
||||
except the new symbol must be defined by the end of the link.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -repro
|
||||
Embed input files into
|
||||
.Dv .repro
|
||||
section.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -retain-symbols-file Ns = Ns Ar file
|
||||
Keep only symbols listed in
|
||||
.Ar file .
|
||||
@@ -608,12 +700,16 @@ Note that this option removes symbols only from
|
||||
section and does not affect
|
||||
.Dv .dynsym
|
||||
section, which is used for dynamic linking.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -reverse-sections
|
||||
Reverses the order of input sections before assigning them the offsets \
|
||||
in the output file.
|
||||
.Pp
|
||||
.It Fl -rpath Ns = Ns Ar dir
|
||||
Add
|
||||
.Ar dir
|
||||
to runtime search path.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -run Cm command Ar arg Ar
|
||||
Run
|
||||
.Cm command
|
||||
@@ -621,33 +717,64 @@ with
|
||||
.Nm
|
||||
as
|
||||
.Pa /usr/bin/ld .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -section-start Ns = Ns Ar section Ns = Ns Ar address
|
||||
Set
|
||||
.Ar address
|
||||
to
|
||||
.Ar section .
|
||||
.Ar address
|
||||
is a hexadecimal number that may start with an optional
|
||||
.Sq 0x .
|
||||
.Pp
|
||||
.It Fl -shared , -Bshareable
|
||||
Create a share library.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -shuffle-sections
|
||||
.It Fl -shuffle-sections Ns = Ns Ar number
|
||||
Randomizes the output by shuffleing the order of input sections before \
|
||||
assigning them the offsets in the output file. If
|
||||
.Ar number
|
||||
is given, it's used as a seed for the random number generator, so that \
|
||||
the linker produces the same output as for the same seed. If no seed \
|
||||
is given, it uses a random number as a seed.
|
||||
.Pp
|
||||
.It Fl -spare-dynamic-tags Ns = Ns Ar number
|
||||
Reserve given
|
||||
.Ar number
|
||||
of tags in
|
||||
.Dv .dynamic
|
||||
section.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -start-lib
|
||||
.It Fl -end-lib
|
||||
Handle object files between
|
||||
.Fl -start-lib
|
||||
and
|
||||
.Fl -end-lib
|
||||
as if they were in an archive file. That means object files between them \
|
||||
are linked only when they are needed to resolve undefined symbols. \
|
||||
The options are useful if you want to link object files only when they are \
|
||||
needed but want to avoid the overhead of running
|
||||
.Xr ar 3 .
|
||||
.Pp
|
||||
.It Fl -static
|
||||
Do not link against shared libraries.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -stats
|
||||
Print input statistics.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -sysroot Ns = Ns Ar dir
|
||||
Set target system root directory to
|
||||
.Ar dir .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -thread-count Ns = Ns Ar count
|
||||
Use
|
||||
.Ar count
|
||||
number of threads.
|
||||
.
|
||||
.It Fl -threads , -no-threads
|
||||
.Pp
|
||||
.It Fl -threads
|
||||
.It Fl -no-threads
|
||||
Use multiple threads.
|
||||
By default,
|
||||
.Nm
|
||||
@@ -659,34 +786,40 @@ To use only one thread, pass
|
||||
.Fl -no-threads
|
||||
or
|
||||
.Fl -thread-count Ns = Ns Sy 1 .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -trace
|
||||
Print name of each input file.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -unique Ns = Ns Ar pattern
|
||||
Don't merge input sections that match
|
||||
.Ar pattern .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -unresolved-symbols Ns = Ns Op Sy \
|
||||
report-all | ignore-all | ignore-in-object-files | ignore-in-shared-libs
|
||||
How to handle undefined symbols.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -version-script Ns = Ns Ar file
|
||||
Read version script from
|
||||
.Ar file .
|
||||
.
|
||||
.Pp
|
||||
.It Fl -warn-common
|
||||
.It Fl -no-warn-common
|
||||
Warn about common symbols.
|
||||
.
|
||||
.It Fl -warn-unresolved-symbols , -error-unresolved-symbols
|
||||
.Pp
|
||||
.It Fl -warn-once
|
||||
Only warn once for each undefined symbol instead of warn for each relocation
|
||||
referring an undefined symbol.
|
||||
.Pp
|
||||
.It Fl -warn-unresolved-symbols
|
||||
.It Fl -error-unresolved-symbols
|
||||
Normally, the linker reports an error for unresolved symbols.
|
||||
.Fl -warn-unresolved-symbols
|
||||
option turns it into a warning.
|
||||
.Fl -error-unresolved-symbols
|
||||
option restores the default behavior.
|
||||
.
|
||||
.It Fl -whole-archive , -no-whole-archive
|
||||
.Pp
|
||||
.It Fl -whole-archive
|
||||
.It Fl -no-whole-archive
|
||||
When archive files
|
||||
.Pf ( Sy .a
|
||||
files) are given to a linker, only object
|
||||
@@ -700,7 +833,7 @@ all archive members to the output, you should pass
|
||||
.Fl -whole-archive .
|
||||
.Fl -no-whole-archive
|
||||
restores the default behavior for subsequent archives.
|
||||
.
|
||||
.Pp
|
||||
.It Fl -wrap Ns = Ns Ar symbol
|
||||
Make
|
||||
.Ar symbol
|
||||
@@ -709,8 +842,8 @@ to be resolved to
|
||||
The original symbol can be resolved as
|
||||
.Sy __real_ Ns Ar symbol .
|
||||
This option is typically used for wrapping an existing function.
|
||||
.
|
||||
.It Fl z Cm cet-report Ns = Ns Sy none | warning | error
|
||||
.Pp
|
||||
.It Fl z Cm cet-report Ns = Ns Op Sy none | warning | error
|
||||
Intel Control-flow Enforcement Technology (CET) is a new x86 feature \
|
||||
available since Tiger Lake which is released in 2020.
|
||||
It defines new instructions to harden security to protect programs from \
|
||||
@@ -740,8 +873,9 @@ bit in
|
||||
.Li .note.gnu.property
|
||||
section to determine whether or not an object file was compiled with
|
||||
.Fl fcf-protection .
|
||||
.
|
||||
.It Fl z Cm now , Fl z Cm lazy
|
||||
.Pp
|
||||
.It Fl z Cm now
|
||||
.It Fl z Cm lazy
|
||||
By default, functions referring other ELF modules are resolved by the
|
||||
dynamic linker when they are called for the first time.
|
||||
.Fl z Cm now
|
||||
@@ -749,12 +883,12 @@ marks an executable or a shared library file so that all dynamic
|
||||
symbols are loaded when a file is loaded to memory.
|
||||
.Fl z Cm lazy
|
||||
restores the default behavior.
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm origin
|
||||
Mark object requiring immediate
|
||||
.Dv $ORIGIN
|
||||
processing at runtime.
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm ibt
|
||||
Turn on
|
||||
.Li GNU_PROPERTY_X86_FEATURE_1_IBT
|
||||
@@ -762,7 +896,7 @@ bit in
|
||||
.Li .note.gnu.property
|
||||
section to indicate that the output uses IBT-enabled PLT. This option implies
|
||||
.Fl z Cm ibtplt .
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm ibtplt
|
||||
Generate Intel Branch Tracking (IBT)-enabled PLT.
|
||||
.Pp
|
||||
@@ -776,21 +910,23 @@ This mechanism makes ROP attacks difficult.
|
||||
.Pp
|
||||
Since PLT can be used as an indirect branch target, we need a different \
|
||||
instruction sequence for IBT-enabled PLT. If
|
||||
.Sy -z ibtplt
|
||||
.Sy -z Cm ibtplt
|
||||
is specified,
|
||||
.Nm
|
||||
generates PLT entries that start with a landing pad. The size of IBT-enabled \
|
||||
PLT is 24 bytes as opposed to 16 bytes regular PLT.
|
||||
.
|
||||
.It Fl z Cm execstack , Fl z Cm noexecstack
|
||||
.Pp
|
||||
.It Fl z Cm execstack
|
||||
.It Fl z Cm noexecstack
|
||||
By default, the pages for the stack area (i.e. the pages where local
|
||||
variables reside) are not executable for security reasons.
|
||||
.Fl z Cm execstack
|
||||
makes it executable.
|
||||
.Fl z Cm noexecstack
|
||||
restores the default behavior.
|
||||
.
|
||||
.It Fl z Cm keep-text-section-prefix , Fl z Cm nokeep-text-section-prefix
|
||||
.Pp
|
||||
.It Fl z Cm keep-text-section-prefix
|
||||
.It Fl z Cm nokeep-text-section-prefix
|
||||
Keep
|
||||
.Dv .text.hot ,
|
||||
.Dv .text.unknown ,
|
||||
@@ -799,8 +935,9 @@ Keep
|
||||
and
|
||||
.Dv .text.exit
|
||||
as separate sections in the final binary.
|
||||
.
|
||||
.It Fl z Cm relro , Fl z Cm norelro
|
||||
.Pp
|
||||
.It Fl z Cm relro
|
||||
.It Fl z Cm norelro
|
||||
Some sections such as
|
||||
.Dv .dynamic
|
||||
have to be writable only during an executable or \
|
||||
@@ -822,8 +959,10 @@ generates a
|
||||
segment.
|
||||
.Fl z Cm norelro
|
||||
disables the feature.
|
||||
.
|
||||
.It Fl z Cm separate-loadable-segments , Fl z Cm separate-code , Fl z Cm noseparate-code
|
||||
.Pp
|
||||
.It Fl z Cm separate-loadable-segments
|
||||
.It Fl z Cm separate-code
|
||||
.It Fl z Cm noseparate-code
|
||||
If one memory page contains multiple segments,
|
||||
the page protection bits are set in such a way that needed attributes \
|
||||
(writable or executable) are satisifed for all segments.
|
||||
@@ -840,18 +979,20 @@ adds paddings only between executable and non-executable segments.
|
||||
.Pp
|
||||
.Cm noseparate-code
|
||||
does not add any paddings between segments.
|
||||
.
|
||||
.It Fl z Cm defs , Fl z Cm nodefs
|
||||
.Pp
|
||||
.It Fl z Cm defs
|
||||
.It Fl z Cm nodefs
|
||||
Report undefined symbols (even with
|
||||
.Fl -shared ) .
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm shstk
|
||||
Enforce shadow stack by turning GNU_PROPERTY_X86_FEATURE_1_SHSTK bit in
|
||||
.Li .note.gnu.property
|
||||
output section. Shadow stack is part of Intel Control-flow Enforcement \
|
||||
Technology (CET), which is available since Tiger Lake (2020).
|
||||
.
|
||||
.It Fl z Cm notext , Fl z Cm textoff , Fl z Cm text
|
||||
.Pp
|
||||
.It Fl z Cm text
|
||||
.It Fl z Cm notext , Fl z Cm textoff
|
||||
.Nm
|
||||
by default reports an error if dynamic relocations are created in read-only \
|
||||
sections.
|
||||
@@ -864,7 +1005,7 @@ are given,
|
||||
creates such dynamic relocations without reporting an error.
|
||||
.Fl z Cm text
|
||||
restores the default behavior.
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm max-page-size
|
||||
Some CPU ISAs support multiple different memory page sizes.
|
||||
This option specifies the maximum page size that an output binary can run on.
|
||||
@@ -872,31 +1013,31 @@ If you specify a large value, the output can run on both large and small page \
|
||||
systems, but it wastes a bit of memory at page boundaries on systems with \
|
||||
small pages.
|
||||
.Pp
|
||||
The default value is 4 KiB for i386 and x86-64, and 64 KiB for ARM64.
|
||||
.
|
||||
The default value is 4 KiB for i386, x86-64 and RISC-V, and 64 KiB for ARM64.
|
||||
.Pp
|
||||
.It Fl z Cm nodefaultlib
|
||||
Make the dynamic loader to ignore default search paths.
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm nodelete
|
||||
Mark DSO non-deletable at runtime.
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm nodlopen
|
||||
Mark DSO not available to
|
||||
.Xr dlopen 3 .
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm nodump
|
||||
Mark DSO not available to
|
||||
.Xr dldump 3 .
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm nocopyreloc
|
||||
Do not create copy relocations.
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm initfirst
|
||||
Mark DSO to be initialized first at runtime.
|
||||
.
|
||||
.Pp
|
||||
.It Fl z Cm interpose
|
||||
Mark object to interpose all DSOs but executable.
|
||||
.
|
||||
.Pp
|
||||
.ig
|
||||
.It Fl (
|
||||
.It Fl )
|
||||
@@ -905,9 +1046,7 @@ Mark object to interpose all DSOs but executable.
|
||||
.It Fl -allow-shlib-undefined
|
||||
.It Fl -color-diagnostics
|
||||
.It Fl -dc
|
||||
.It Fl -disable-new-dtags
|
||||
.It Fl -dp
|
||||
.It Fl -enable-new-dtags
|
||||
.It Fl -end-group
|
||||
.It Fl -fatal-warnings
|
||||
.It Fl -gdb-index
|
||||
@@ -939,6 +1078,7 @@ Ignored
|
||||
.Sh SEE ALSO
|
||||
.Xr gold 1 ,
|
||||
.Xr ld 1 ,
|
||||
.Xr elf 5
|
||||
.Xr ld.so 8
|
||||
.
|
||||
.\"=============================================================================
|
||||
|
||||
@@ -0,0 +1,600 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use such instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch from
|
||||
// Thumb to ARM.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
//
|
||||
// You can distinguish Thumb functions from ARM functions by looking
|
||||
// at the least significant bit (LSB) of its "address". If LSB is 0,
|
||||
// it's ARM; otherwise, Thumb. LSB is not a part of its real address.
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, then `foo` is a function using Thumb instructions whose
|
||||
// address is 0x2000 (not 0x2001).
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static i32 sign_extend(u32 val, u32 size) {
|
||||
return (i64)(val << (31 - size)) >> (31 - size);
|
||||
};
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/BL--BLX--immediate-
|
||||
u32 sign = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ sign;
|
||||
u32 J2 = !I2 ^ sign;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (sign << 10) | imm10;
|
||||
*(u16 *)(loc + 2) =
|
||||
(*(u16 *)(loc + 2) & 0xd000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOVT
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
*(u16 *)loc = (*(u16 *)loc & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
*(u16 *)(loc + 2) =
|
||||
((*(u16 *)(loc + 2)) & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0xe52de004, // 1: push {lr}
|
||||
0xe59fe004, // ldr lr, [pc, #4]
|
||||
0xe08fe00e, // add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // .word .got.plt - 1b - 16
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
0xe59fc004, // 1: ldr ip, 2f
|
||||
0xe08cc00f, // add ip, ip, pc
|
||||
0xe59cf000, // ldr pc, [ip]
|
||||
0x00000000, // 2: .word sym@PLTGOT - 1b
|
||||
};
|
||||
|
||||
u8 *ent = buf + sizeof(plt0) + sym->get_plt_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
0xe59fc004, // 1: ldr ip, 2f
|
||||
0xe08cc00f, // add ip, ip, pc
|
||||
0xe59cf000, // ldr pc, [ip]
|
||||
0x00000000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define T (sym.get_addr(ctx) & 1)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u32 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_ARM_ABS32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_ARM_RELATIVE, 0};
|
||||
*(u32 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_ARM_REL32:
|
||||
*(u32 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_ARM_THM_CALL:
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
write_thm_b_imm(loc, 4);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else if (T) {
|
||||
write_thm_b_imm(loc, S + A - P);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(S + A - P, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite with BLX
|
||||
}
|
||||
continue;
|
||||
case R_ARM_BASE_PREL:
|
||||
*(u32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_PREL:
|
||||
*(u32 *)loc = G + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(u32 *)loc = G + A;
|
||||
continue;
|
||||
case R_ARM_TARGET2:
|
||||
*(u32 *)loc = GOT + G + A - P;
|
||||
continue;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24: {
|
||||
u32 val;
|
||||
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
val = 4;
|
||||
} else {
|
||||
val = S + A - P;
|
||||
}
|
||||
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xff00'0000) | ((val >> 2) & 0x00ff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP11: {
|
||||
assert(T);
|
||||
u32 val = (S + A - P) >> 1;
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (val & 0x07ff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, S + A - P);
|
||||
} else {
|
||||
assert(sym.extra.thumb_to_arm_thunk_idx != -1);
|
||||
u64 thunk_addr =
|
||||
ctx.thumb_to_arm->shdr.sh_addr +
|
||||
sym.extra.thumb_to_arm_thunk_idx * ThumbToArmSection::ENTRY_SIZE;
|
||||
write_thm_b_imm(loc, thunk_addr - P - 4);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_PREL31: {
|
||||
u32 val = S + A - P;
|
||||
*(u32 *)loc = (*(u32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(u32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin + 8;
|
||||
continue;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1)
|
||||
*(u32 *)loc = S - ctx.tls_begin + 8;
|
||||
else
|
||||
*(u32 *)loc = sym.get_tlsdesc_addr(ctx) + A - P - 6;
|
||||
continue;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(u32 *)loc = 0x8000f3af;
|
||||
} else {
|
||||
u64 addr = ctx.tls_trampoline->shdr.sh_addr;
|
||||
write_thm_b_imm(loc, align_to(addr - P - 4, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite BL with BLX
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef T
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_TARGET1: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported || sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= NEEDS_PLT | NEEDS_THUMB_TO_ARM_THUNK;
|
||||
else if (sym.esym().st_value % 2 == 0)
|
||||
sym.flags |= NEEDS_THUMB_TO_ARM_THUNK;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!ctx.relax_tlsdesc || sym.is_imported)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a function referenced by a Thumb B (branch) instruction is
|
||||
// resovled to a non-thumb function, we can't directly jump from the
|
||||
// thumb function to the ARM function. We can't rewrite B with BX
|
||||
// because there's no such BX instruction that takes an immediate.
|
||||
// BX takes only a register.
|
||||
//
|
||||
// In order to support such branch, we insert a small piece of code to
|
||||
// the resulting executable which switches the processor mode from
|
||||
// Thumb to ARM. This section contains such code.
|
||||
void ThumbToArmSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
if (sym->extra.thumb_to_arm_thunk_idx == -1) {
|
||||
sym->extra.thumb_to_arm_thunk_idx = symbols.size();
|
||||
symbols.push_back(sym);
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbToArmSection::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_size = symbols.size() * ENTRY_SIZE;
|
||||
}
|
||||
|
||||
void ThumbToArmSection::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
i64 offset = 0;
|
||||
|
||||
static u16 insn[] = {
|
||||
0x4778, // bx pc
|
||||
0x46c0, // nop
|
||||
0, 0, // b <imm24>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ENTRY_SIZE);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf + offset, insn, sizeof(insn));
|
||||
|
||||
u32 val = sym->get_addr(ctx) - this->shdr.sh_addr - offset - 12;
|
||||
*(u32 *)(buf + offset + 4) = 0xea00'0000 | (0x00ff'ffff & (val >> 2));
|
||||
offset += sizeof(insn);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsTrampolineSection::copy_buf(Context<E> &ctx) {
|
||||
// Trampoline code for TLSDESC
|
||||
static u32 insn[] = {
|
||||
0xe08e0000, // add r0, lr, r0
|
||||
0xe5901004, // ldr r1, [r0, #4]
|
||||
0xe12fff11, // bx r1
|
||||
};
|
||||
memcpy(ctx.buf + this->shdr.sh_offset, insn, sizeof(insn));
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// I don't know why only ARM uses the different mechanism, but it's
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function sorts .ARM.exidx records.
|
||||
void sort_arm_exidx(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_arm_exidx");
|
||||
|
||||
auto find_exidx = [&]() -> OutputSection<E> * {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
OutputSection<E> *osec = find_exidx();
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant is set in (2) but not
|
||||
// in (3). So they can be distinguished just by looking at a value.
|
||||
struct Entry {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
};
|
||||
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
|
||||
Entry *begin = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
Entry *end = (Entry *)(ctx.buf + osec->shdr.sh_offset + osec->shdr.sh_size);
|
||||
|
||||
struct Entry2 {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
u32 idx;
|
||||
};
|
||||
|
||||
// Read section contents
|
||||
std::vector<Entry2> vec;
|
||||
vec.reserve(end - begin);
|
||||
for (Entry *it = begin; it < end; it++)
|
||||
vec.push_back({it->addr, it->val, (u32)(it - begin)});
|
||||
|
||||
// Sort the records
|
||||
tbb::parallel_sort(vec.begin(), vec.end(), [](const Entry2 &a, const Entry2 &b) {
|
||||
return sign_extend(a.addr, 30) + a.idx * sizeof(Entry) <
|
||||
sign_extend(b.addr, 30) + b.idx * sizeof(Entry);
|
||||
});
|
||||
|
||||
// Write back the sorted records while adjusting relative addresses
|
||||
for (i64 i = 0; i < vec.size(); i++) {
|
||||
u32 offset = (vec[i].idx - i) * sizeof(Entry);
|
||||
begin[i].addr = 0x7fff'ffff & (sign_extend(vec[i].addr, 30) + offset);
|
||||
|
||||
const u32 EXIDX_CANTUNWIND = 1;
|
||||
if (vec[i].val == EXIDX_CANTUNWIND || (vec[i].val & 0x8000'0000))
|
||||
begin[i].val = vec[i].val;
|
||||
else
|
||||
begin[i].val = 0x7fff'ffff & (sign_extend(vec[i].val, 30) + offset);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+100
-96
@@ -22,30 +22,17 @@ static u64 page(u64 val) {
|
||||
return val & ~(u64)0xfff;
|
||||
}
|
||||
|
||||
template <>
|
||||
void GotPltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 *buf = (u64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
// The first slot of .got.plt points to _DYNAMIC.
|
||||
buf[0] = ctx.dynamic ? ctx.dynamic->shdr.sh_addr : 0;
|
||||
buf[1] = 0;
|
||||
buf[2] = 0;
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols)
|
||||
buf[sym->get_gotplt_idx(ctx)] = ctx.plt->shdr.sh_addr;
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xf0, 0x7b, 0xbf, 0xa9, // stp x16, x30, [sp,#-16]!
|
||||
0x10, 0x00, 0x00, 0x90, // adrp x16, .got.plt[2]
|
||||
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, .got.plt[2]]
|
||||
0x10, 0x02, 0x00, 0x91, // add x16, x16, .got.plt[2]
|
||||
0x20, 0x02, 0x1f, 0xd6, // br x17
|
||||
0x1f, 0x20, 0x03, 0xd5, // nop
|
||||
0x1f, 0x20, 0x03, 0xd5, // nop
|
||||
0x1f, 0x20, 0x03, 0xd5, // nop
|
||||
static const u32 plt0[] = {
|
||||
0xa9bf7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x90000010, // adrp x16, .got.plt[2]
|
||||
0xf9400211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x91000210, // add x16, x16, .got.plt[2]
|
||||
0xd61f0220, // br x17
|
||||
0xd503201f, // nop
|
||||
0xd503201f, // nop
|
||||
0xd503201f, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
@@ -60,11 +47,11 @@ static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
|
||||
|
||||
static const u8 data[] = {
|
||||
0x10, 0x00, 0x00, 0x90, // adrp x16, .got.plt[n]
|
||||
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, .got.plt[n]]
|
||||
0x10, 0x02, 0x00, 0x91, // add x16, x16, .got.plt[n]
|
||||
0x20, 0x02, 0x1f, 0xd6, // br x17
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, .got.plt[n]
|
||||
0xf9400211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x91000210, // add x16, x16, .got.plt[n]
|
||||
0xd61f0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
@@ -91,11 +78,11 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * ARM64::pltgot_size;
|
||||
|
||||
static const u8 data[] = {
|
||||
0x10, 0x00, 0x00, 0x90, // adrp x16, GOT[n]
|
||||
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, GOT[n]]
|
||||
0x20, 0x02, 0x1f, 0xd6, // br x17
|
||||
0x1f, 0x20, 0x03, 0xd5, // nop
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, GOT[n]
|
||||
0xf9400211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f0220, // br x17
|
||||
0xd503201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym->get_got_addr(ctx);
|
||||
@@ -109,18 +96,18 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
*(u64 *)(base + loc) = val;
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
case R_AARCH64_PREL32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
@@ -130,6 +117,7 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = get_range_extn();
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
@@ -162,22 +150,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
if (needs_dynrel[i]) {
|
||||
*dynrel++ = {P, R_AARCH64_ABS64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (needs_baserel[i]) {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_AARCH64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
*(u64 *)loc = S + A;
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_AARCH64_ABS64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_AARCH64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
@@ -197,12 +181,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(u32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(u32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(u32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
continue;
|
||||
@@ -249,7 +236,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(u32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_CONDBR19: {
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
*(u32 *)loc |= bits(val, 20, 2) << 5;
|
||||
@@ -294,6 +282,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(u32 *)loc |= bits(val, 23, 12) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A - ctx.tls_begin + 16, 11, 0) << 10;
|
||||
continue;
|
||||
@@ -357,7 +346,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -368,7 +356,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -381,7 +369,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
*(u64 *)loc = S + A;
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
continue;
|
||||
case R_AARCH64_ABS32:
|
||||
*(u32 *)loc = S + A;
|
||||
@@ -399,7 +390,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -413,7 +404,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -428,7 +419,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -450,9 +441,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, ERROR, ERROR }, // DSO
|
||||
{ NONE, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -469,19 +460,24 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
@@ -494,9 +490,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = -1;
|
||||
sym->thunk_sym_idx = -1;
|
||||
sym->flags &= (u8)~NEEDS_THUNK;
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
sym->flags &= (u8)~NEEDS_RANGE_EXTN_THUNK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +515,7 @@ static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
}
|
||||
|
||||
// We create a thunk no further than 100 MiB from any section.
|
||||
constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
|
||||
// We create a thunk for each 10 MiB input sections.
|
||||
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
|
||||
@@ -547,9 +543,9 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
while (b < members.size()) {
|
||||
// Move D foward as far as we can jump from B to D.
|
||||
while (d < members.size() && offset - members[b]->offset < MAX_DISTANCE) {
|
||||
offset = align_to(offset, members[d]->shdr.sh_addralign);
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->offset = offset;
|
||||
offset += members[d]->shdr.sh_size;
|
||||
offset += members[d]->sh_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
@@ -560,7 +556,7 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->offset + members[c - 1]->shdr.sh_size;
|
||||
i64 c_end = members[c - 1]->offset + members[c - 1]->sh_size;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
@@ -574,9 +570,11 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(&members[b], &members[c], [&](InputSection<E> *isec) {
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
isec->range_extn.resize(rels.size());
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->get_range_extn();
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -590,15 +588,16 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (sym.thunk_idx != -1) {
|
||||
isec->range_extn[i] = {sym.thunk_idx, sym.thunk_sym_idx};
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i] = {sym.extra.thunk_idx, sym.extra.thunk_sym_idx};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
isec->range_extn[i] = {thunk.thunk_idx, -1};
|
||||
range_extn[i] = {thunk.thunk_idx, -1};
|
||||
|
||||
if (!(sym.flags.fetch_or(NEEDS_THUNK) & NEEDS_THUNK)) {
|
||||
if (!(sym.flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
|
||||
NEEDS_RANGE_EXTN_THUNK)) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
@@ -614,20 +613,20 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = thunk.thunk_idx;
|
||||
sym->thunk_sym_idx = i++;
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(&members[b], &members[c], [&](InputSection<E> *isec) {
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
RangeExtensionRef &ref = isec->range_extn[i];
|
||||
|
||||
if (ref.thunk_idx == thunk.thunk_idx) {
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
ref.sym_idx = sym.thunk_sym_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -642,7 +641,7 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
static void mark_thunk_symbols(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
static void gc_thunk_symbols(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec.thunks) {
|
||||
i64 sz = thunk->symbols.size();
|
||||
thunk->symbol_map.resize(sz);
|
||||
@@ -652,9 +651,10 @@ static void mark_thunk_symbols(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// Mark referenced thunk symbols
|
||||
tbb::parallel_for_each(osec.members, [&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
RangeExtensionRef &ref = isec->range_extn[i];
|
||||
RangeExtensionRef &ref = range_extn[i];
|
||||
if (ref.thunk_idx == -1)
|
||||
continue;
|
||||
|
||||
@@ -685,16 +685,16 @@ static void shrink_section(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
|
||||
i64 offset = 0;
|
||||
|
||||
auto add_thunk = [&]() {
|
||||
auto add_thunk = [&] {
|
||||
thunks[0]->offset = offset;
|
||||
offset += thunks[0]->size();
|
||||
thunks = thunks.subspan(1);
|
||||
};
|
||||
|
||||
auto add_isec = [&]() {
|
||||
offset = align_to(offset, members[0]->shdr.sh_addralign);
|
||||
auto add_isec = [&] {
|
||||
offset = align_to(offset, 1 << members[0]->p2align);
|
||||
members[0]->offset = offset;
|
||||
offset += members[0]->shdr.sh_size;
|
||||
offset += members[0]->sh_size;
|
||||
members = members.subspan(1);
|
||||
};
|
||||
|
||||
@@ -722,12 +722,16 @@ static void shrink_section(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
i64 create_range_extension_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_range_extension_thunks");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->range_extn.resize(file->sections.size());
|
||||
|
||||
// First, we create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would need thunks. To do so, we start
|
||||
// with an initial layout in which output sections are separated far
|
||||
// apart.
|
||||
for (i64 i = 0; Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->shdr.sh_addr = i++ << 31;
|
||||
if (chunk->shdr.sh_flags & SHF_ALLOC)
|
||||
chunk->shdr.sh_addr = i++ << 31;
|
||||
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
@@ -741,9 +745,9 @@ i64 create_range_extension_thunks(Context<E> &ctx) {
|
||||
set_osec_offsets(ctx);
|
||||
|
||||
// Based on the current file layout, remove thunk symbols that turned
|
||||
// to be unnecessary.
|
||||
// out to be unnecessary.
|
||||
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
|
||||
mark_thunk_symbols(ctx, *osec);
|
||||
gc_thunk_symbols(ctx, *osec);
|
||||
});
|
||||
|
||||
// Recompute output section sizes that contain thunks. New section
|
||||
@@ -760,20 +764,20 @@ i64 create_range_extension_thunks(Context<E> &ctx) {
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const u8 insn[] = {
|
||||
0x10, 0x00, 0x00, 0x90, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x10, 0x02, 0x00, 0x91, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0x00, 0x02, 0x1f, 0xd6, // br x16
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(insn));
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * ENTRY_SIZE;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , insn, sizeof(insn));
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adr(loc, bits(page(S) - page(P), 32, 12));
|
||||
*(u32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
|
||||
+157
-75
@@ -4,31 +4,20 @@ namespace mold::elf {
|
||||
|
||||
using E = I386;
|
||||
|
||||
template <>
|
||||
void GotPltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
// The first slot of .got.plt points to _DYNAMIC.
|
||||
buf[0] = ctx.dynamic ? ctx.dynamic->shdr.sh_addr : 0;
|
||||
buf[1] = 0;
|
||||
buf[2] = 0;
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols)
|
||||
buf[sym->get_gotplt_idx(ctx)] = sym->get_plt_addr(ctx) + 6;
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0xb3, 0x04, 0, 0, 0, // pushl 4(%ebx)
|
||||
0xff, 0xa3, 0x08, 0, 0, 0, // jmp *8(%ebx)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
0xff, 0xb3, 0, 0, 0, 0, // pushl GOTPLT+4(%ebx)
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *GOTPLT+8(%ebx)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl (GOTPLT+4)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *(GOTPLT+8)
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl GOTPLT+4
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+8
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
@@ -48,7 +37,7 @@ static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
@@ -85,7 +74,7 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
@@ -103,17 +92,17 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
@@ -172,24 +161,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOTPLT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
if (needs_dynrel[i]) {
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
write32(A);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (needs_baserel[i]) {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_386_RELATIVE, 0};
|
||||
write32(S + A);
|
||||
continue;
|
||||
}
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
@@ -199,7 +175,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_386_32:
|
||||
write32(S + A);
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
write32(S + A);
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
write32(A);
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_386_RELATIVE, 0};
|
||||
write32(S + A);
|
||||
}
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
@@ -208,21 +193,28 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write16s(S + A);
|
||||
continue;
|
||||
case R_386_PC32:
|
||||
if (sym.is_absolute() || !sym.is_imported || !ctx.arg.shared) {
|
||||
write32(S + A - P);
|
||||
} else {
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
write32(A);
|
||||
}
|
||||
continue;
|
||||
case R_386_PLT32:
|
||||
write32(S + A - P);
|
||||
continue;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
write32(sym.get_got_addr(ctx) + A - GOTPLT);
|
||||
write32(G + A);
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
write32(S + A - GOTPLT);
|
||||
write32(S + A - GOT);
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
write32(GOTPLT + A - P);
|
||||
write32(GOT + A - P);
|
||||
continue;
|
||||
case R_386_TLS_GOTIE:
|
||||
write32(sym.get_gottp_addr(ctx) + A - GOTPLT);
|
||||
write32(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
continue;
|
||||
case R_386_TLS_LE:
|
||||
write32(S + A - ctx.tls_end);
|
||||
@@ -231,13 +223,72 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write32(sym.get_gottp_addr(ctx) + A);
|
||||
continue;
|
||||
case R_386_TLS_GD:
|
||||
write32(sym.get_tlsgd_addr(ctx) + A - GOTPLT);
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_LDM:
|
||||
write32(ctx.got->get_tlsld_addr(ctx) + A - GOTPLT);
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
|
||||
0x66, 0x90,
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
write32(S + A - ctx.tls_begin);
|
||||
if (ctx.got->tlsld_idx == -1)
|
||||
write32(S + A - ctx.tls_end);
|
||||
else
|
||||
write32(S + A - ctx.tls_begin);
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
@@ -250,7 +301,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
write32(S + A - ctx.tls_end);
|
||||
} else {
|
||||
write32(sym.get_tlsdesc_addr(ctx) + A - GOTPLT);
|
||||
write32(sym.get_tlsdesc_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
@@ -268,14 +319,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOTPLT
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -286,7 +336,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -321,10 +371,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOTPLT ctx.gotplt->shdr.sh_addr
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
@@ -334,7 +384,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_386_32:
|
||||
*(u32 *)loc = S + A;
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
@@ -346,13 +399,16 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
*(u32 *)loc = GOTPLT + A;
|
||||
*(u32 *)loc = GOT + A;
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
*(u32 *)loc = S + A - GOTPLT;
|
||||
*(u32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
*(u32 *)loc = sym.esym().st_size + A;
|
||||
@@ -363,13 +419,13 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef GOTPLT
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -383,7 +439,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -399,7 +455,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -409,7 +465,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -428,9 +484,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_386_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ ERROR, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -440,20 +496,46 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
case R_386_PLT32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_GOT32)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_GOT32)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!ctx.arg.relax || ctx.arg.shared)
|
||||
|
||||
@@ -0,0 +1,813 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = RISCV64;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u32 itype(u32 val) {
|
||||
return val << 20;
|
||||
}
|
||||
|
||||
static u32 stype(u32 val) {
|
||||
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
|
||||
}
|
||||
|
||||
static u32 btype(u32 val) {
|
||||
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
|
||||
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
|
||||
}
|
||||
|
||||
static u32 utype(u32 val) {
|
||||
// U-type instructions are used in combination with I-type
|
||||
// instructions. U-type insn sets an immediate to the upper 20-bits
|
||||
// of a register. I-type insn sign-extends a 12-bits immediate and
|
||||
// add it to a register value to construct a complete value. 0x800
|
||||
// is added here to compensate for the sign-extension.
|
||||
return bits(val + 0x800, 31, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 jtype(u32 val) {
|
||||
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 cbtype(u32 val) {
|
||||
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static u32 cjtype(u32 val) {
|
||||
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
|
||||
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
|
||||
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_itype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'11111'111'11111'1111111;
|
||||
*loc = (*loc & mask) | itype(val);
|
||||
}
|
||||
|
||||
static void write_stype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | stype(val);
|
||||
}
|
||||
|
||||
static void write_btype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | btype(val);
|
||||
}
|
||||
|
||||
static void write_utype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | utype(val);
|
||||
}
|
||||
|
||||
static void write_jtype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | jtype(val);
|
||||
}
|
||||
|
||||
static void write_cbtype(u16 *loc, u32 val) {
|
||||
u32 mask = 0b1110001110000011;
|
||||
*loc = (*loc & mask) | cbtype(val);
|
||||
}
|
||||
|
||||
static void write_cjtype(u16 *loc, u32 val) {
|
||||
u32 mask = 0b1110000000000011;
|
||||
*loc = (*loc & mask) | cjtype(val);
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset);
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0x00000397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c30333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
0x0003be03, // ld t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
|
||||
0xfd430313, // addi t1, t1, -44 # .plt entry
|
||||
0x00038293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
|
||||
0x00135313, // srli t1, t1, 1 # .plt entry offset
|
||||
0x0082b283, // ld t0, 8(t0) # link map
|
||||
0x000e0067, // jr t3
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 2, gotplt - plt);
|
||||
write_itype(buf + 4, gotplt - plt);
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, Symbol<E> &sym) {
|
||||
u32 *ent = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset + ctx.plt_hdr_size +
|
||||
sym.get_plt_idx(ctx) * ctx.plt_size);
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e0367, // jalr t1, t3
|
||||
0x00000013, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, gotplt - plt);
|
||||
write_itype(ent + 1, gotplt - plt);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
write_plt_header(ctx);
|
||||
for (Symbol<E> *sym : symbols)
|
||||
write_plt_entry(ctx, *sym);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e0367, // jalr t1, t3
|
||||
0x00000013, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u32 *ent = buf + sym->get_pltgot_idx(ctx) * 4;
|
||||
u64 got = sym->get_got_addr(ctx);
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, got - plt);
|
||||
write_itype(ent + 1, got - plt);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += val;
|
||||
return;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
return;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | (val & 0b0011'1111);
|
||||
return;
|
||||
case R_RISCV_SET8:
|
||||
*loc = val;
|
||||
return;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = val;
|
||||
return;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_RISCV_NONE || rel.r_type == R_RISCV_RELAX ||
|
||||
rel.r_type == R_RISCV_ALIGN)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
i64 r_offset = rel.r_offset + r_deltas[i];
|
||||
u8 *loc = base + r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_RISCV_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_RISCV_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_TLS_DTPMOD32:
|
||||
case R_RISCV_TLS_DTPMOD64:
|
||||
case R_RISCV_TLS_DTPREL32:
|
||||
case R_RISCV_TLS_DTPREL64:
|
||||
case R_RISCV_TLS_TPREL32:
|
||||
case R_RISCV_TLS_TPREL64:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
write_btype((u32 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_JAL:
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
if (r_deltas[i + 1] - r_deltas[i] != 0) {
|
||||
// auipc + jalr -> jal
|
||||
assert(r_deltas[i + 1] - r_deltas[i] == -4);
|
||||
u32 jalr = *(u32 *)&contents[rels[i].r_offset + 4];
|
||||
*(u32 *)loc = (0b11111'000000 & jalr) | 0b101111;
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
} else {
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
write_utype((u32 *)loc, val);
|
||||
write_itype((u32 *)(loc + 4), val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
*(u32 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
*(u32 *)loc = P;
|
||||
} else {
|
||||
*(u32 *)loc = S + A - P;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_itype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
write_itype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_stype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
write_stype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_HI20:
|
||||
write_utype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
write_utype((u32 *)loc, S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_RISCV_TPREL_ADD:
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
write_cbtype((u16 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_JUMP:
|
||||
write_cjtype((u16 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_LUI:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = S + A - P;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
|
||||
// In the above loop, PC-relative HI20 relocations overwrote
|
||||
// instructions with full 32-bit values to allow their corresponding
|
||||
// PCREL_LO12 relocations to read their values. This loop restore
|
||||
// the original instructions.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_GOT_HI20:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
case R_RISCV_TLS_GD_HI20: {
|
||||
u32 *loc = (u32 *)(base + rels[i].r_offset + r_deltas[i]);
|
||||
u32 val = *loc;
|
||||
*loc = *(u32 *)&contents[rels[i].r_offset];
|
||||
write_utype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_RISCV_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// A non-alloc section isn't relaxed, so just copy it as one big chunk.
|
||||
if (!(shdr().sh_flags & SHF_ALLOC)) {
|
||||
if (is_compressed())
|
||||
uncompress(ctx, buf);
|
||||
else
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory-allocated sections may be relaxed, so copy each segment
|
||||
// individually.
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
i64 pos = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
i64 delta = r_deltas[i + 1] - r_deltas[i];
|
||||
if (delta == 0)
|
||||
continue;
|
||||
assert(delta < 0);
|
||||
|
||||
const ElfRel<E> &r = rels[i];
|
||||
memcpy(buf, contents.data() + pos, r.r_offset - pos);
|
||||
buf += r.r_offset - pos;
|
||||
pos = r.r_offset - delta;
|
||||
}
|
||||
|
||||
memcpy(buf, contents.data() + pos, contents.size() - pos);
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_RISCV_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
case R_RISCV_HI20: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TLS_DTPMOD32:
|
||||
case R_RISCV_TLS_DTPMOD64:
|
||||
case R_RISCV_TLS_DTPREL32:
|
||||
case R_RISCV_TLS_DTPREL64:
|
||||
case R_RISCV_TLS_TPREL32:
|
||||
case R_RISCV_TLS_TPREL64:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_RISCV_GOT_HI20:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
ctx.has_gottp_rel = true;
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
case R_RISCV_ADD8:
|
||||
case R_RISCV_ADD16:
|
||||
case R_RISCV_ADD32:
|
||||
case R_RISCV_ADD64:
|
||||
case R_RISCV_SUB8:
|
||||
case R_RISCV_SUB16:
|
||||
case R_RISCV_SUB32:
|
||||
case R_RISCV_SUB64:
|
||||
case R_RISCV_ALIGN:
|
||||
break;
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
break;
|
||||
case R_RISCV_RVC_LUI:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
case R_RISCV_SET8:
|
||||
case R_RISCV_SET16:
|
||||
case R_RISCV_SET32:
|
||||
break;
|
||||
case R_RISCV_32_PCREL: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
return isec && (isec->shdr().sh_flags & SHF_ALLOC);
|
||||
}
|
||||
|
||||
// Initializes r_deltas and sorted_symbols.
|
||||
static void initialize_storage(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->r_deltas.resize(file->sections.size());
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
isec->get_r_deltas().resize(isec->get_rels(ctx).size() + 1);
|
||||
|
||||
file->sorted_symbols.resize(file->sections.size());
|
||||
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
file->sorted_symbols[isec->shndx].push_back(sym);
|
||||
|
||||
for (std::vector<Symbol<E> *> &vec : file->sorted_symbols)
|
||||
sort(vec, [](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
|
||||
});
|
||||
}
|
||||
|
||||
// Returns the distance between a relocated place and a symbol.
|
||||
static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We handle absolute symbols as if they were infinitely far away
|
||||
// because `relax_section` may increase a distance between a branch
|
||||
// instruction and an absolute symbol. Branching to an absolute
|
||||
// location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
return INT32_MAX;
|
||||
|
||||
// Likewise, relocations against weak undefined symbols won't be relaxed.
|
||||
if (sym.esym().is_undef_weak())
|
||||
return INT32_MAX;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
return S + A - P;
|
||||
}
|
||||
|
||||
// Relax R_RISCV_CALL and R_RISCV_CALL_PLT relocations.
|
||||
static void relax_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::span<i32> r_deltas = isec.get_r_deltas();
|
||||
std::span<Symbol<E> *> syms = isec.get_sorted_symbols();
|
||||
i64 delta = 0;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &r = rels[i];
|
||||
i64 delta2 = 0;
|
||||
|
||||
r_deltas[i] += delta;
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_ALIGN: {
|
||||
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate
|
||||
// some or all of the instructions so that the instruction that
|
||||
// immediately follows the NOPs is aligned to a specified
|
||||
// alignment boundary.
|
||||
u64 loc = isec.get_addr() + r.r_offset + delta;
|
||||
|
||||
// The total bytes of NOPs is stored to r_addend, so the next
|
||||
// instruction is r_addend away.
|
||||
u64 next_loc = loc + r.r_addend;
|
||||
|
||||
u64 alignment = (std::popcount<u64>(r.r_addend) == 1)
|
||||
? r.r_addend : next_power_of_two(r.r_addend);
|
||||
|
||||
if (next_loc % alignment)
|
||||
delta2 = align_to(loc, alignment) - next_loc;
|
||||
break;
|
||||
}
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (ctx.arg.relax) {
|
||||
if (i == rels.size() - 1 || rels[i + 1].r_type != R_RISCV_RELAX)
|
||||
break;
|
||||
|
||||
// If the jump target is within ±1 MiB, we can replace AUIPC+JALR
|
||||
// with JAL, saving 4 bytes.
|
||||
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
|
||||
i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
if (dist % 2 == 0 && -(1 << 20) <= dist && dist < (1 << 20))
|
||||
delta2 = -4;
|
||||
}
|
||||
}
|
||||
|
||||
if (delta2 == 0)
|
||||
continue;
|
||||
|
||||
while (!syms.empty() && syms[0]->value <= r.r_offset) {
|
||||
syms[0]->value += delta;
|
||||
syms = syms.subspan(1);
|
||||
}
|
||||
|
||||
delta += delta2;
|
||||
}
|
||||
|
||||
for (Symbol<E> *sym : syms)
|
||||
sym->value += delta;
|
||||
r_deltas[rels.size()] += delta;
|
||||
|
||||
isec.sh_size += delta;
|
||||
}
|
||||
|
||||
// RISC-V instructions are 16 or 32 bits long, so immediates encoded
|
||||
// in instructions can't be 32 bits long. Therefore, branch and load
|
||||
// instructions can't refer the 4 GiB address space unlike x86-64.
|
||||
// In fact, JAL (jump and link) instruction can jump to only within
|
||||
// ±1 MiB as their immediate is only 21 bits long.
|
||||
//
|
||||
// If you want to jump to somewhere further than that, you need to
|
||||
// construct a full 32-bit offset using multiple instruction and
|
||||
// branch to that place (e.g. AUIPC and JALR instead of JAL).
|
||||
// In this comment, we refer instructions such as JAL as the short
|
||||
// encoding and ones such as AUIPC+JALR as the long encoding.
|
||||
//
|
||||
// By default, compiler always uses the long encoding so that branch
|
||||
// targets are always encodable. This is a safe bet for them but
|
||||
// may result in inefficient code. Therefore, the RISC-V psABI defines
|
||||
// a mechanism for the linker to replace long encoding instructions
|
||||
// with short ones, shrinking the section and increasing the code
|
||||
// density.
|
||||
//
|
||||
// This is contrary to the psABIs for the other RISC processors such as
|
||||
// ARM64. Typically, they use short instructions by default, and a
|
||||
// linker creates so-called "thunks" to extend ranges of short jumps.
|
||||
// On RISC-V, instructions are in the long encoding by default, and
|
||||
// the linker shrinks them if it can.
|
||||
//
|
||||
// When we shrink a section, we need to adjust relocation offsets and
|
||||
// symbol values. For example, if we replace AUIPC+JALR with JAL
|
||||
// (which saves 4 bytes), all relocations pointing to anywhere after
|
||||
// that location need to be shifted by 4. In addition to that, any
|
||||
// symbol that refers anywhere after that locatioin need to be shifted
|
||||
// by 4 bytes as well.
|
||||
//
|
||||
// For relocations, we use `r_deltas` array to memorize how many bytes
|
||||
// have be adjusted. For symbols, we directly mutate their `value`
|
||||
// member.
|
||||
//
|
||||
// This operation seems to be optional, as by default instructions are
|
||||
// using the long encoding, but calling this function is actually
|
||||
// mandatory because of R_RISCV_ALIGN. R_RISCV_ALIGN relocation is a
|
||||
// directive to the linker to align the location referred to by the
|
||||
// relocation to a specified byte boundary. We at least have to
|
||||
// interpret them satisfy the constraints imposed by R_RISCV_ALIGN
|
||||
// relocations.
|
||||
i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "riscv_resize_sections");
|
||||
initialize_storage(ctx);
|
||||
|
||||
// Find R_RISCV_CALL AND R_RISCV_CALL_PLT that can be relaxed.
|
||||
// This step should only shrink sections.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
relax_section(ctx, *isec);
|
||||
});
|
||||
|
||||
// Re-compute section offset again to finalize them.
|
||||
compute_section_sizes(ctx);
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+170
-89
@@ -4,25 +4,6 @@ namespace mold::elf {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
template <>
|
||||
void GotPltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 *buf = (u64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
// The first slot of .got.plt points to _DYNAMIC, as requested by
|
||||
// the x86-64 psABI. The second and the third slots are reserved by
|
||||
// the psABI.
|
||||
buf[0] = ctx.dynamic ? ctx.dynamic->shdr.sh_addr : 0;
|
||||
buf[1] = 0;
|
||||
buf[2] = 0;
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
if (ctx.arg.z_ibtplt)
|
||||
buf[sym->get_gotplt_idx(ctx)] = sym->get_plt_addr(ctx) + 10;
|
||||
else
|
||||
buf[sym->get_gotplt_idx(ctx)] = sym->get_plt_addr(ctx) + 6;
|
||||
}
|
||||
}
|
||||
|
||||
// The compact PLT format is used when `-z now` is given. If the flag
|
||||
// is given, all PLT symbols are resolved eagerly on startup, so we
|
||||
// can omit code for lazy symbol resolution from PLT in that case.
|
||||
@@ -43,44 +24,49 @@ static void write_compact_plt(Context<E> &ctx) {
|
||||
|
||||
// The IBTPLT is a security-enhanced version of the regular PLT.
|
||||
// It uses Indirect Branch Tracking (IBT) feature which is part of
|
||||
// Intel Control-Flow Enforcement (CET). IBTPLT is slightly larger
|
||||
// than the regular PLT (24 bytes vs 16 bytes for each entry).
|
||||
// Intel Control-Flow Enforcement (CET).
|
||||
//
|
||||
// Note that our IBTPLT instruction sequence is different from the one
|
||||
// used in GNU ld. GNU's IBTPLT implementation uses two separate
|
||||
// sections (.plt and .plt.sec) in which one PLT entry takes 32 bytes
|
||||
// in total.
|
||||
// in total. Our PLT consists of just .plt and each entry is 16 bytes
|
||||
// long.
|
||||
//
|
||||
// Our PLT entry clobbers r11, but that's fine because the resolver
|
||||
// function (_dl_runtime_resolve) does not preserve r11 anyway.
|
||||
static void write_ibtplt(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0x53, // push %r11
|
||||
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
*(u32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0x68, 0, 0, 0, 0, // push $index_in_relplt
|
||||
0xf2, 0xe9, 0, 0, 0, 0, // jmp PLT[0]
|
||||
0x0f, 0x1f, 0x00, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 6) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 10;
|
||||
*(u32 *)(ent + 11) = relplt_idx++;
|
||||
*(u32 *)(ent + 17) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 21;
|
||||
*(u32 *)(ent + 6) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,23 +131,23 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)(base + loc) = val;
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
@@ -219,6 +205,28 @@ static u32 relax_gottpoff(u8 *loc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
@@ -291,19 +299,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
|
||||
#define GOT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
if (needs_dynrel[i]) {
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
write64(A);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (needs_baserel[i]) {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
|
||||
write64(S + A);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
write8(S + A);
|
||||
@@ -318,7 +313,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
continue;
|
||||
case R_X86_64_64:
|
||||
write64(S + A);
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
write64(S + A);
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
write64(A);
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
|
||||
write64(S + A);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_PC8:
|
||||
write8s(S + A - P);
|
||||
@@ -330,7 +334,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC64:
|
||||
write64(S + A - P);
|
||||
if (sym.is_absolute() || !sym.is_imported || !ctx.arg.shared) {
|
||||
write64(S + A - P);
|
||||
} else {
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
write64(A);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
@@ -383,15 +392,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
// Relax GD to LE
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
|
||||
i64 val = S - ctx.tls_end + A + 4;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)(loc + 8) = val;
|
||||
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
@@ -400,11 +429,38 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_X86_64_TLSLD:
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
// Relax LD to LE
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
@@ -441,10 +497,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
continue;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
static const u8 insn[] = {
|
||||
0x48, 0xc7, 0xc0, 0, 0, 0, 0, // mov $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - ctx.tls_end + 4);
|
||||
} else {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
@@ -500,7 +556,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -552,13 +608,22 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)loc = S + A;
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
*(u64 *)loc = S + A - ctx.tls_begin;
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
@@ -584,7 +649,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
// need to scan relocations.
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -599,7 +664,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -620,7 +685,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -632,7 +697,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -652,9 +717,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_PC64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
{ ERROR, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
@@ -688,30 +753,46 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
case R_X86_64_PLTOFF64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
}
|
||||
case R_X86_64_TLSGD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
}
|
||||
case R_X86_64_TLSLD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSLD reloc must be followed by PLT32 or GOTPCREL";
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
ctx.has_gottp_rel = true;
|
||||
|
||||
@@ -722,7 +803,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPC32_TLSDESC: {
|
||||
if (memcmp(loc - 3, "\x48\x8d\x05", 3))
|
||||
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
|
||||
+211
-70
@@ -38,16 +38,20 @@ Options:
|
||||
-l LIBNAME Search for a given library
|
||||
-m TARGET Set target
|
||||
-o FILE, --output FILE Set output filename
|
||||
-q, --emit-relocs Leaves relocation sections in the output
|
||||
-r, --relocatable Generate relocatable output
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
--Bdynamic Link against shared libraries (default)
|
||||
--Bstatic Do not link against shared libraries
|
||||
--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
|
||||
--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
|
||||
--allow-multiple-definition Allow multiple definitions
|
||||
--as-needed Only set DT_NEEDED if used
|
||||
--no-as-needed
|
||||
@@ -61,10 +65,12 @@ Options:
|
||||
--compress-debug-sections [none,zlib,zlib-gabi,zlib-gnu]
|
||||
Compress .debug_* sections
|
||||
--dc Ignored
|
||||
--dependency-file=FILE Write Makefile-style dependency rules to FILE
|
||||
--defsym=SYMBOL=VALUE Define a symbol alias
|
||||
--demangle Demangle C++ symbols in log messages (default)
|
||||
--no-demangle
|
||||
--disable-new-dtags Ignored
|
||||
--enable-new-dtags Emit DT_RUNPATH for --rpath (default)
|
||||
--disable-new-dtags Emit DT_RPATH for --rpath
|
||||
--dp Ignored
|
||||
--dynamic-list Read a list of dynamic symbols
|
||||
--eh-frame-hdr Create .eh_frame_hdr section
|
||||
@@ -78,21 +84,21 @@ Options:
|
||||
--no-fork
|
||||
--gc-sections Remove unreferenced sections
|
||||
--no-gc-sections
|
||||
--gdb-index Ignored
|
||||
--gdb-index Create .gdb_index for faster gdb startup
|
||||
--hash-style [sysv,gnu,both]
|
||||
Set hash style
|
||||
--icf Fold identical code
|
||||
--icf=[all,none] Fold identical code
|
||||
--no-icf
|
||||
--image-base ADDR Set the base address to a given value
|
||||
--init SYMBOL Call SYMBOl at load-time
|
||||
--no-undefined Report undefined symbols (even with --shared)
|
||||
--noinhibit-exec Create an output file even if errors occur
|
||||
--oformat=binary Omit ELF, section and program headers
|
||||
--pack-dyn-relocs=[relr,none]
|
||||
Pack dynamic relocations
|
||||
--perf Print performance statistics
|
||||
--pie, --pic-executable Create a position independent executable
|
||||
--no-pie, --no-pic-executable
|
||||
--plugin Ignored
|
||||
--plugin-opt Ignored
|
||||
--pop-state Pop state of flags governing input file handling
|
||||
--preload
|
||||
--no-preload
|
||||
@@ -108,16 +114,18 @@ Options:
|
||||
--repro Embed input files to .repro section
|
||||
--require-defined SYMBOL Require SYMBOL be defined in the final output
|
||||
--retain-symbols-file FILE Keep only symbols listed in FILE
|
||||
--reverse-sections Reverses input sections in the output file
|
||||
--rpath DIR Add DIR to 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
|
||||
--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
|
||||
--start-lib Give following object files in-archive-file semantics
|
||||
--end-lib End the effect of --start-lib
|
||||
--static Do not link against shared libraries
|
||||
--stats Print input statistics
|
||||
--sysroot DIR Set target system root directory
|
||||
--thread-count COUNT, --threads=COUNT
|
||||
@@ -131,6 +139,9 @@ Options:
|
||||
--version-script FILE Read version script
|
||||
--warn-common Warn about common symbols
|
||||
--no-warn-common
|
||||
--warn-once Only warn once for each undefined symbol
|
||||
--warn-shared-textrel Warn if the output .so needs text relocations
|
||||
--warn-textrel Warn if the output file needs text relocations
|
||||
--warn-unresolved-symbols Report unresolved symbols as warnings
|
||||
--error-unresolved-symbols
|
||||
Report unresolved symbols as errors (default)
|
||||
@@ -217,6 +228,19 @@ bool read_arg(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool read_eq(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
std::string_view &arg, std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0].starts_with(opt + "=")) {
|
||||
arg = args[0].substr(opt.size() + 1);
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool read_flag(std::span<std::string_view> &args, std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
@@ -259,51 +283,28 @@ bool read_z_arg(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string create_response_file(Context<E> &ctx) {
|
||||
std::string buf;
|
||||
std::stringstream out;
|
||||
|
||||
std::string cwd = std::filesystem::current_path();
|
||||
out << "-C " << cwd.substr(1) << "\n";
|
||||
|
||||
if (cwd != "/") {
|
||||
out << "--chroot ..";
|
||||
i64 depth = std::count(cwd.begin(), cwd.end(), '/');
|
||||
for (i64 i = 1; i < depth; i++)
|
||||
out << "/..";
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
for (i64 i = 1; i < ctx.cmdline_args.size(); i++) {
|
||||
std::string_view arg = ctx.cmdline_args[i];
|
||||
|
||||
if (arg == "-repro" || arg == "--repro") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
out << arg << "\n";
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_hex(Context<E> &ctx, std::string opt, std::string_view value) {
|
||||
if (!value.starts_with("0x") && !value.starts_with("0X"))
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
value = value.substr(2);
|
||||
if (value.starts_with("0x") || value.starts_with("0X"))
|
||||
value = value.substr(2);
|
||||
if (value.find_first_not_of("0123456789abcdefABCDEF") != value.npos)
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
return std::stol(std::string(value), nullptr, 16);
|
||||
return std::stoul(std::string(value), nullptr, 16);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_number(Context<E> &ctx, std::string opt,
|
||||
std::string_view value) {
|
||||
size_t nread;
|
||||
i64 ret = std::stol(std::string(value), &nread, 0);
|
||||
|
||||
if (value.starts_with('-')) {
|
||||
i64 ret = std::stoul(std::string(value.substr(1)), &nread, 0);
|
||||
if (value.size() - 1 != nread)
|
||||
Fatal(ctx) << "option -" << opt << ": not a number: " << value;
|
||||
return -ret;
|
||||
}
|
||||
|
||||
i64 ret = std::stoul(std::string(value), &nread, 0);
|
||||
if (value.size() != nread)
|
||||
Fatal(ctx) << "option -" << opt << ": not a number: " << value;
|
||||
return ret;
|
||||
@@ -396,21 +397,41 @@ static bool is_file(std::string_view path) {
|
||||
(st.st_mode & S_IFMT) != S_IFDIR;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::variant<Symbol<E> *, u64>
|
||||
parse_defsym_value(Context<E> &ctx, std::string_view s) {
|
||||
if (s.starts_with("0x") || s.starts_with("0X")) {
|
||||
size_t nread;
|
||||
u64 addr = std::stoull(std::string(s), &nread, 16);
|
||||
if (s.size() != nread)
|
||||
return {};
|
||||
return addr;
|
||||
}
|
||||
|
||||
if (s.find_first_not_of("0123456789") == s.npos)
|
||||
return (u64)std::stoull(std::string(s), nullptr, 10);
|
||||
return get_symbol(ctx, s);
|
||||
}
|
||||
|
||||
// Returns a PLT header size and a PLT entry size.
|
||||
template <typename E>
|
||||
static std::pair<i64, i64> get_plt_size(Context<E> &ctx) {
|
||||
switch (E::e_machine) {
|
||||
case EM_X86_64:
|
||||
if constexpr (std::is_same_v<E, X86_64>) {
|
||||
if (ctx.arg.z_now)
|
||||
return {0, 8};
|
||||
if (ctx.arg.z_ibtplt)
|
||||
return {16, 24};
|
||||
return {32, 16};
|
||||
return {16, 16};
|
||||
case EM_386:
|
||||
return {16, 16};
|
||||
case EM_AARCH64:
|
||||
return {32, 16};
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, I386>)
|
||||
return {16, 16};
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
return {32, 16};
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
return {20, 16};
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
return {32, 16};
|
||||
unreachable();
|
||||
}
|
||||
|
||||
@@ -424,6 +445,12 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.page_size = E::page_size;
|
||||
|
||||
bool version_shown = false;
|
||||
bool warn_shared_textrel = false;
|
||||
|
||||
// RISC-V object files contains lots of local symbols, so by default
|
||||
// we discard them. This is compatible with GNU ld.
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
ctx.arg.discard_locals = true;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
@@ -458,6 +485,10 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.emulation = EM_386;
|
||||
} else if (arg == "aarch64linux") {
|
||||
ctx.arg.emulation = EM_AARCH64;
|
||||
} else if (arg == "armelf_linux_eabi") {
|
||||
ctx.arg.emulation = EM_ARM;
|
||||
} else if (arg == "elf64lriscv") {
|
||||
ctx.arg.emulation = EM_RISCV;
|
||||
} else {
|
||||
Fatal(ctx) << "unknown -m argument: " << arg;
|
||||
}
|
||||
@@ -476,18 +507,24 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.Bsymbolic_functions = false;
|
||||
} else if (read_arg(ctx, args, arg, "exclude-libs")) {
|
||||
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
|
||||
} else if (read_flag(args, "q") || read_flag(args, "emit-relocs")) {
|
||||
ctx.arg.emit_relocs = true;
|
||||
} else if (read_arg(ctx, args, arg, "e") ||
|
||||
read_arg(ctx, args, arg, "entry")) {
|
||||
ctx.arg.entry = arg;
|
||||
} else if (read_arg(ctx, args, arg, "Map")) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "print-dependencies")) {
|
||||
ctx.arg.print_dependencies = 1;
|
||||
} else if (read_flag(args, "print-dependencies=full")) {
|
||||
ctx.arg.print_dependencies = 2;
|
||||
} else if (read_flag(args, "print-map") || read_flag(args, "M")) {
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "static") || read_flag(args, "Bstatic")) {
|
||||
} else if (read_flag(args, "Bstatic") || read_flag(args, "dn") || read_flag(args, "static")) {
|
||||
ctx.arg.is_static = true;
|
||||
remaining.push_back("-Bstatic");
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
} else if (read_flag(args, "Bdynamic") || read_flag(args, "dy")) {
|
||||
ctx.arg.is_static = false;
|
||||
remaining.push_back("-Bdynamic");
|
||||
} else if (read_flag(args, "shared") || read_flag(args, "Bshareable")) {
|
||||
@@ -496,17 +533,33 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
|
||||
} else if (read_flag(args, "start-lib")) {
|
||||
remaining.push_back("-start-lib");
|
||||
} else if (read_arg(ctx, args, arg, "dependency-file")) {
|
||||
ctx.arg.dependency_file = arg;
|
||||
} else if (read_arg(ctx, args, arg, "defsym")) {
|
||||
size_t pos = arg.find('=');
|
||||
if (pos == arg.npos || pos == arg.size() - 1)
|
||||
Fatal(ctx) << "-defsym: syntax error: " << arg;
|
||||
ctx.arg.defsyms.push_back({arg.substr(0, pos), arg.substr(pos + 1)});
|
||||
ctx.arg.defsyms.emplace_back(get_symbol(ctx, arg.substr(0, pos)),
|
||||
parse_defsym_value(ctx, arg.substr(pos + 1)));
|
||||
} else if (read_flag(args, ":lto-pass2")) {
|
||||
ctx.arg.lto_pass2 = true;
|
||||
} else if (read_arg(ctx, args, arg, ":ignore-ir-file")) {
|
||||
ctx.arg.ignore_ir_file.insert(arg);
|
||||
} else if (read_flag(args, "demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_flag(args, "no-demangle")) {
|
||||
ctx.arg.demangle = false;
|
||||
} else if (read_flag(args, "default-symver")) {
|
||||
ctx.arg.default_symver = true;
|
||||
} else if (read_flag(args, "noinhibit-exec")) {
|
||||
ctx.arg.noinhibit_exec = true;
|
||||
} else if (read_flag(args, "shuffle-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
} else if (read_eq(ctx, args, arg, "shuffle-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
ctx.arg.shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
|
||||
} else if (read_flag(args, "reverse-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_REVERSE;
|
||||
} else if (read_arg(ctx, args, arg, "y") ||
|
||||
read_arg(ctx, args, arg, "trace-symbol")) {
|
||||
ctx.arg.trace_symbol.push_back(arg);
|
||||
@@ -573,6 +626,10 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.relax = true;
|
||||
} else if (read_flag(args, "no-relax")) {
|
||||
ctx.arg.relax = false;
|
||||
} else if (read_flag(args, "gdb-index")) {
|
||||
ctx.arg.gdb_index = true;
|
||||
} else if (read_flag(args, "no-gdb-index")) {
|
||||
ctx.arg.gdb_index = false;
|
||||
} else if (read_flag(args, "r") || read_flag(args, "relocatable")) {
|
||||
ctx.arg.relocatable = true;
|
||||
} else if (read_flag(args, "perf")) {
|
||||
@@ -607,6 +664,16 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.warn_common = true;
|
||||
} else if (read_flag(args, "no-warn-common")) {
|
||||
ctx.arg.warn_common = false;
|
||||
} else if (read_flag(args, "warn-once")) {
|
||||
ctx.arg.warn_once = true;
|
||||
} else if (read_flag(args, "warn-shared-textrel")) {
|
||||
warn_shared_textrel = true;
|
||||
} else if (read_flag(args, "warn-textrel")) {
|
||||
ctx.arg.warn_textrel = true;
|
||||
} else if (read_flag(args, "enable-new-dtags")) {
|
||||
ctx.arg.enable_new_dtags = true;
|
||||
} else if (read_flag(args, "disable-new-dtags")) {
|
||||
ctx.arg.enable_new_dtags = false;
|
||||
} else if (read_arg(ctx, args, arg, "compress-debug-sections")) {
|
||||
if (arg == "zlib" || arg == "zlib-gabi")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_GABI;
|
||||
@@ -623,8 +690,24 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.is_static = true;
|
||||
} else if (read_flag(args, "no-omagic")) {
|
||||
ctx.arg.omagic = false;
|
||||
} else if (read_arg(ctx, args, arg, "oformat")) {
|
||||
if (arg != "binary")
|
||||
Fatal(ctx) << "-oformat: " << arg << " is not supported";
|
||||
ctx.arg.oformat_binary = true;
|
||||
} else if (read_arg(ctx, args, arg, "retain-symbols-file")) {
|
||||
read_retain_symbols_file(ctx, arg);
|
||||
} else if (read_arg(ctx, args, arg, "section-start")) {
|
||||
size_t pos = arg.find('=');
|
||||
if (pos == arg.npos || pos == arg.size() - 1)
|
||||
Fatal(ctx) << "-section-start: syntax error: " << arg;
|
||||
ctx.arg.section_start[arg.substr(0, pos)] =
|
||||
parse_hex(ctx, "section-start", arg.substr(pos + 1));
|
||||
} else if (read_arg(ctx, args, arg, "Tbss")) {
|
||||
ctx.arg.section_start[".bss"] = parse_hex(ctx, "Tbss", arg);
|
||||
} else if (read_arg(ctx, args, arg, "Tdata")) {
|
||||
ctx.arg.section_start[".data"] = parse_hex(ctx, "Tdata", arg);
|
||||
} else if (read_arg(ctx, args, arg, "Ttext")) {
|
||||
ctx.arg.section_start[".text"] = parse_hex(ctx, "Ttext", arg);
|
||||
} else if (read_flag(args, "repro")) {
|
||||
ctx.arg.repro = true;
|
||||
} else if (read_z_flag(args, "now")) {
|
||||
@@ -641,7 +724,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.z_execstack = true;
|
||||
} else if (read_z_arg(ctx, args, arg, "max-page-size")) {
|
||||
ctx.page_size = parse_number(ctx, "-z max-page-size", arg);
|
||||
if (__builtin_popcountll(ctx.page_size) != 1)
|
||||
if (std::popcount<u64>(ctx.page_size) != 1)
|
||||
Fatal(ctx) << "-z max-page-size " << arg << ": value must be a power of 2";
|
||||
} else if (read_z_flag(args, "noexecstack")) {
|
||||
ctx.arg.z_execstack = false;
|
||||
@@ -729,18 +812,72 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag(args, "no-quick-exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_arg(ctx, args, arg, "plugin")) {
|
||||
ctx.arg.plugin = arg;
|
||||
} else if (read_arg(ctx, args, arg, "plugin-opt")) {
|
||||
ctx.arg.plugin_opt.push_back(std::string(arg));
|
||||
} else if (read_flag(args, "lto-cs-profile-generate")) {
|
||||
ctx.arg.plugin_opt.push_back("cs-profile-generate");
|
||||
} else if (read_arg(ctx, args, arg, "lto-cs-profile-file")) {
|
||||
ctx.arg.plugin_opt.push_back("cs-profile-path=" + std::string(arg));
|
||||
} else if (read_flag(args, "lto-debug-pass-manager")) {
|
||||
ctx.arg.plugin_opt.push_back("debug-pass-manager");
|
||||
} else if (read_flag(args, "disable-verify")) {
|
||||
ctx.arg.plugin_opt.push_back("disable-verify");
|
||||
} else if (read_flag(args, "lto-emit-asm")) {
|
||||
ctx.arg.plugin_opt.push_back("emit-asm");
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-jobs")) {
|
||||
ctx.arg.plugin_opt.push_back("jobs=" + std::string(arg));
|
||||
} else if (read_flag(args, "no-legacy-pass-manager")) {
|
||||
ctx.arg.plugin_opt.push_back("legacy-pass-manager");
|
||||
} else if (read_arg(ctx, args, arg, "lto-partitions")) {
|
||||
ctx.arg.plugin_opt.push_back("lto-partitions=" + std::string(arg));
|
||||
} else if (read_flag(args, "no-lto-legacy-pass-manager")) {
|
||||
ctx.arg.plugin_opt.push_back("new-pass-manager");
|
||||
} else if (read_arg(ctx, args, arg, "lto-obj-path")) {
|
||||
ctx.arg.plugin_opt.push_back("obj-path=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-filename")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-filename=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-format")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-format=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-hotness-threshold")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-hotness-threshold=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-passes")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-passes=" + std::string(arg));
|
||||
} else if (read_flag(args, "opt-remarks-with_hotness")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-with-hotness");
|
||||
} else if (args[0].starts_with("-lto-O")) {
|
||||
ctx.arg.plugin_opt.push_back("O" + std::string(args[0].substr(6)));
|
||||
args = args.subspan(1);
|
||||
} else if (args[0].starts_with("--lto-O")) {
|
||||
ctx.arg.plugin_opt.push_back("O" + std::string(args[0].substr(7)));
|
||||
args = args.subspan(1);
|
||||
} else if (read_arg(ctx, args, arg, "lto-pseudo-probe-for-profiling")) {
|
||||
ctx.arg.plugin_opt.push_back("pseudo-probe-for-profiling=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "lto-sample-profile")) {
|
||||
ctx.arg.plugin_opt.push_back("sample-profile=" + std::string(arg));
|
||||
} else if (read_flag(args, "save-temps")) {
|
||||
ctx.arg.plugin_opt.push_back("save-temps");
|
||||
} else if (read_flag(args, "thinlto-emit-imports-files")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-emit-imports-files");
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-index-only")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-index-only=" + std::string(arg));
|
||||
} else if (read_flag(args, "thinlto-index-only")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-index-only");
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-object-suffix-replace")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-object-suffix-replace=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-prefix-replace")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-prefix-replace=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-jobs")) {
|
||||
ctx.arg.plugin_opt.push_back("jobs=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "thread-count")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "thread-count", arg);
|
||||
} else if (read_flag(args, "threads")) {
|
||||
ctx.arg.thread_count = 0;
|
||||
} else if (read_flag(args, "no-threads")) {
|
||||
ctx.arg.thread_count = 1;
|
||||
} else if (args[0].starts_with("-threads=")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "threads=", args[0].substr(9));
|
||||
args = args.subspan(1);
|
||||
} else if (args[0].starts_with("--threads=")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "threads=", args[0].substr(10));
|
||||
args = args.subspan(1);
|
||||
} else if (read_eq(ctx, args, arg, "threads")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "threads", arg);
|
||||
} else if (read_flag(args, "discard-all") || read_flag(args, "x")) {
|
||||
ctx.arg.discard_all = true;
|
||||
} else if (read_flag(args, "discard-locals") || read_flag(args, "X")) {
|
||||
@@ -795,7 +932,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
if (arg == "binary")
|
||||
Fatal(ctx)
|
||||
<< "mold does not suppor `-b binary`. If you want to convert a binary"
|
||||
" file into an object file, use `objcopy -I binary -O elf64-x86-64"
|
||||
<< " file into an object file, use `objcopy -I binary -O default"
|
||||
<< " <input-file> <output-file.o>` instead.";
|
||||
Fatal(ctx) << "unknown command line option: -b " << arg;
|
||||
} else if (read_arg(ctx, args, arg, "auxiliary") ||
|
||||
@@ -805,6 +942,8 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
read_arg(ctx, args, arg, "F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag(args, "preload")) {
|
||||
Warn(ctx) << "--preload is deprecated and will be removed in a"
|
||||
<< " future version";
|
||||
ctx.arg.preload = true;
|
||||
} else if (read_flag(args, "no-preload")) {
|
||||
ctx.arg.preload = false;
|
||||
@@ -814,10 +953,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
} else if (read_flag(args, "O1")) {
|
||||
} else if (read_flag(args, "O2")) {
|
||||
} else if (read_flag(args, "verbose")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin-opt")) {
|
||||
} else if (read_flag(args, "color-diagnostics")) {
|
||||
} else if (read_flag(args, "gdb-index")) {
|
||||
} else if (read_flag(args, "eh-frame-hdr")) {
|
||||
} else if (read_flag(args, "start-group")) {
|
||||
} else if (read_flag(args, "end-group")) {
|
||||
@@ -896,10 +1032,12 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.arg.shared) {
|
||||
// Remove redundant `/..` or `/.` from library paths.
|
||||
for (std::string &path : ctx.arg.library_paths)
|
||||
path = path_clean(path);
|
||||
|
||||
if (ctx.arg.shared)
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.dynamic_linker = "";
|
||||
}
|
||||
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
@@ -920,7 +1058,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
}
|
||||
|
||||
if (ctx.arg.image_base % ctx.page_size)
|
||||
Fatal(ctx) << "-image-base msut be a multiple of -max-page-size";
|
||||
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
|
||||
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
@@ -940,6 +1078,9 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
|
||||
}
|
||||
|
||||
if (ctx.arg.shared && warn_shared_textrel)
|
||||
ctx.arg.warn_textrel = true;
|
||||
|
||||
std::tie(ctx.plt_hdr_size, ctx.plt_size) = get_plt_size(ctx);
|
||||
|
||||
ctx.arg.undefined.push_back(ctx.arg.entry);
|
||||
@@ -960,8 +1101,6 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::string_view &arg, \
|
||||
std::string name); \
|
||||
\
|
||||
template std::string create_response_file(Context<E> &ctx); \
|
||||
\
|
||||
template \
|
||||
void parse_nonpositional_args(Context<E> &ctx, \
|
||||
std::vector<std::string_view> &remaining)
|
||||
@@ -969,5 +1108,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -4,10 +4,6 @@
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
typedef uint8_t u8;
|
||||
@@ -23,6 +19,8 @@ typedef int64_t i64;
|
||||
struct X86_64;
|
||||
struct I386;
|
||||
struct ARM64;
|
||||
struct ARM32;
|
||||
struct RISCV64;
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
@@ -69,6 +67,8 @@ static constexpr u32 SHT_GNU_VERDEF = 0x6ffffffd;
|
||||
static constexpr u32 SHT_GNU_VERNEED = 0x6ffffffe;
|
||||
static constexpr u32 SHT_GNU_VERSYM = 0x6fffffff;
|
||||
static constexpr u32 SHT_X86_64_UNWIND = 0x70000001;
|
||||
static constexpr u32 SHT_ARM_EXIDX = 0x70000001;
|
||||
static constexpr u32 SHT_ARM_ATTRIBUTES = 0x70000003;
|
||||
|
||||
static constexpr u32 SHF_WRITE = 0x1;
|
||||
static constexpr u32 SHF_ALLOC = 0x2;
|
||||
@@ -124,6 +124,7 @@ static constexpr u32 PT_TLS = 7;
|
||||
static constexpr u32 PT_GNU_EH_FRAME = 0x6474e550;
|
||||
static constexpr u32 PT_GNU_STACK = 0x6474e551;
|
||||
static constexpr u32 PT_GNU_RELRO = 0x6474e552;
|
||||
static constexpr u32 PT_ARM_EXIDX = 0x70000001;
|
||||
|
||||
static constexpr u32 PF_X = 1;
|
||||
static constexpr u32 PF_W = 2;
|
||||
@@ -143,8 +144,10 @@ static constexpr u32 ELFCLASS64 = 2;
|
||||
static constexpr u32 EV_CURRENT = 1;
|
||||
|
||||
static constexpr u32 EM_386 = 3;
|
||||
static constexpr u32 EM_ARM = 40;
|
||||
static constexpr u32 EM_X86_64 = 62;
|
||||
static constexpr u32 EM_AARCH64 = 183;
|
||||
static constexpr u32 EM_RISCV = 243;
|
||||
|
||||
static constexpr u32 EI_CLASS = 4;
|
||||
static constexpr u32 EI_DATA = 5;
|
||||
@@ -231,6 +234,21 @@ static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_SHSTK = 2;
|
||||
|
||||
static constexpr u32 ELFCOMPRESS_ZLIB = 1;
|
||||
|
||||
static constexpr u32 EF_ARM_ABI_FLOAT_SOFT = 0x00000200;
|
||||
static constexpr u32 EF_ARM_ABI_FLOAT_HARD = 0x00000400;
|
||||
static constexpr u32 EF_ARM_EABI_VER5 = 0x05000000;
|
||||
|
||||
static constexpr u32 EF_RISCV_RVC = 1;
|
||||
static constexpr u32 EF_RISCV_FLOAT_ABI = 6;
|
||||
static constexpr u32 EF_RISCV_FLOAT_ABI_SOFT = 0;
|
||||
static constexpr u32 EF_RISCV_FLOAT_ABI_SINGLE = 2;
|
||||
static constexpr u32 EF_RISCV_FLOAT_ABI_DOUBLE = 4;
|
||||
static constexpr u32 EF_RISCV_FLOAT_ABI_QUAD = 6;
|
||||
static constexpr u32 EF_RISCV_RVE = 8;
|
||||
static constexpr u32 EF_RISCV_TSO = 16;
|
||||
|
||||
static constexpr u32 STO_RISCV_VARIANT_CC = 0x80;
|
||||
|
||||
static constexpr u32 R_X86_64_NONE = 0;
|
||||
static constexpr u32 R_X86_64_64 = 1;
|
||||
static constexpr u32 R_X86_64_PC32 = 2;
|
||||
@@ -627,6 +645,394 @@ inline std::string rel_to_string<ARM64>(u32 r_type) {
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 R_ARM_NONE = 0x0;
|
||||
static constexpr u32 R_ARM_PC24 = 0x1;
|
||||
static constexpr u32 R_ARM_ABS32 = 0x2;
|
||||
static constexpr u32 R_ARM_REL32 = 0x3;
|
||||
static constexpr u32 R_ARM_LDR_PC_G0 = 0x4;
|
||||
static constexpr u32 R_ARM_ABS16 = 0x5;
|
||||
static constexpr u32 R_ARM_ABS12 = 0x6;
|
||||
static constexpr u32 R_ARM_THM_ABS5 = 0x7;
|
||||
static constexpr u32 R_ARM_ABS8 = 0x8;
|
||||
static constexpr u32 R_ARM_SBREL32 = 0x9;
|
||||
static constexpr u32 R_ARM_THM_CALL = 0xa;
|
||||
static constexpr u32 R_ARM_THM_PC8 = 0xb;
|
||||
static constexpr u32 R_ARM_BREL_ADJ = 0xc;
|
||||
static constexpr u32 R_ARM_TLS_DESC = 0xd;
|
||||
static constexpr u32 R_ARM_THM_SWI8 = 0xe;
|
||||
static constexpr u32 R_ARM_XPC25 = 0xf;
|
||||
static constexpr u32 R_ARM_THM_XPC22 = 0x10;
|
||||
static constexpr u32 R_ARM_TLS_DTPMOD32 = 0x11;
|
||||
static constexpr u32 R_ARM_TLS_DTPOFF32 = 0x12;
|
||||
static constexpr u32 R_ARM_TLS_TPOFF32 = 0x13;
|
||||
static constexpr u32 R_ARM_COPY = 0x14;
|
||||
static constexpr u32 R_ARM_GLOB_DAT = 0x15;
|
||||
static constexpr u32 R_ARM_JUMP_SLOT = 0x16;
|
||||
static constexpr u32 R_ARM_RELATIVE = 0x17;
|
||||
static constexpr u32 R_ARM_GOTOFF32 = 0x18;
|
||||
static constexpr u32 R_ARM_BASE_PREL = 0x19;
|
||||
static constexpr u32 R_ARM_GOT_BREL = 0x1a;
|
||||
static constexpr u32 R_ARM_PLT32 = 0x1b;
|
||||
static constexpr u32 R_ARM_CALL = 0x1c;
|
||||
static constexpr u32 R_ARM_JUMP24 = 0x1d;
|
||||
static constexpr u32 R_ARM_THM_JUMP24 = 0x1e;
|
||||
static constexpr u32 R_ARM_BASE_ABS = 0x1f;
|
||||
static constexpr u32 R_ARM_ALU_PCREL_7_0 = 0x20;
|
||||
static constexpr u32 R_ARM_ALU_PCREL_15_8 = 0x21;
|
||||
static constexpr u32 R_ARM_ALU_PCREL_23_15 = 0x22;
|
||||
static constexpr u32 R_ARM_LDR_SBREL_11_0_NC = 0x23;
|
||||
static constexpr u32 R_ARM_ALU_SBREL_19_12_NC = 0x24;
|
||||
static constexpr u32 R_ARM_ALU_SBREL_27_20_CK = 0x25;
|
||||
static constexpr u32 R_ARM_TARGET1 = 0x26;
|
||||
static constexpr u32 R_ARM_SBREL31 = 0x27;
|
||||
static constexpr u32 R_ARM_V4BX = 0x28;
|
||||
static constexpr u32 R_ARM_TARGET2 = 0x29;
|
||||
static constexpr u32 R_ARM_PREL31 = 0x2a;
|
||||
static constexpr u32 R_ARM_MOVW_ABS_NC = 0x2b;
|
||||
static constexpr u32 R_ARM_MOVT_ABS = 0x2c;
|
||||
static constexpr u32 R_ARM_MOVW_PREL_NC = 0x2d;
|
||||
static constexpr u32 R_ARM_MOVT_PREL = 0x2e;
|
||||
static constexpr u32 R_ARM_THM_MOVW_ABS_NC = 0x2f;
|
||||
static constexpr u32 R_ARM_THM_MOVT_ABS = 0x30;
|
||||
static constexpr u32 R_ARM_THM_MOVW_PREL_NC = 0x31;
|
||||
static constexpr u32 R_ARM_THM_MOVT_PREL = 0x32;
|
||||
static constexpr u32 R_ARM_THM_JUMP19 = 0x33;
|
||||
static constexpr u32 R_ARM_THM_JUMP6 = 0x34;
|
||||
static constexpr u32 R_ARM_THM_ALU_PREL_11_0 = 0x35;
|
||||
static constexpr u32 R_ARM_THM_PC12 = 0x36;
|
||||
static constexpr u32 R_ARM_ABS32_NOI = 0x37;
|
||||
static constexpr u32 R_ARM_REL32_NOI = 0x38;
|
||||
static constexpr u32 R_ARM_ALU_PC_G0_NC = 0x39;
|
||||
static constexpr u32 R_ARM_ALU_PC_G0 = 0x3a;
|
||||
static constexpr u32 R_ARM_ALU_PC_G1_NC = 0x3b;
|
||||
static constexpr u32 R_ARM_ALU_PC_G1 = 0x3c;
|
||||
static constexpr u32 R_ARM_ALU_PC_G2 = 0x3d;
|
||||
static constexpr u32 R_ARM_LDR_PC_G1 = 0x3e;
|
||||
static constexpr u32 R_ARM_LDR_PC_G2 = 0x3f;
|
||||
static constexpr u32 R_ARM_LDRS_PC_G0 = 0x40;
|
||||
static constexpr u32 R_ARM_LDRS_PC_G1 = 0x41;
|
||||
static constexpr u32 R_ARM_LDRS_PC_G2 = 0x42;
|
||||
static constexpr u32 R_ARM_LDC_PC_G0 = 0x43;
|
||||
static constexpr u32 R_ARM_LDC_PC_G1 = 0x44;
|
||||
static constexpr u32 R_ARM_LDC_PC_G2 = 0x45;
|
||||
static constexpr u32 R_ARM_ALU_SB_G0_NC = 0x46;
|
||||
static constexpr u32 R_ARM_ALU_SB_G0 = 0x47;
|
||||
static constexpr u32 R_ARM_ALU_SB_G1_NC = 0x48;
|
||||
static constexpr u32 R_ARM_ALU_SB_G1 = 0x49;
|
||||
static constexpr u32 R_ARM_ALU_SB_G2 = 0x4a;
|
||||
static constexpr u32 R_ARM_LDR_SB_G0 = 0x4b;
|
||||
static constexpr u32 R_ARM_LDR_SB_G1 = 0x4c;
|
||||
static constexpr u32 R_ARM_LDR_SB_G2 = 0x4d;
|
||||
static constexpr u32 R_ARM_LDRS_SB_G0 = 0x4e;
|
||||
static constexpr u32 R_ARM_LDRS_SB_G1 = 0x4f;
|
||||
static constexpr u32 R_ARM_LDRS_SB_G2 = 0x50;
|
||||
static constexpr u32 R_ARM_LDC_SB_G0 = 0x51;
|
||||
static constexpr u32 R_ARM_LDC_SB_G1 = 0x52;
|
||||
static constexpr u32 R_ARM_LDC_SB_G2 = 0x53;
|
||||
static constexpr u32 R_ARM_MOVW_BREL_NC = 0x54;
|
||||
static constexpr u32 R_ARM_MOVT_BREL = 0x55;
|
||||
static constexpr u32 R_ARM_MOVW_BREL = 0x56;
|
||||
static constexpr u32 R_ARM_THM_MOVW_BREL_NC = 0x57;
|
||||
static constexpr u32 R_ARM_THM_MOVT_BREL = 0x58;
|
||||
static constexpr u32 R_ARM_THM_MOVW_BREL = 0x59;
|
||||
static constexpr u32 R_ARM_TLS_GOTDESC = 0x5a;
|
||||
static constexpr u32 R_ARM_TLS_CALL = 0x5b;
|
||||
static constexpr u32 R_ARM_TLS_DESCSEQ = 0x5c;
|
||||
static constexpr u32 R_ARM_THM_TLS_CALL = 0x5d;
|
||||
static constexpr u32 R_ARM_PLT32_ABS = 0x5e;
|
||||
static constexpr u32 R_ARM_GOT_ABS = 0x5f;
|
||||
static constexpr u32 R_ARM_GOT_PREL = 0x60;
|
||||
static constexpr u32 R_ARM_GOT_BREL12 = 0x61;
|
||||
static constexpr u32 R_ARM_GOTOFF12 = 0x62;
|
||||
static constexpr u32 R_ARM_GOTRELAX = 0x63;
|
||||
static constexpr u32 R_ARM_GNU_VTENTRY = 0x64;
|
||||
static constexpr u32 R_ARM_GNU_VTINHERIT = 0x65;
|
||||
static constexpr u32 R_ARM_THM_JUMP11 = 0x66;
|
||||
static constexpr u32 R_ARM_THM_JUMP8 = 0x67;
|
||||
static constexpr u32 R_ARM_TLS_GD32 = 0x68;
|
||||
static constexpr u32 R_ARM_TLS_LDM32 = 0x69;
|
||||
static constexpr u32 R_ARM_TLS_LDO32 = 0x6a;
|
||||
static constexpr u32 R_ARM_TLS_IE32 = 0x6b;
|
||||
static constexpr u32 R_ARM_TLS_LE32 = 0x6c;
|
||||
static constexpr u32 R_ARM_TLS_LDO12 = 0x6d;
|
||||
static constexpr u32 R_ARM_TLS_LE12 = 0x6e;
|
||||
static constexpr u32 R_ARM_TLS_IE12GP = 0x6f;
|
||||
static constexpr u32 R_ARM_PRIVATE_0 = 0x70;
|
||||
static constexpr u32 R_ARM_PRIVATE_1 = 0x71;
|
||||
static constexpr u32 R_ARM_PRIVATE_2 = 0x72;
|
||||
static constexpr u32 R_ARM_PRIVATE_3 = 0x73;
|
||||
static constexpr u32 R_ARM_PRIVATE_4 = 0x74;
|
||||
static constexpr u32 R_ARM_PRIVATE_5 = 0x75;
|
||||
static constexpr u32 R_ARM_PRIVATE_6 = 0x76;
|
||||
static constexpr u32 R_ARM_PRIVATE_7 = 0x77;
|
||||
static constexpr u32 R_ARM_PRIVATE_8 = 0x78;
|
||||
static constexpr u32 R_ARM_PRIVATE_9 = 0x79;
|
||||
static constexpr u32 R_ARM_PRIVATE_10 = 0x7a;
|
||||
static constexpr u32 R_ARM_PRIVATE_11 = 0x7b;
|
||||
static constexpr u32 R_ARM_PRIVATE_12 = 0x7c;
|
||||
static constexpr u32 R_ARM_PRIVATE_13 = 0x7d;
|
||||
static constexpr u32 R_ARM_PRIVATE_14 = 0x7e;
|
||||
static constexpr u32 R_ARM_PRIVATE_15 = 0x7f;
|
||||
static constexpr u32 R_ARM_ME_TOO = 0x80;
|
||||
static constexpr u32 R_ARM_THM_TLS_DESCSEQ16 = 0x81;
|
||||
static constexpr u32 R_ARM_THM_TLS_DESCSEQ32 = 0x82;
|
||||
static constexpr u32 R_ARM_THM_BF16 = 0x88;
|
||||
static constexpr u32 R_ARM_THM_BF12 = 0x89;
|
||||
static constexpr u32 R_ARM_THM_BF18 = 0x8a;
|
||||
static constexpr u32 R_ARM_IRELATIVE = 0xa0;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<ARM32>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
case R_ARM_NONE: return "R_ARM_NONE";
|
||||
case R_ARM_PC24: return "R_ARM_PC24";
|
||||
case R_ARM_ABS32: return "R_ARM_ABS32";
|
||||
case R_ARM_REL32: return "R_ARM_REL32";
|
||||
case R_ARM_LDR_PC_G0: return "R_ARM_LDR_PC_G0";
|
||||
case R_ARM_ABS16: return "R_ARM_ABS16";
|
||||
case R_ARM_ABS12: return "R_ARM_ABS12";
|
||||
case R_ARM_THM_ABS5: return "R_ARM_THM_ABS5";
|
||||
case R_ARM_ABS8: return "R_ARM_ABS8";
|
||||
case R_ARM_SBREL32: return "R_ARM_SBREL32";
|
||||
case R_ARM_THM_CALL: return "R_ARM_THM_CALL";
|
||||
case R_ARM_THM_PC8: return "R_ARM_THM_PC8";
|
||||
case R_ARM_BREL_ADJ: return "R_ARM_BREL_ADJ";
|
||||
case R_ARM_TLS_DESC: return "R_ARM_TLS_DESC";
|
||||
case R_ARM_THM_SWI8: return "R_ARM_THM_SWI8";
|
||||
case R_ARM_XPC25: return "R_ARM_XPC25";
|
||||
case R_ARM_THM_XPC22: return "R_ARM_THM_XPC22";
|
||||
case R_ARM_TLS_DTPMOD32: return "R_ARM_TLS_DTPMOD32";
|
||||
case R_ARM_TLS_DTPOFF32: return "R_ARM_TLS_DTPOFF32";
|
||||
case R_ARM_TLS_TPOFF32: return "R_ARM_TLS_TPOFF32";
|
||||
case R_ARM_COPY: return "R_ARM_COPY";
|
||||
case R_ARM_GLOB_DAT: return "R_ARM_GLOB_DAT";
|
||||
case R_ARM_JUMP_SLOT: return "R_ARM_JUMP_SLOT";
|
||||
case R_ARM_RELATIVE: return "R_ARM_RELATIVE";
|
||||
case R_ARM_GOTOFF32: return "R_ARM_GOTOFF32";
|
||||
case R_ARM_BASE_PREL: return "R_ARM_BASE_PREL";
|
||||
case R_ARM_GOT_BREL: return "R_ARM_GOT_BREL";
|
||||
case R_ARM_PLT32: return "R_ARM_PLT32";
|
||||
case R_ARM_CALL: return "R_ARM_CALL";
|
||||
case R_ARM_JUMP24: return "R_ARM_JUMP24";
|
||||
case R_ARM_THM_JUMP24: return "R_ARM_THM_JUMP24";
|
||||
case R_ARM_BASE_ABS: return "R_ARM_BASE_ABS";
|
||||
case R_ARM_ALU_PCREL_7_0: return "R_ARM_ALU_PCREL_7_0";
|
||||
case R_ARM_ALU_PCREL_15_8: return "R_ARM_ALU_PCREL_15_8";
|
||||
case R_ARM_ALU_PCREL_23_15: return "R_ARM_ALU_PCREL_23_15";
|
||||
case R_ARM_LDR_SBREL_11_0_NC: return "R_ARM_LDR_SBREL_11_0_NC";
|
||||
case R_ARM_ALU_SBREL_19_12_NC: return "R_ARM_ALU_SBREL_19_12_NC";
|
||||
case R_ARM_ALU_SBREL_27_20_CK: return "R_ARM_ALU_SBREL_27_20_CK";
|
||||
case R_ARM_TARGET1: return "R_ARM_TARGET1";
|
||||
case R_ARM_SBREL31: return "R_ARM_SBREL31";
|
||||
case R_ARM_V4BX: return "R_ARM_V4BX";
|
||||
case R_ARM_TARGET2: return "R_ARM_TARGET2";
|
||||
case R_ARM_PREL31: return "R_ARM_PREL31";
|
||||
case R_ARM_MOVW_ABS_NC: return "R_ARM_MOVW_ABS_NC";
|
||||
case R_ARM_MOVT_ABS: return "R_ARM_MOVT_ABS";
|
||||
case R_ARM_MOVW_PREL_NC: return "R_ARM_MOVW_PREL_NC";
|
||||
case R_ARM_MOVT_PREL: return "R_ARM_MOVT_PREL";
|
||||
case R_ARM_THM_MOVW_ABS_NC: return "R_ARM_THM_MOVW_ABS_NC";
|
||||
case R_ARM_THM_MOVT_ABS: return "R_ARM_THM_MOVT_ABS";
|
||||
case R_ARM_THM_MOVW_PREL_NC: return "R_ARM_THM_MOVW_PREL_NC";
|
||||
case R_ARM_THM_MOVT_PREL: return "R_ARM_THM_MOVT_PREL";
|
||||
case R_ARM_THM_JUMP19: return "R_ARM_THM_JUMP19";
|
||||
case R_ARM_THM_JUMP6: return "R_ARM_THM_JUMP6";
|
||||
case R_ARM_THM_ALU_PREL_11_0: return "R_ARM_THM_ALU_PREL_11_0";
|
||||
case R_ARM_THM_PC12: return "R_ARM_THM_PC12";
|
||||
case R_ARM_ABS32_NOI: return "R_ARM_ABS32_NOI";
|
||||
case R_ARM_REL32_NOI: return "R_ARM_REL32_NOI";
|
||||
case R_ARM_ALU_PC_G0_NC: return "R_ARM_ALU_PC_G0_NC";
|
||||
case R_ARM_ALU_PC_G0: return "R_ARM_ALU_PC_G0";
|
||||
case R_ARM_ALU_PC_G1_NC: return "R_ARM_ALU_PC_G1_NC";
|
||||
case R_ARM_ALU_PC_G1: return "R_ARM_ALU_PC_G1";
|
||||
case R_ARM_ALU_PC_G2: return "R_ARM_ALU_PC_G2";
|
||||
case R_ARM_LDR_PC_G1: return "R_ARM_LDR_PC_G1";
|
||||
case R_ARM_LDR_PC_G2: return "R_ARM_LDR_PC_G2";
|
||||
case R_ARM_LDRS_PC_G0: return "R_ARM_LDRS_PC_G0";
|
||||
case R_ARM_LDRS_PC_G1: return "R_ARM_LDRS_PC_G1";
|
||||
case R_ARM_LDRS_PC_G2: return "R_ARM_LDRS_PC_G2";
|
||||
case R_ARM_LDC_PC_G0: return "R_ARM_LDC_PC_G0";
|
||||
case R_ARM_LDC_PC_G1: return "R_ARM_LDC_PC_G1";
|
||||
case R_ARM_LDC_PC_G2: return "R_ARM_LDC_PC_G2";
|
||||
case R_ARM_ALU_SB_G0_NC: return "R_ARM_ALU_SB_G0_NC";
|
||||
case R_ARM_ALU_SB_G0: return "R_ARM_ALU_SB_G0";
|
||||
case R_ARM_ALU_SB_G1_NC: return "R_ARM_ALU_SB_G1_NC";
|
||||
case R_ARM_ALU_SB_G1: return "R_ARM_ALU_SB_G1";
|
||||
case R_ARM_ALU_SB_G2: return "R_ARM_ALU_SB_G2";
|
||||
case R_ARM_LDR_SB_G0: return "R_ARM_LDR_SB_G0";
|
||||
case R_ARM_LDR_SB_G1: return "R_ARM_LDR_SB_G1";
|
||||
case R_ARM_LDR_SB_G2: return "R_ARM_LDR_SB_G2";
|
||||
case R_ARM_LDRS_SB_G0: return "R_ARM_LDRS_SB_G0";
|
||||
case R_ARM_LDRS_SB_G1: return "R_ARM_LDRS_SB_G1";
|
||||
case R_ARM_LDRS_SB_G2: return "R_ARM_LDRS_SB_G2";
|
||||
case R_ARM_LDC_SB_G0: return "R_ARM_LDC_SB_G0";
|
||||
case R_ARM_LDC_SB_G1: return "R_ARM_LDC_SB_G1";
|
||||
case R_ARM_LDC_SB_G2: return "R_ARM_LDC_SB_G2";
|
||||
case R_ARM_MOVW_BREL_NC: return "R_ARM_MOVW_BREL_NC";
|
||||
case R_ARM_MOVT_BREL: return "R_ARM_MOVT_BREL";
|
||||
case R_ARM_MOVW_BREL: return "R_ARM_MOVW_BREL";
|
||||
case R_ARM_THM_MOVW_BREL_NC: return "R_ARM_THM_MOVW_BREL_NC";
|
||||
case R_ARM_THM_MOVT_BREL: return "R_ARM_THM_MOVT_BREL";
|
||||
case R_ARM_THM_MOVW_BREL: return "R_ARM_THM_MOVW_BREL";
|
||||
case R_ARM_TLS_GOTDESC: return "R_ARM_TLS_GOTDESC";
|
||||
case R_ARM_TLS_CALL: return "R_ARM_TLS_CALL";
|
||||
case R_ARM_TLS_DESCSEQ: return "R_ARM_TLS_DESCSEQ";
|
||||
case R_ARM_THM_TLS_CALL: return "R_ARM_THM_TLS_CALL";
|
||||
case R_ARM_PLT32_ABS: return "R_ARM_PLT32_ABS";
|
||||
case R_ARM_GOT_ABS: return "R_ARM_GOT_ABS";
|
||||
case R_ARM_GOT_PREL: return "R_ARM_GOT_PREL";
|
||||
case R_ARM_GOT_BREL12: return "R_ARM_GOT_BREL12";
|
||||
case R_ARM_GOTOFF12: return "R_ARM_GOTOFF12";
|
||||
case R_ARM_GOTRELAX: return "R_ARM_GOTRELAX";
|
||||
case R_ARM_GNU_VTENTRY: return "R_ARM_GNU_VTENTRY";
|
||||
case R_ARM_GNU_VTINHERIT: return "R_ARM_GNU_VTINHERIT";
|
||||
case R_ARM_THM_JUMP11: return "R_ARM_THM_JUMP11";
|
||||
case R_ARM_THM_JUMP8: return "R_ARM_THM_JUMP8";
|
||||
case R_ARM_TLS_GD32: return "R_ARM_TLS_GD32";
|
||||
case R_ARM_TLS_LDM32: return "R_ARM_TLS_LDM32";
|
||||
case R_ARM_TLS_LDO32: return "R_ARM_TLS_LDO32";
|
||||
case R_ARM_TLS_IE32: return "R_ARM_TLS_IE32";
|
||||
case R_ARM_TLS_LE32: return "R_ARM_TLS_LE32";
|
||||
case R_ARM_TLS_LDO12: return "R_ARM_TLS_LDO12";
|
||||
case R_ARM_TLS_LE12: return "R_ARM_TLS_LE12";
|
||||
case R_ARM_TLS_IE12GP: return "R_ARM_TLS_IE12GP";
|
||||
case R_ARM_PRIVATE_0: return "R_ARM_PRIVATE_0";
|
||||
case R_ARM_PRIVATE_1: return "R_ARM_PRIVATE_1";
|
||||
case R_ARM_PRIVATE_2: return "R_ARM_PRIVATE_2";
|
||||
case R_ARM_PRIVATE_3: return "R_ARM_PRIVATE_3";
|
||||
case R_ARM_PRIVATE_4: return "R_ARM_PRIVATE_4";
|
||||
case R_ARM_PRIVATE_5: return "R_ARM_PRIVATE_5";
|
||||
case R_ARM_PRIVATE_6: return "R_ARM_PRIVATE_6";
|
||||
case R_ARM_PRIVATE_7: return "R_ARM_PRIVATE_7";
|
||||
case R_ARM_PRIVATE_8: return "R_ARM_PRIVATE_8";
|
||||
case R_ARM_PRIVATE_9: return "R_ARM_PRIVATE_9";
|
||||
case R_ARM_PRIVATE_10: return "R_ARM_PRIVATE_10";
|
||||
case R_ARM_PRIVATE_11: return "R_ARM_PRIVATE_11";
|
||||
case R_ARM_PRIVATE_12: return "R_ARM_PRIVATE_12";
|
||||
case R_ARM_PRIVATE_13: return "R_ARM_PRIVATE_13";
|
||||
case R_ARM_PRIVATE_14: return "R_ARM_PRIVATE_14";
|
||||
case R_ARM_PRIVATE_15: return "R_ARM_PRIVATE_15";
|
||||
case R_ARM_ME_TOO: return "R_ARM_ME_TOO";
|
||||
case R_ARM_THM_TLS_DESCSEQ16: return "R_ARM_THM_TLS_DESCSEQ16";
|
||||
case R_ARM_THM_TLS_DESCSEQ32: return "R_ARM_THM_TLS_DESCSEQ32";
|
||||
case R_ARM_THM_BF16: return "R_ARM_THM_BF16";
|
||||
case R_ARM_THM_BF12: return "R_ARM_THM_BF12";
|
||||
case R_ARM_THM_BF18: return "R_ARM_THM_BF18";
|
||||
case R_ARM_IRELATIVE: return "R_ARM_IRELATIVE";
|
||||
}
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 R_RISCV_NONE = 0;
|
||||
static constexpr u32 R_RISCV_32 = 1;
|
||||
static constexpr u32 R_RISCV_64 = 2;
|
||||
static constexpr u32 R_RISCV_RELATIVE = 3;
|
||||
static constexpr u32 R_RISCV_COPY = 4;
|
||||
static constexpr u32 R_RISCV_JUMP_SLOT = 5;
|
||||
static constexpr u32 R_RISCV_TLS_DTPMOD32 = 6;
|
||||
static constexpr u32 R_RISCV_TLS_DTPMOD64 = 7;
|
||||
static constexpr u32 R_RISCV_TLS_DTPREL32 = 8;
|
||||
static constexpr u32 R_RISCV_TLS_DTPREL64 = 9;
|
||||
static constexpr u32 R_RISCV_TLS_TPREL32 = 10;
|
||||
static constexpr u32 R_RISCV_TLS_TPREL64 = 11;
|
||||
static constexpr u32 R_RISCV_BRANCH = 16;
|
||||
static constexpr u32 R_RISCV_JAL = 17;
|
||||
static constexpr u32 R_RISCV_CALL = 18;
|
||||
static constexpr u32 R_RISCV_CALL_PLT = 19;
|
||||
static constexpr u32 R_RISCV_GOT_HI20 = 20;
|
||||
static constexpr u32 R_RISCV_TLS_GOT_HI20 = 21;
|
||||
static constexpr u32 R_RISCV_TLS_GD_HI20 = 22;
|
||||
static constexpr u32 R_RISCV_PCREL_HI20 = 23;
|
||||
static constexpr u32 R_RISCV_PCREL_LO12_I = 24;
|
||||
static constexpr u32 R_RISCV_PCREL_LO12_S = 25;
|
||||
static constexpr u32 R_RISCV_HI20 = 26;
|
||||
static constexpr u32 R_RISCV_LO12_I = 27;
|
||||
static constexpr u32 R_RISCV_LO12_S = 28;
|
||||
static constexpr u32 R_RISCV_TPREL_HI20 = 29;
|
||||
static constexpr u32 R_RISCV_TPREL_LO12_I = 30;
|
||||
static constexpr u32 R_RISCV_TPREL_LO12_S = 31;
|
||||
static constexpr u32 R_RISCV_TPREL_ADD = 32;
|
||||
static constexpr u32 R_RISCV_ADD8 = 33;
|
||||
static constexpr u32 R_RISCV_ADD16 = 34;
|
||||
static constexpr u32 R_RISCV_ADD32 = 35;
|
||||
static constexpr u32 R_RISCV_ADD64 = 36;
|
||||
static constexpr u32 R_RISCV_SUB8 = 37;
|
||||
static constexpr u32 R_RISCV_SUB16 = 38;
|
||||
static constexpr u32 R_RISCV_SUB32 = 39;
|
||||
static constexpr u32 R_RISCV_SUB64 = 40;
|
||||
static constexpr u32 R_RISCV_GNU_VTINHERIT = 41;
|
||||
static constexpr u32 R_RISCV_GNU_VTENTRY = 42;
|
||||
static constexpr u32 R_RISCV_ALIGN = 43;
|
||||
static constexpr u32 R_RISCV_RVC_BRANCH = 44;
|
||||
static constexpr u32 R_RISCV_RVC_JUMP = 45;
|
||||
static constexpr u32 R_RISCV_RVC_LUI = 46;
|
||||
static constexpr u32 R_RISCV_RELAX = 51;
|
||||
static constexpr u32 R_RISCV_SUB6 = 52;
|
||||
static constexpr u32 R_RISCV_SET6 = 53;
|
||||
static constexpr u32 R_RISCV_SET8 = 54;
|
||||
static constexpr u32 R_RISCV_SET16 = 55;
|
||||
static constexpr u32 R_RISCV_SET32 = 56;
|
||||
static constexpr u32 R_RISCV_32_PCREL = 57;
|
||||
static constexpr u32 R_RISCV_IRELATIVE = 58;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<RISCV64>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
case R_RISCV_NONE: return "R_RISCV_NONE";
|
||||
case R_RISCV_32: return "R_RISCV_32";
|
||||
case R_RISCV_64: return "R_RISCV_64";
|
||||
case R_RISCV_RELATIVE: return "R_RISCV_RELATIVE";
|
||||
case R_RISCV_COPY: return "R_RISCV_COPY";
|
||||
case R_RISCV_JUMP_SLOT: return "R_RISCV_JUMP_SLOT";
|
||||
case R_RISCV_TLS_DTPMOD32: return "R_RISCV_TLS_DTPMOD32";
|
||||
case R_RISCV_TLS_DTPMOD64: return "R_RISCV_TLS_DTPMOD64";
|
||||
case R_RISCV_TLS_DTPREL32: return "R_RISCV_TLS_DTPREL32";
|
||||
case R_RISCV_TLS_DTPREL64: return "R_RISCV_TLS_DTPREL64";
|
||||
case R_RISCV_TLS_TPREL32: return "R_RISCV_TLS_TPREL32";
|
||||
case R_RISCV_TLS_TPREL64: return "R_RISCV_TLS_TPREL64";
|
||||
case R_RISCV_BRANCH: return "R_RISCV_BRANCH";
|
||||
case R_RISCV_JAL: return "R_RISCV_JAL";
|
||||
case R_RISCV_CALL: return "R_RISCV_CALL";
|
||||
case R_RISCV_CALL_PLT: return "R_RISCV_CALL_PLT";
|
||||
case R_RISCV_GOT_HI20: return "R_RISCV_GOT_HI20";
|
||||
case R_RISCV_TLS_GOT_HI20: return "R_RISCV_TLS_GOT_HI20";
|
||||
case R_RISCV_TLS_GD_HI20: return "R_RISCV_TLS_GD_HI20";
|
||||
case R_RISCV_PCREL_HI20: return "R_RISCV_PCREL_HI20";
|
||||
case R_RISCV_PCREL_LO12_I: return "R_RISCV_PCREL_LO12_I";
|
||||
case R_RISCV_PCREL_LO12_S: return "R_RISCV_PCREL_LO12_S";
|
||||
case R_RISCV_HI20: return "R_RISCV_HI20";
|
||||
case R_RISCV_LO12_I: return "R_RISCV_LO12_I";
|
||||
case R_RISCV_LO12_S: return "R_RISCV_LO12_S";
|
||||
case R_RISCV_TPREL_HI20: return "R_RISCV_TPREL_HI20";
|
||||
case R_RISCV_TPREL_LO12_I: return "R_RISCV_TPREL_LO12_I";
|
||||
case R_RISCV_TPREL_LO12_S: return "R_RISCV_TPREL_LO12_S";
|
||||
case R_RISCV_TPREL_ADD: return "R_RISCV_TPREL_ADD";
|
||||
case R_RISCV_ADD8: return "R_RISCV_ADD8";
|
||||
case R_RISCV_ADD16: return "R_RISCV_ADD16";
|
||||
case R_RISCV_ADD32: return "R_RISCV_ADD32";
|
||||
case R_RISCV_ADD64: return "R_RISCV_ADD64";
|
||||
case R_RISCV_SUB8: return "R_RISCV_SUB8";
|
||||
case R_RISCV_SUB16: return "R_RISCV_SUB16";
|
||||
case R_RISCV_SUB32: return "R_RISCV_SUB32";
|
||||
case R_RISCV_SUB64: return "R_RISCV_SUB64";
|
||||
case R_RISCV_GNU_VTINHERIT: return "R_RISCV_GNU_VTINHERIT";
|
||||
case R_RISCV_GNU_VTENTRY: return "R_RISCV_GNU_VTENTRY";
|
||||
case R_RISCV_ALIGN: return "R_RISCV_ALIGN";
|
||||
case R_RISCV_RVC_BRANCH: return "R_RISCV_RVC_BRANCH";
|
||||
case R_RISCV_RVC_JUMP: return "R_RISCV_RVC_JUMP";
|
||||
case R_RISCV_RVC_LUI: return "R_RISCV_RVC_LUI";
|
||||
case R_RISCV_RELAX: return "R_RISCV_RELAX";
|
||||
case R_RISCV_SUB6: return "R_RISCV_SUB6";
|
||||
case R_RISCV_SET6: return "R_RISCV_SET6";
|
||||
case R_RISCV_SET8: return "R_RISCV_SET8";
|
||||
case R_RISCV_SET16: return "R_RISCV_SET16";
|
||||
case R_RISCV_SET32: return "R_RISCV_SET32";
|
||||
case R_RISCV_32_PCREL: return "R_RISCV_32_PCREL";
|
||||
case R_RISCV_IRELATIVE: return "R_RISCV_IRELATIVE";
|
||||
}
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 DW_EH_PE_absptr = 0;
|
||||
static constexpr u32 DW_EH_PE_omit = 0xff;
|
||||
static constexpr u32 DW_EH_PE_uleb128 = 0x01;
|
||||
@@ -644,6 +1050,38 @@ static constexpr u32 DW_EH_PE_datarel = 0x30;
|
||||
static constexpr u32 DW_EH_PE_funcrel = 0x40;
|
||||
static constexpr u32 DW_EH_PE_aligned = 0x50;
|
||||
|
||||
static constexpr u32 DW_AT_low_pc = 0x11;
|
||||
static constexpr u32 DW_AT_high_pc = 0x12;
|
||||
static constexpr u32 DW_AT_producer = 0x25;
|
||||
static constexpr u32 DW_AT_ranges = 0x55;
|
||||
|
||||
static constexpr u32 DW_TAG_compile_unit = 0x11;
|
||||
|
||||
static constexpr u32 DW_FORM_addr = 0x01;
|
||||
static constexpr u32 DW_FORM_block2 = 0x03;
|
||||
static constexpr u32 DW_FORM_block4 = 0x04;
|
||||
static constexpr u32 DW_FORM_data2 = 0x05;
|
||||
static constexpr u32 DW_FORM_data4 = 0x06;
|
||||
static constexpr u32 DW_FORM_data8 = 0x07;
|
||||
static constexpr u32 DW_FORM_string = 0x08;
|
||||
static constexpr u32 DW_FORM_block = 0x09;
|
||||
static constexpr u32 DW_FORM_block1 = 0x0a;
|
||||
static constexpr u32 DW_FORM_data1 = 0x0b;
|
||||
static constexpr u32 DW_FORM_flag = 0x0c;
|
||||
static constexpr u32 DW_FORM_sdata = 0x0d;
|
||||
static constexpr u32 DW_FORM_strp = 0x0e;
|
||||
static constexpr u32 DW_FORM_udata = 0x0f;
|
||||
static constexpr u32 DW_FORM_ref_addr = 0x10;
|
||||
static constexpr u32 DW_FORM_ref1 = 0x11;
|
||||
static constexpr u32 DW_FORM_ref2 = 0x12;
|
||||
static constexpr u32 DW_FORM_ref4 = 0x13;
|
||||
static constexpr u32 DW_FORM_ref8 = 0x14;
|
||||
static constexpr u32 DW_FORM_ref_udata = 0x15;
|
||||
static constexpr u32 DW_FORM_indirect = 0x16;
|
||||
static constexpr u32 DW_FORM_sec_offset = 0x17;
|
||||
static constexpr u32 DW_FORM_exprloc = 0x18;
|
||||
static constexpr u32 DW_FORM_flag_present = 0x19;
|
||||
|
||||
struct Elf64Sym {
|
||||
bool is_defined() const { return !is_undef(); }
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
@@ -867,6 +1305,7 @@ struct X86_64 {
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_X86_64_64;
|
||||
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
|
||||
@@ -880,6 +1319,7 @@ struct X86_64 {
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<X86_64> : public Elf64Sym {};
|
||||
@@ -897,6 +1337,7 @@ struct I386 {
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_386_32;
|
||||
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
|
||||
@@ -910,6 +1351,7 @@ struct I386 {
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<I386> : public Elf32Sym {};
|
||||
@@ -927,6 +1369,7 @@ struct ARM64 {
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_AARCH64_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_AARCH64_ABS64;
|
||||
static constexpr u32 R_RELATIVE = R_AARCH64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_AARCH64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_AARCH64_TLS_DTPREL64;
|
||||
@@ -940,6 +1383,7 @@ struct ARM64 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<ARM64> : public Elf64Sym {};
|
||||
@@ -950,4 +1394,67 @@ template <> struct ElfRel<ARM64> : public Elf64Rela {};
|
||||
template <> struct ElfDyn<ARM64> : public Elf64Dyn {};
|
||||
template <> struct ElfChdr<ARM64> : public Elf64Chdr {};
|
||||
|
||||
struct ARM32 {
|
||||
using WordTy = u32;
|
||||
|
||||
static constexpr u32 R_NONE = R_ARM_NONE;
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_ARM_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ARM_ABS32;
|
||||
static constexpr u32 R_RELATIVE = R_ARM_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_ARM_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_ARM_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_ARM_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_ARM_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_ARM_TLS_DESC;
|
||||
|
||||
static constexpr u32 word_size = 4;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_ARM;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<ARM32> : public Elf32Sym {};
|
||||
template <> struct ElfShdr<ARM32> : public Elf32Shdr {};
|
||||
template <> struct ElfEhdr<ARM32> : public Elf32Ehdr {};
|
||||
template <> struct ElfPhdr<ARM32> : public Elf32Phdr {};
|
||||
template <> struct ElfRel<ARM32> : public Elf32Rel {};
|
||||
template <> struct ElfDyn<ARM32> : public Elf32Dyn {};
|
||||
template <> struct ElfChdr<ARM32> : public Elf32Chdr {};
|
||||
|
||||
struct RISCV64 {
|
||||
using WordTy = u64;
|
||||
|
||||
static constexpr u32 R_NONE = R_RISCV_NONE;
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_64;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
|
||||
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = false;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<RISCV64> : public Elf64Sym {};
|
||||
template <> struct ElfShdr<RISCV64> : public Elf64Shdr {};
|
||||
template <> struct ElfEhdr<RISCV64> : public Elf64Ehdr {};
|
||||
template <> struct ElfPhdr<RISCV64> : public Elf64Phdr {};
|
||||
template <> struct ElfRel<RISCV64> : public Elf64Rela {};
|
||||
template <> struct ElfDyn<RISCV64> : public Elf64Dyn {};
|
||||
template <> struct ElfChdr<RISCV64> : public Elf64Chdr {};
|
||||
|
||||
} // namespace mold::elf
|
||||
+29
-20
@@ -11,24 +11,28 @@ namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static bool is_init_fini(const InputSection<E> &isec) {
|
||||
return isec.shdr.sh_type == SHT_INIT_ARRAY ||
|
||||
isec.shdr.sh_type == SHT_FINI_ARRAY ||
|
||||
isec.shdr.sh_type == SHT_PREINIT_ARRAY ||
|
||||
isec.name().starts_with(".ctors") ||
|
||||
isec.name().starts_with(".dtors") ||
|
||||
isec.name().starts_with(".init") ||
|
||||
isec.name().starts_with(".fini");
|
||||
u32 type = isec.shdr().sh_type;
|
||||
std::string_view name = isec.name();
|
||||
|
||||
return type == SHT_INIT_ARRAY ||
|
||||
type == SHT_FINI_ARRAY ||
|
||||
type == SHT_PREINIT_ARRAY ||
|
||||
(std::is_same_v<E, ARM32> && type == SHT_ARM_EXIDX) ||
|
||||
name.starts_with(".ctors") ||
|
||||
name.starts_with(".dtors") ||
|
||||
name.starts_with(".init") ||
|
||||
name.starts_with(".fini");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool mark_section(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && !isec->is_visited.exchange(true);
|
||||
return isec && isec->is_alive && !isec->extra().is_visited.exchange(true);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
assert(isec->extra().is_visited);
|
||||
|
||||
// A relocation can refer either a section fragment (i.e. a piece of
|
||||
// string in a mergeable string section) or a symbol. Mark all
|
||||
@@ -41,10 +45,10 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (ElfRel<E> &rel : fde.get_rels().subspan(1))
|
||||
for (ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->input_section))
|
||||
feeder.add(sym->input_section);
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
@@ -56,15 +60,15 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mark_section(sym.input_section))
|
||||
if (!mark_section(sym.get_input_section()))
|
||||
continue;
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.input_section, feeder, depth + 1);
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.input_section);
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +88,7 @@ collect_root_set(Context<E> &ctx) {
|
||||
if (SectionFragment<E> *frag = sym->get_frag())
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
else
|
||||
enqueue_section(sym->input_section);
|
||||
enqueue_section(sym->get_input_section());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,11 +102,11 @@ collect_root_set(Context<E> &ctx) {
|
||||
// reduce the amount of non-memory-mapped segments, you should
|
||||
// use `strip` command, compile without debug info or use
|
||||
// -strip-all linker option.
|
||||
if (!(isec->shdr.sh_flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
if (!(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
isec->extra().is_visited = true;
|
||||
|
||||
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
|
||||
isec->shdr.sh_type == SHT_NOTE)
|
||||
isec->shdr().sh_type == SHT_NOTE)
|
||||
enqueue_section(isec.get());
|
||||
}
|
||||
});
|
||||
@@ -155,7 +159,7 @@ static void sweep(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && !isec->is_visited) {
|
||||
if (isec && isec->is_alive && !isec->extra().is_visited) {
|
||||
if (ctx.arg.print_gc_sections)
|
||||
SyncOut(ctx) << "removing unused section " << *isec;
|
||||
isec->kill();
|
||||
@@ -184,6 +188,9 @@ template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->extras.resize(file->sections.size());
|
||||
|
||||
mark_nonalloc_fragments(ctx);
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
|
||||
@@ -197,5 +204,7 @@ void gc_sections(Context<E> &ctx) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+53
-48
@@ -53,6 +53,7 @@
|
||||
// thread and even better with multiple threads.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <array>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
@@ -62,13 +63,6 @@
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define COMMON_DIGEST_FOR_OPENSSL
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
#else
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
static constexpr int64_t HASH_SIZE = 16;
|
||||
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
@@ -105,7 +99,7 @@ static void uniquify_cies(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static bool is_eligible(InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr;
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
@@ -139,7 +133,7 @@ static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
|
||||
return false;
|
||||
|
||||
for (FdeRecord<E> &fde : isec.get_fdes())
|
||||
if (fde.get_rels().size() > 1)
|
||||
if (fde.get_rels(isec.file).size() > 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -151,10 +145,10 @@ static u64 combine_hash(u64 a, u64 b) {
|
||||
|
||||
template <typename E>
|
||||
struct LeafHasher {
|
||||
size_t operator()(const InputSection<E> *isec) const {
|
||||
size_t operator()(InputSection<E> *isec) const {
|
||||
u64 h = hash_string(isec->contents);
|
||||
for (FdeRecord<E> &fde : isec->get_fdes()) {
|
||||
u64 h2 = hash_string(fde.get_contents().substr(8));
|
||||
u64 h2 = hash_string(fde.get_contents(isec->file).substr(8));
|
||||
h = combine_hash(h, h2);
|
||||
}
|
||||
return h;
|
||||
@@ -163,7 +157,7 @@ struct LeafHasher {
|
||||
|
||||
template <typename E>
|
||||
struct LeafEq {
|
||||
bool operator()(const InputSection<E> *a, const InputSection<E> *b) const {
|
||||
bool operator()(InputSection<E> *a, InputSection<E> *b) const {
|
||||
if (a->contents != b->contents)
|
||||
return false;
|
||||
|
||||
@@ -174,7 +168,8 @@ struct LeafEq {
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++)
|
||||
if (x[i].get_contents().substr(8) != y[i].get_contents().substr(8))
|
||||
if (x[i].get_contents(a->file).substr(8) !=
|
||||
y[i].get_contents(b->file).substr(8))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -203,23 +198,23 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
if (is_leaf(ctx, *isec)) {
|
||||
leaf++;
|
||||
isec->icf_leaf = true;
|
||||
isec->extra().icf_leaf = true;
|
||||
auto [it, inserted] = map.insert({isec.get(), isec.get()});
|
||||
if (!inserted && isec->get_priority() < it->second->get_priority())
|
||||
it->second = isec.get();
|
||||
} else {
|
||||
eligible++;
|
||||
isec->icf_eligible = true;
|
||||
isec->extra().icf_eligible = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections) {
|
||||
if (isec && isec->is_alive && isec->icf_leaf) {
|
||||
if (isec && isec->is_alive && isec->extra().icf_leaf) {
|
||||
auto it = map.find(isec.get());
|
||||
assert(it != map.end());
|
||||
isec->leader = it->second;
|
||||
isec->extra().leader = it->second;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -240,7 +235,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
InputSection<E> *isec = sym.input_section;
|
||||
InputSection<E> *isec = sym.get_input_section();
|
||||
|
||||
if (!sym.file) {
|
||||
hash('1');
|
||||
@@ -250,10 +245,10 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash((u64)frag);
|
||||
} else if (!isec) {
|
||||
hash('3');
|
||||
} else if (isec->leader) {
|
||||
} else if (isec->extra().leader) {
|
||||
hash('4');
|
||||
hash((u64)isec->leader);
|
||||
} else if (isec->icf_eligible) {
|
||||
hash((u64)isec->extra().leader);
|
||||
} else if (isec->extra().icf_eligible) {
|
||||
hash('5');
|
||||
} else {
|
||||
hash('6');
|
||||
@@ -263,24 +258,24 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
};
|
||||
|
||||
hash_string(isec.contents);
|
||||
hash(isec.shdr.sh_flags);
|
||||
hash(isec.shdr().sh_flags);
|
||||
hash(isec.get_fdes().size());
|
||||
hash(isec.get_rels(ctx).size());
|
||||
|
||||
for (FdeRecord<E> &fde : isec.get_fdes()) {
|
||||
hash(fde.cie->icf_idx);
|
||||
hash(isec.file.cies[fde.cie_idx].icf_idx);
|
||||
|
||||
// Bytes 0 to 4 contain the length of this record, and
|
||||
// bytes 4 to 8 contain an offset to CIE.
|
||||
hash_string(fde.get_contents().substr(8));
|
||||
hash_string(fde.get_contents(isec.file).substr(8));
|
||||
|
||||
hash(fde.get_rels().size());
|
||||
hash(fde.get_rels(isec.file).size());
|
||||
|
||||
for (ElfRel<E> &rel : fde.get_rels().subspan(1)) {
|
||||
for (ElfRel<E> &rel : fde.get_rels(isec.file).subspan(1)) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
hash(rel.r_type);
|
||||
hash(rel.r_offset - fde.input_offset);
|
||||
hash(fde.cie->input_section.get_addend(rel));
|
||||
hash(isec.file.cies[fde.cie_idx].input_section.get_addend(rel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +309,7 @@ static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
|
||||
if (isec && isec->is_alive && isec->icf_eligible)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
num_sections[i]++;
|
||||
});
|
||||
|
||||
@@ -329,12 +324,12 @@ static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
i64 idx = section_indices[i];
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
|
||||
if (isec && isec->is_alive && isec->icf_eligible)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
sections[idx++] = isec.get();
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
sections[i]->icf_idx = i;
|
||||
sections[i]->extra().icf_idx = i;
|
||||
});
|
||||
|
||||
return sections;
|
||||
@@ -359,12 +354,15 @@ static void gather_edges(Context<E> &ctx,
|
||||
std::vector<u32> &edge_indices) {
|
||||
Timer t(ctx, "gather_edges");
|
||||
|
||||
if (sections.empty())
|
||||
return;
|
||||
|
||||
std::vector<i64> num_edges(sections.size());
|
||||
edge_indices.resize(sections.size());
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
InputSection<E> &isec = *sections[i];
|
||||
assert(isec.icf_eligible);
|
||||
assert(isec.extra().icf_eligible);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
@@ -373,9 +371,10 @@ static void gather_edges(Context<E> &ctx,
|
||||
} else {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag() && sym.input_section &&
|
||||
sym.input_section->icf_eligible)
|
||||
num_edges[i]++;
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
num_edges[i]++;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -395,9 +394,10 @@ static void gather_edges(Context<E> &ctx,
|
||||
frag_idx++;
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag() && sym.input_section &&
|
||||
sym.input_section->icf_eligible)
|
||||
edges[idx++] = sym.input_section->icf_idx;
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
edges[idx++] = isec->extra().icf_idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -458,11 +458,11 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->leader) {
|
||||
if (isec.get() == isec->leader)
|
||||
if (isec && isec->is_alive && isec->extra().leader) {
|
||||
if (isec.get() == isec->extra().leader)
|
||||
leaders.push_back(isec.get());
|
||||
else
|
||||
map.insert({isec->leader, isec.get()});
|
||||
map.insert({isec->extra().leader, isec.get()});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -496,6 +496,9 @@ template <typename E>
|
||||
void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "icf");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->extras.resize(file->sections.size());
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
|
||||
@@ -555,11 +558,11 @@ void icf_sections(Context<E> &ctx) {
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
auto it = map->find(digest[i]);
|
||||
assert(it != map->end());
|
||||
sections[i]->leader = it->second;
|
||||
sections[i]->extra().leader = it->second;
|
||||
});
|
||||
|
||||
// Since free'ing the map is slow, postpone it.
|
||||
ctx.on_exit.push_back([=]() { delete map; });
|
||||
ctx.on_exit.push_back([=] { delete map; });
|
||||
}
|
||||
|
||||
if (ctx.arg.print_icf_sections)
|
||||
@@ -570,12 +573,12 @@ void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "reassign");
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
InputSection<E> *isec = sym->input_section;
|
||||
if (isec && isec->leader && isec->leader != isec) {
|
||||
sym->input_section = isec->leader;
|
||||
isec->kill();
|
||||
if (sym->file == file) {
|
||||
InputSection<E> *isec = sym->get_input_section();
|
||||
if (isec && isec->extra().leader && isec->extra().leader != isec) {
|
||||
isec->kill();
|
||||
isec->killed_by_icf = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -588,5 +591,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+287
-212
@@ -3,7 +3,6 @@
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
#include <unistd.h>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
@@ -48,13 +47,12 @@ ElfShdr<E> *InputFile<E>::find_section(i64 type) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputFile<E>::clear_symbols(Context<E> &ctx) {
|
||||
assert(!is_alive);
|
||||
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : get_global_syms()) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
if (sym->file == this) {
|
||||
sym->file = nullptr;
|
||||
sym->input_section = nullptr;
|
||||
sym->shndx = 0;
|
||||
sym->value = -1;
|
||||
sym->sym_idx = -1;
|
||||
sym->ver_idx = 0;
|
||||
@@ -72,15 +70,12 @@ ObjectFile<E>::ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
this->is_alive = !is_in_lib;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E>::ObjectFile() {}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *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.push_back(std::unique_ptr<ObjectFile<E>>(obj));
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -121,68 +116,6 @@ u32 ObjectFile<E>::read_note_gnu_property(Context<E> &ctx,
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::pair<std::string_view, const ElfShdr<E> *>
|
||||
ObjectFile<E>::uncompress_contents(Context<E> &ctx, const ElfShdr<E> &shdr,
|
||||
std::string_view name) {
|
||||
if (shdr.sh_type == SHT_NOBITS)
|
||||
return {{}, &shdr};
|
||||
|
||||
auto do_uncompress = [&](std::string_view data, u64 size) {
|
||||
u8 *buf = new u8[size];
|
||||
ctx.string_pool.push_back(std::unique_ptr<u8[]>(buf));
|
||||
|
||||
unsigned long size2 = size;
|
||||
if (uncompress(buf, &size2, (u8 *)&data[0], data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": " << name << ": uncompress failed";
|
||||
if (size != size2)
|
||||
Fatal(ctx) << *this << ": " << name << ": uncompress: invalid size";
|
||||
return std::string_view((char *)buf, size);
|
||||
};
|
||||
|
||||
auto copy_shdr = [&](const ElfShdr<E> &shdr) {
|
||||
ElfShdr<E> *ret = new ElfShdr<E>;
|
||||
ctx.shdr_pool.push_back(std::unique_ptr<ElfShdr<E>>(ret));
|
||||
*ret = shdr;
|
||||
return ret;
|
||||
};
|
||||
|
||||
if (name.starts_with(".zdebug")) {
|
||||
// Old-style compressed section
|
||||
std::string_view data = this->get_string(ctx, shdr);
|
||||
if (!data.starts_with("ZLIB") || data.size() <= 12)
|
||||
Fatal(ctx) << *this << ": " << name << ": corrupted compressed section";
|
||||
u64 size = *(ubig64 *)&data[4];
|
||||
std::string_view contents = do_uncompress(data.substr(12), size);
|
||||
|
||||
ElfShdr<E> *shdr2 = copy_shdr(shdr);
|
||||
shdr2->sh_size = size;
|
||||
return {contents, shdr2};
|
||||
}
|
||||
|
||||
if (shdr.sh_flags & SHF_COMPRESSED) {
|
||||
// New-style compressed section
|
||||
std::string_view data = this->get_string(ctx, shdr);
|
||||
if (data.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": " << name << ": corrupted compressed section";
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&data[0];
|
||||
data = data.substr(sizeof(ElfChdr<E>));
|
||||
|
||||
if (hdr.ch_type != ELFCOMPRESS_ZLIB)
|
||||
Fatal(ctx) << *this << ": " << name << ": unsupported compression type";
|
||||
|
||||
ElfShdr<E> *shdr2 = copy_shdr(shdr);
|
||||
shdr2->sh_flags &= ~(u64)(SHF_COMPRESSED);
|
||||
shdr2->sh_size = hdr.ch_size;
|
||||
shdr2->sh_addralign = hdr.ch_addralign;
|
||||
|
||||
std::string_view contents = do_uncompress(data, hdr.ch_size);
|
||||
return {contents, shdr2};
|
||||
}
|
||||
|
||||
return {this->get_string(ctx, shdr), &shdr};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// Read sections
|
||||
@@ -224,10 +157,25 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
case SHT_NULL:
|
||||
case SHT_ARM_ATTRIBUTES:
|
||||
break;
|
||||
default: {
|
||||
std::string_view name = this->shstrtab.data() + shdr.sh_name;
|
||||
if (name == ".note.GNU-stack" || name.starts_with(".gnu.warning."))
|
||||
|
||||
// .note.GNU-stack section controls executable-ness of the stack
|
||||
// area in GNU linkers. We ignore that section because silently
|
||||
// making the stack area executable is too dangerous. Tell our
|
||||
// users about the difference if that matters.
|
||||
if (name == ".note.GNU-stack") {
|
||||
if (shdr.sh_flags & SHF_EXECINSTR)
|
||||
Warn(ctx) << *this << ": this file may cause a segmentation"
|
||||
" fault because it requires an executable stack. See"
|
||||
" https://github.com/rui314/mold/tree/main/docs/execstack.md"
|
||||
" for more info.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.starts_with(".gnu.warning."))
|
||||
continue;
|
||||
|
||||
if (name == ".note.gnu.property") {
|
||||
@@ -244,17 +192,37 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (name == ".gnu.linkonce.d.DW.ref.__gxx_personality_v0")
|
||||
continue;
|
||||
|
||||
// Ignore debug sections if --strip-all or --strip-debug is given.
|
||||
if ((ctx.arg.strip_all || ctx.arg.strip_debug) &&
|
||||
is_debug_section(shdr, name))
|
||||
continue;
|
||||
|
||||
std::string_view contents;
|
||||
const ElfShdr<E> *shdr2;
|
||||
std::tie(contents, shdr2) = uncompress_contents(ctx, shdr, name);
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
|
||||
this->sections[i] =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, *shdr2, name,
|
||||
contents, i);
|
||||
// Save debug sections for --gdb-index.
|
||||
if (ctx.arg.gdb_index) {
|
||||
InputSection<E> *isec = this->sections[i].get();
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = isec;
|
||||
if (name == ".debug_abbrev")
|
||||
debug_abbrev = isec;
|
||||
if (name == ".debug_ranges")
|
||||
debug_ranges = isec;
|
||||
|
||||
// If --gdb-index is given, contents of .debug_gnu_pubnames and
|
||||
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
|
||||
// in an output file is just a waste of space.
|
||||
if (name == ".debug_gnu_pubnames") {
|
||||
debug_pubnames = isec;
|
||||
isec->is_alive = false;
|
||||
}
|
||||
|
||||
if (name == ".debug_gnu_pubtypes") {
|
||||
debug_pubtypes = isec;
|
||||
isec->is_alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
static Counter counter("regular_sections");
|
||||
counter++;
|
||||
@@ -276,12 +244,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (std::unique_ptr<InputSection<E>> &target = sections[shdr.sh_info]) {
|
||||
assert(target->relsec_idx == -1);
|
||||
target->relsec_idx = i;
|
||||
|
||||
if (target->shdr.sh_flags & SHF_ALLOC) {
|
||||
i64 size = shdr.sh_size / sizeof(ElfRel<E>);
|
||||
target->needs_dynrel.resize(size);
|
||||
target->needs_baserel.resize(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,13 +254,9 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];
|
||||
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
|
||||
read_ehframe(ctx, *isec);
|
||||
isec->is_ehframe = true;
|
||||
isec->is_alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (FdeRecord<E> &fde : fdes)
|
||||
fde.cie = &cies[fde.cie_idx];
|
||||
}
|
||||
|
||||
// .eh_frame contains data records explaining how to handle exceptions.
|
||||
@@ -336,14 +294,8 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
|
||||
// Verify relocations.
|
||||
for (i64 i = 1; i < rels.size(); i++)
|
||||
if (rels[i].r_type != E::R_NONE &&
|
||||
rels[i].r_offset <= rels[i - 1].r_offset)
|
||||
Fatal(ctx) << isec << ": relocation offsets must increase monotonically";
|
||||
|
||||
// Read CIEs and FDEs until empty.
|
||||
std::string_view contents = this->get_string(ctx, isec.shdr);
|
||||
std::string_view contents = this->get_string(ctx, isec.shdr());
|
||||
i64 rel_idx = 0;
|
||||
|
||||
for (std::string_view data = contents; !data.empty();) {
|
||||
@@ -363,7 +315,7 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
|
||||
if (id == 0) {
|
||||
// This is CIE.
|
||||
cies.push_back(CieRecord<E>(ctx, *this, isec, begin_offset, rel_begin));
|
||||
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) {
|
||||
@@ -376,7 +328,7 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
if (rels[rel_begin].r_offset - begin_offset != 8)
|
||||
Fatal(ctx) << isec << ": FDE's first relocation should have offset 8";
|
||||
|
||||
fdes.push_back(FdeRecord<E>(begin_offset, rel_begin));
|
||||
fdes.emplace_back(begin_offset, rel_begin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,31 +368,6 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool should_write_to_local_symtab(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (ctx.arg.discard_all || ctx.arg.strip_all || ctx.arg.retain_symbols_file)
|
||||
return false;
|
||||
if (sym.get_type() == STT_SECTION)
|
||||
return false;
|
||||
|
||||
// Local symbols are discarded if --discard-local is given or they
|
||||
// are not in a mergeable section. I *believe* we exclude symbols in
|
||||
// mergeable sections because (1) they are too many and (2) they are
|
||||
// merged, so their origins shouldn't matter, but I dont' really
|
||||
// know the rationale. Anyway, this is the behavior of the
|
||||
// traditional linkers.
|
||||
if (sym.name().starts_with(".L")) {
|
||||
if (ctx.arg.discard_locals)
|
||||
return false;
|
||||
|
||||
if (InputSection<E> *isec = sym.input_section)
|
||||
if (isec->shdr.sh_flags & SHF_MERGE)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a symbol object for a given key. This function handles
|
||||
// the -wrap option.
|
||||
template <typename E>
|
||||
@@ -478,8 +405,10 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 1; i < this->first_global; i++) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
std::string_view name = symbol_strtab.data() + esym.st_name;
|
||||
if (esym.is_common())
|
||||
Fatal(ctx) << *this << ": common local symbol?";
|
||||
|
||||
std::string_view name = symbol_strtab.data() + esym.st_name;
|
||||
if (name.empty() && esym.st_type == STT_SECTION)
|
||||
if (InputSection<E> *sec = get_section(esym))
|
||||
name = sec->name();
|
||||
@@ -490,17 +419,8 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
sym.value = esym.st_value;
|
||||
sym.sym_idx = i;
|
||||
|
||||
if (!esym.is_abs()) {
|
||||
if (esym.is_common())
|
||||
Fatal(ctx) << *this << ": common local symbol?";
|
||||
sym.input_section = get_section(esym);
|
||||
}
|
||||
|
||||
if (should_write_to_local_symtab(ctx, sym)) {
|
||||
sym.write_to_symtab = true;
|
||||
strtab_size += sym.name().size() + 1;
|
||||
num_local_symtab++;
|
||||
}
|
||||
if (!esym.is_abs())
|
||||
sym.shndx = esym.is_abs() ? 0 : get_shndx(esym);
|
||||
}
|
||||
|
||||
this->symbols.resize(this->elf_syms.size());
|
||||
@@ -539,6 +459,35 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Relocations are usually sorted by r_offset in relocation tables,
|
||||
// but for some reason only RISC-V does not follow that convention.
|
||||
// We expect them to be sorted, so sort them if necessary.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
|
||||
if (!std::is_same_v<E, RISCV64>)
|
||||
return;
|
||||
|
||||
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
|
||||
return a.r_type != E::R_NONE && b.r_type != E::R_NONE &&
|
||||
a.r_offset < b.r_offset;
|
||||
};
|
||||
|
||||
sorted_rels.resize(sections.size());
|
||||
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];;
|
||||
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
if (std::is_sorted(rels.begin(), rels.end(), less))
|
||||
continue;
|
||||
|
||||
sorted_rels[isec->shndx] = {rels.begin(), rels.end()};
|
||||
sort(sorted_rels[isec->shndx], less);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t find_null(std::string_view data, u64 entsize) {
|
||||
if (entsize == 1)
|
||||
return data.find('\0');
|
||||
@@ -571,20 +520,26 @@ template <typename E>
|
||||
static std::unique_ptr<MergeableSection<E>>
|
||||
split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
std::unique_ptr<MergeableSection<E>> rec(new MergeableSection<E>);
|
||||
rec->parent = MergedSection<E>::get_instance(ctx, sec.name(), sec.shdr.sh_type,
|
||||
sec.shdr.sh_flags);
|
||||
rec->shdr = sec.shdr;
|
||||
rec->parent = MergedSection<E>::get_instance(ctx, sec.name(), sec.shdr().sh_type,
|
||||
sec.shdr().sh_flags);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
|
||||
// If thes section contents are compressed, uncompress them.
|
||||
if (sec.is_compressed()) {
|
||||
u8 *buf = new u8[sec.sh_size];
|
||||
sec.uncompress(ctx, buf);
|
||||
data = {(char *)buf, sec.sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
|
||||
const char *begin = data.data();
|
||||
u64 entsize = sec.shdr.sh_entsize;
|
||||
u64 entsize = sec.shdr().sh_entsize;
|
||||
HyperLogLog estimator;
|
||||
|
||||
static_assert(sizeof(SectionFragment<E>::alignment) == 2);
|
||||
if (sec.shdr.sh_addralign >= UINT16_MAX)
|
||||
Fatal(ctx) << sec << ": alignment too large";
|
||||
|
||||
if (sec.shdr.sh_flags & SHF_STRINGS) {
|
||||
// Split sections
|
||||
if (sec.shdr().sh_flags & SHF_STRINGS) {
|
||||
while (!data.empty()) {
|
||||
size_t end = find_null(data, entsize);
|
||||
if (end == data.npos)
|
||||
@@ -672,8 +627,8 @@ void ObjectFile<E>::initialize_mergeable_sections(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];
|
||||
if (isec && isec->is_alive && (isec->shdr.sh_flags & SHF_MERGE) &&
|
||||
isec->shdr.sh_size && isec->shdr.sh_entsize &&
|
||||
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_MERGE) &&
|
||||
isec->sh_size && isec->shdr().sh_entsize &&
|
||||
isec->relsec_idx == -1) {
|
||||
mergeable_sections[i] = split_section(ctx, *isec);
|
||||
isec->is_alive = false;
|
||||
@@ -683,15 +638,22 @@ void ObjectFile<E>::initialize_mergeable_sections(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::register_section_pieces(Context<E> &ctx) {
|
||||
for (std::unique_ptr<MergeableSection<E>> &m : mergeable_sections)
|
||||
if (m)
|
||||
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(m->strings[i], m->hashes[i],
|
||||
m->shdr.sh_addralign));
|
||||
m->p2align));
|
||||
|
||||
// Shrink vectors that we will never use again to reclaim memory.
|
||||
m->strings.clear();
|
||||
m->hashes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize rel_fragments
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections) {
|
||||
if (!isec || !isec->is_alive || !(isec->shdr.sh_flags & SHF_ALLOC))
|
||||
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
@@ -779,6 +741,7 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
|
||||
initialize_sections(ctx);
|
||||
initialize_symbols(ctx);
|
||||
sort_relocations(ctx);
|
||||
initialize_mergeable_sections(ctx);
|
||||
initialize_ehframe_sections(ctx);
|
||||
}
|
||||
@@ -803,12 +766,23 @@ static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_lazy) {
|
||||
return (6 << 24) + file->priority;
|
||||
return (5 << 24) + file->priority;
|
||||
}
|
||||
|
||||
// GCC creates symbols in COMDATs with STB_GNU_UNIQUE instead of
|
||||
// STB_WEAK if it was configured to do so at build time or the
|
||||
// -fgnu-unique flag was given. In order to to not select a
|
||||
// GNU_UNIQUE symbol in a discarded COMDAT section, we treat it as
|
||||
// if it were weak.
|
||||
//
|
||||
// It looks like STB_GNU_UNIQUE is not a popular option anymore and
|
||||
// often disabled by default though.
|
||||
bool is_weak = (esym.st_bind == STB_WEAK || esym.st_bind == STB_GNU_UNIQUE);
|
||||
|
||||
if (file->is_dso || is_lazy) {
|
||||
if (esym.is_weak())
|
||||
if (is_weak)
|
||||
return (4 << 24) + file->priority;
|
||||
return (3 << 24) + file->priority;
|
||||
}
|
||||
if (esym.is_weak())
|
||||
if (is_weak)
|
||||
return (2 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
@@ -820,6 +794,11 @@ static u64 get_rank(const Symbol<E> &sym) {
|
||||
return get_rank(sym.file, sym.esym(), !sym.file->is_alive);
|
||||
}
|
||||
|
||||
// Symbol's visibility is set to the most restrictive one. For example,
|
||||
// if one input file has a defined symbol `foo` with the default
|
||||
// visibility and the other input file has an undefined symbol `foo`
|
||||
// with the hidden visibility, the resulting symbol is a hidden defined
|
||||
// symbol.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::merge_visibility(Context<E> &ctx, Symbol<E> &sym,
|
||||
u8 visibility) {
|
||||
@@ -874,13 +853,8 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (get_rank(this, esym, !this->is_alive) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.input_section = isec;
|
||||
|
||||
if (SectionFragmentRef<E> &ref = sym_fragments[i]; ref.frag)
|
||||
sym.value = ref.addend;
|
||||
else
|
||||
sym.value = esym.st_value;
|
||||
|
||||
sym.shndx = isec ? isec->shndx : 0;
|
||||
sym.value = esym.st_value;
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = esym.is_weak();
|
||||
@@ -926,6 +900,16 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Comdat groups are used to de-duplicate functions and data that may
|
||||
// be included into multiple object files. C++ compiler uses comdat
|
||||
// groups to de-duplicate instantiated templates.
|
||||
//
|
||||
// For example, if a compiler decides to instantiate `std::vector<int>`,
|
||||
// it generates code and data for `std::vector<int>` and put them into a
|
||||
// comdat group whose name is the mangled name of `std::vector<int>`.
|
||||
// The instantiation may happen multiple times for different translation
|
||||
// units. Then linker de-duplicates them so that the resulting executable
|
||||
// contains only a single copy of `std::vector<int>`.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_comdat_groups() {
|
||||
for (auto &pair : comdat_groups) {
|
||||
@@ -961,6 +945,14 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
// If a protected/hidden undefined symbol is resolved to an
|
||||
// imported symbol, it's handled as if no symbols were found.
|
||||
if (sym.file && sym.file->is_dso &&
|
||||
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
|
||||
report_undef(ctx, *this, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.file &&
|
||||
(!sym.esym().is_undef() || sym.file->priority <= this->priority))
|
||||
continue;
|
||||
@@ -977,9 +969,9 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
auto claim = [&]() {
|
||||
auto claim = [&] {
|
||||
sym.file = this;
|
||||
sym.input_section = nullptr;
|
||||
sym.shndx = 0;
|
||||
sym.value = 0;
|
||||
sym.sym_idx = i;
|
||||
sym.is_weak = false;
|
||||
@@ -987,7 +979,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
Warn(ctx) << "undefined symbol: " << *this << ": " << sym;
|
||||
report_undef(ctx, *this, sym);
|
||||
|
||||
// Convert remaining undefined symbols to dynamic symbols.
|
||||
if (ctx.arg.shared) {
|
||||
@@ -1017,7 +1009,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
|
||||
// Convert remaining undefined symbols to absolute symbols with value 0.
|
||||
if (ctx.arg.unresolved_symbols != UNRESOLVED_ERROR ||
|
||||
esym.is_undef_weak()) {
|
||||
ctx.arg.noinhibit_exec || esym.is_undef_weak()) {
|
||||
claim();
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_imported = false;
|
||||
@@ -1029,7 +1021,7 @@ template <typename E>
|
||||
void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations against seciton contents
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec && isec->is_alive && (isec->shdr.sh_flags & SHF_ALLOC))
|
||||
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC))
|
||||
isec->scan_relocations(ctx);
|
||||
|
||||
// Scan relocations against exception frames
|
||||
@@ -1048,6 +1040,24 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Common symbols are used by C's tantative definitions. Tentative
|
||||
// definition is an obscure C feature which allows users to omit `extern`
|
||||
// from global variable declarations in a header file. For example, if you
|
||||
// have a tentative definition `int foo;` in a header which is included
|
||||
// into multiple translation units, `foo` will be included into multiple
|
||||
// object files, but it won't cause the duplicate symbol error. Instead,
|
||||
// the linker will merge them into a single instance of `foo`.
|
||||
//
|
||||
// If a header file contains a tentative definition `int foo;` and one of
|
||||
// a C file contains a definition with initial value such as `int foo = 5;`,
|
||||
// then the "real" definition wins. The symbol for the tentative definition
|
||||
// will be resolved to the real definition. If there is no "real"
|
||||
// definition, the tentative definition gets the default initial value 0.
|
||||
//
|
||||
// Tentative definitions are represented as "common symbols" in an object
|
||||
// file. In this function, we allocate spaces in .bss for remaining common
|
||||
// symbols that were not resolved to usual defined symbols in previous
|
||||
// passes.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
if (!has_common_symbol)
|
||||
@@ -1062,28 +1072,30 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (sym.file != this) {
|
||||
if (ctx.arg.warn_common)
|
||||
Warn(ctx) << *this << ": multiple common symbols: " << sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *shdr = new ElfShdr<E>;
|
||||
ctx.shdr_pool.push_back(std::unique_ptr<ElfShdr<E>>(shdr));
|
||||
elf_sections2.push_back({});
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
|
||||
memset(shdr, 0, sizeof(*shdr));
|
||||
shdr->sh_flags = SHF_ALLOC;
|
||||
shdr->sh_type = SHT_NOBITS;
|
||||
shdr->sh_size = this->elf_syms[i].st_size;
|
||||
shdr->sh_addralign = this->elf_syms[i].st_value;
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
shdr.sh_flags = SHF_ALLOC;
|
||||
shdr.sh_type = SHT_NOBITS;
|
||||
shdr.sh_size = this->elf_syms[i].st_size;
|
||||
shdr.sh_addralign = this->elf_syms[i].st_value;
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, *shdr, ".common",
|
||||
std::string_view(), sections.size());
|
||||
std::make_unique<InputSection<E>>(ctx, *this, ".common", idx);
|
||||
isec->output_section = osec;
|
||||
|
||||
sym.file = this;
|
||||
sym.input_section = isec.get();
|
||||
sym.shndx = idx;
|
||||
sym.value = 0;
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
@@ -1096,36 +1108,53 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool should_write_to_global_symtab(Symbol<E> &sym) {
|
||||
return sym.get_type() != STT_SECTION && sym.is_alive();
|
||||
static bool should_write_to_local_symtab(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.get_type() == STT_SECTION)
|
||||
return false;
|
||||
|
||||
// Local symbols are discarded if --discard-local is given or they
|
||||
// are not in a mergeable section. I *believe* we exclude symbols in
|
||||
// mergeable sections because (1) they are too many and (2) they are
|
||||
// merged, so their origins shouldn't matter, but I dont' really
|
||||
// know the rationale. Anyway, this is the behavior of the
|
||||
// traditional linkers.
|
||||
if (sym.name().starts_with(".L")) {
|
||||
if (ctx.arg.discard_locals)
|
||||
return false;
|
||||
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->shdr().sh_flags & SHF_MERGE)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::compute_symtab(Context<E> &ctx) {
|
||||
if (ctx.arg.retain_symbols_file) {
|
||||
std::span<Symbol<E> *> syms(this->symbols);
|
||||
for (Symbol<E> *sym : syms.subspan(this->first_global)) {
|
||||
if (sym->file == this && sym->write_to_symtab) {
|
||||
strtab_size += sym->name().size() + 1;
|
||||
num_global_symtab++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.arg.strip_all)
|
||||
return;
|
||||
|
||||
if (ctx.arg.gc_sections && !ctx.arg.discard_all) {
|
||||
// Detect symbols pointing to sections discarded by -gc-sections
|
||||
// to not copy them to symtab.
|
||||
auto is_alive = [&](Symbol<E> &sym) -> bool {
|
||||
if (!ctx.arg.gc_sections)
|
||||
return true;
|
||||
|
||||
if (SectionFragment<E> *frag = sym.get_frag())
|
||||
return frag->is_alive;
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
return isec->is_alive;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Compute the size of local symbols
|
||||
if (!ctx.arg.discard_all && !ctx.arg.strip_all && !ctx.arg.retain_symbols_file) {
|
||||
for (i64 i = 1; i < this->first_global; i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
|
||||
if (sym.write_to_symtab && !sym.is_alive()) {
|
||||
strtab_size -= sym.name().size() + 1;
|
||||
num_local_symtab--;
|
||||
sym.write_to_symtab = false;
|
||||
if (is_alive(sym) && should_write_to_local_symtab(ctx, sym)) {
|
||||
this->strtab_size += sym.name().size() + 1;
|
||||
this->num_local_symtab++;
|
||||
sym.write_to_symtab = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1134,24 +1163,25 @@ void ObjectFile<E>::compute_symtab(Context<E> &ctx) {
|
||||
for (i64 i = this->first_global; i < this->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
|
||||
if (sym.file == this && should_write_to_global_symtab(sym)) {
|
||||
strtab_size += sym.name().size() + 1;
|
||||
if (sym.file == this && is_alive(sym) &&
|
||||
(!ctx.arg.retain_symbols_file || sym.write_to_symtab)) {
|
||||
this->strtab_size += sym.name().size() + 1;
|
||||
this->num_global_symtab++;
|
||||
sym.write_to_symtab = true;
|
||||
num_global_symtab++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::write_symtab(Context<E> &ctx) {
|
||||
u8 *symtab_base = ctx.buf + ctx.symtab->shdr.sh_offset;
|
||||
ElfSym<E> *symtab_base = (ElfSym<E> *)(ctx.buf + ctx.symtab->shdr.sh_offset);
|
||||
i64 symtab_idx;
|
||||
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
i64 strtab_off = strtab_offset;
|
||||
i64 symtab_off;
|
||||
i64 strtab_off = this->strtab_offset;
|
||||
|
||||
auto write_sym = [&](Symbol<E> &sym) {
|
||||
ElfSym<E> &esym = *(ElfSym<E> *)(symtab_base + symtab_off);
|
||||
symtab_off += sizeof(esym);
|
||||
ElfSym<E> &esym = symtab_base[symtab_idx++];
|
||||
|
||||
esym = sym.esym();
|
||||
esym.st_name = strtab_off;
|
||||
@@ -1161,10 +1191,10 @@ void ObjectFile<E>::write_symtab(Context<E> &ctx) {
|
||||
else
|
||||
esym.st_value = sym.get_addr(ctx);
|
||||
|
||||
if (sym.input_section)
|
||||
esym.st_shndx = sym.input_section->output_section->shndx;
|
||||
else if (sym.shndx)
|
||||
esym.st_shndx = sym.shndx;
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
esym.st_shndx = isec->output_section->shndx;
|
||||
else if (sym.shndx < 0)
|
||||
esym.st_shndx = -sym.shndx;
|
||||
else if (esym.is_undef())
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
else
|
||||
@@ -1174,14 +1204,14 @@ void ObjectFile<E>::write_symtab(Context<E> &ctx) {
|
||||
strtab_off += sym.name().size() + 1;
|
||||
};
|
||||
|
||||
symtab_off = local_symtab_offset;
|
||||
symtab_idx = this->local_symtab_idx;
|
||||
for (i64 i = 1; i < this->first_global; i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (sym.write_to_symtab)
|
||||
write_sym(sym);
|
||||
}
|
||||
|
||||
symtab_off = global_symtab_offset;
|
||||
symtab_idx = this->global_symtab_idx;
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (sym.file == this && sym.write_to_symtab)
|
||||
@@ -1214,7 +1244,7 @@ template <typename E>
|
||||
SharedFile<E> *
|
||||
SharedFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
SharedFile<E> *obj = new SharedFile(ctx, mf);
|
||||
ctx.dso_pool.push_back(std::unique_ptr<SharedFile<E>>(obj));
|
||||
ctx.dso_pool.emplace_back(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1325,7 +1355,7 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
|
||||
if (get_rank(this, esym, false) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.input_section = nullptr;
|
||||
sym.shndx = 0;
|
||||
sym.value = esym.st_value;
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = versyms[i];
|
||||
@@ -1371,17 +1401,60 @@ std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
|
||||
template <typename E>
|
||||
bool SharedFile<E>::is_readonly(Context<E> &ctx, Symbol<E> *sym) {
|
||||
ElfEhdr<E> *ehdr = (ElfEhdr<E> *)this->mf->data;
|
||||
ElfPhdr<E> *phdr = (ElfPhdr<E> *)(this->mf->data + ehdr->e_phoff);
|
||||
ElfPhdr<E> *phdr = this->get_phdr();
|
||||
u64 val = sym->esym().st_value;
|
||||
|
||||
for (i64 i = 0; i < ehdr->e_phnum; i++)
|
||||
for (i64 i = 0; i < this->get_ehdr().e_phnum; i++)
|
||||
if (phdr[i].p_type == PT_LOAD && !(phdr[i].p_flags & PF_W) &&
|
||||
phdr[i].p_vaddr <= val && val < phdr[i].p_vaddr + phdr[i].p_memsz)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void SharedFile<E>::compute_symtab(Context<E> &ctx) {
|
||||
if (ctx.arg.strip_all)
|
||||
return;
|
||||
|
||||
// Compute the size of global symbols.
|
||||
for (i64 i = this->first_global; i < this->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
|
||||
if (sym.file == this && (sym.is_imported || sym.is_exported) &&
|
||||
(!ctx.arg.retain_symbols_file || sym.write_to_symtab)) {
|
||||
this->strtab_size += sym.name().size() + 1;
|
||||
this->num_global_symtab++;
|
||||
sym.write_to_symtab = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void SharedFile<E>::write_symtab(Context<E> &ctx) {
|
||||
ElfSym<E> *symtab =
|
||||
(ElfSym<E> *)(ctx.buf + ctx.symtab->shdr.sh_offset) + this->global_symtab_idx;
|
||||
|
||||
u8 *strtab = ctx.buf + ctx.strtab->shdr.sh_offset + this->strtab_offset;
|
||||
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (sym.file != this || !sym.write_to_symtab)
|
||||
continue;
|
||||
|
||||
ElfSym<E> &esym = *symtab++;
|
||||
esym.st_name = strtab - (ctx.buf + ctx.strtab->shdr.sh_offset);
|
||||
esym.st_value = 0;
|
||||
esym.st_size = 0;
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = sym.visibility;
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
|
||||
write_string(strtab, sym.name());
|
||||
strtab += sym.name().size() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class ObjectFile<E>; \
|
||||
template class SharedFile<E>; \
|
||||
@@ -1390,5 +1463,7 @@ bool SharedFile<E>::is_readonly(Context<E> &ctx, Symbol<E> *sym) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+126
-30
@@ -1,6 +1,7 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
@@ -24,29 +25,83 @@ bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const ElfShdr<E> &shdr, std::string_view name,
|
||||
std::string_view contents, i64 section_idx)
|
||||
: file(file), shdr(shdr), contents(contents), nameptr(name.data()),
|
||||
namelen(name.size()), section_idx(section_idx) {
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr.sh_type, shdr.sh_flags);
|
||||
static inline i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr.sh_type == SHT_NOBITS || shdr.sh_size == 0)
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, shdr().sh_size};
|
||||
|
||||
bool compressed;
|
||||
|
||||
if (name.starts_with(".zdebug")) {
|
||||
sh_size = *(ubig64 *)&contents[4];
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = true;
|
||||
} else if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
sh_size = chdr.ch_size;
|
||||
p2align = to_p2align(chdr.ch_addralign);
|
||||
compressed = true;
|
||||
} else {
|
||||
sh_size = shdr().sh_size;
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = false;
|
||||
}
|
||||
|
||||
// Uncompress early if the relocation is REL-type so that we can read
|
||||
// addends from section contents. If RELA-type, we don't need to do this
|
||||
// because addends are in relocations.
|
||||
if (compressed && E::is_rel) {
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool InputSection<E>::is_compressed() {
|
||||
return !E::is_rel &&
|
||||
(name().starts_with(".zdebug") || (shdr().sh_flags & SHF_COMPRESSED));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx, u8 *buf) {
|
||||
auto do_uncompress = [&](std::string_view data) {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": uncompress failed";
|
||||
assert(size == sh_size);
|
||||
};
|
||||
|
||||
if (name().starts_with(".zdebug")) {
|
||||
// Old-style compressed section
|
||||
if (!contents.starts_with("ZLIB") || contents.size() <= 12)
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
do_uncompress(contents.substr(12));
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy data
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
assert(shdr().sh_flags & SHF_COMPRESSED);
|
||||
|
||||
// Apply relocations
|
||||
if (shdr.sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
// New-style compressed section
|
||||
if (contents.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
if (hdr.ch_type != ELFCOMPRESS_ZLIB)
|
||||
Fatal(ctx) << *this << ": unsupported compression type";
|
||||
do_uncompress(contents.substr(sizeof(ElfChdr<E>)));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -73,12 +128,19 @@ template <typename E>
|
||||
void InputSection<E>::dispatch(Context<E> &ctx, Action table[3][4], i64 i,
|
||||
const ElfRel<E> &rel, Symbol<E> &sym) {
|
||||
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
bool is_code = (shdr.sh_flags & SHF_EXECINSTR);
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
bool is_writable = (shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto error = [&]() {
|
||||
Error(ctx) << *this << ": " << rel << " relocation against symbol `"
|
||||
<< sym << "' can not be used; recompile with -fPIC";
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << *this << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
|
||||
auto warn_textrel = [&] {
|
||||
if (ctx.arg.warn_textrel)
|
||||
Warn(ctx) << *this << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
@@ -106,29 +168,35 @@ void InputSection<E>::dispatch(Context<E> &ctx, Action table[3][4], i64 i,
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
return;
|
||||
case CPLT: {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
sym.is_canonical = true;
|
||||
return;
|
||||
}
|
||||
case DYNREL:
|
||||
if (!is_writable) {
|
||||
if (!is_code || ctx.arg.z_text) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
warn_textrel();
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
|
||||
assert(sym.is_imported);
|
||||
needs_dynrel[i] = true;
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
case BASEREL:
|
||||
if (!is_writable) {
|
||||
if (!is_code || ctx.arg.z_text) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
warn_textrel();
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
|
||||
needs_baserel[i] = true;
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
@@ -138,7 +206,31 @@ void InputSection<E>::dispatch(Context<E> &ctx, Action table[3][4], i64 i,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::report_undef(Context<E> &ctx, Symbol<E> &sym) {
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
if constexpr (std::is_same_v<E, RISCV64>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else if (is_compressed()) {
|
||||
uncompress(ctx, buf);
|
||||
} else {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void report_undef(Context<E> &ctx, InputFile<E> &file, Symbol<E> &sym) {
|
||||
if (ctx.arg.warn_once && !ctx.warned.insert({(void *)&sym, 1}))
|
||||
return;
|
||||
|
||||
switch (ctx.arg.unresolved_symbols) {
|
||||
case UNRESOLVED_ERROR:
|
||||
Error(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
@@ -151,12 +243,16 @@ void InputSection<E>::report_undef(Context<E> &ctx, Symbol<E> &sym) {
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template struct CieRecord<E>; \
|
||||
template class InputSection<E>;
|
||||
#define INSTANTIATE(E) \
|
||||
template struct CieRecord<E>; \
|
||||
template class InputSection<E>; \
|
||||
template void report_undef(Context<E> &, InputFile<E> &, Symbol<E> &)
|
||||
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -408,5 +408,7 @@ void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+661
@@ -0,0 +1,661 @@
|
||||
// This file handles the linker plugin to support LTO (Link-Time
|
||||
// Optimization).
|
||||
//
|
||||
// LTO is a technique to do whole-program optimization to a program. Since
|
||||
// a linker sees the whole program as opposed to a single compilation
|
||||
// unit, it in theory can do some optimizations that cannot be done in the
|
||||
// usual separate compilation model. For example, LTO should be able to
|
||||
// inline functions that are defined in other compilation unit.
|
||||
//
|
||||
// In GCC and Clang, all you have to do to enable LTO is adding the
|
||||
// `-flto` flag to the compiler and the linker command lines. If `-flto`
|
||||
// is given, the compiler generates a file that contains not machine code
|
||||
// but the compiler's IR (intermediate representation). In GCC, the output
|
||||
// is an ELF file which wraps GCC's IR. In LLVM, it's not even an ELF file
|
||||
// but just a raw LLVM IR file.
|
||||
//
|
||||
// Here is what we have to do if at least one input file is not a usual
|
||||
// ELF file but an IR object file:
|
||||
//
|
||||
// 1. Read symbols both from usual ELF files and from IR object files and
|
||||
// resolve symbols as usual.
|
||||
//
|
||||
// 2. Pass all IR objects to the compiler backend. The compiler backend
|
||||
// compiles the IRs and returns a few big ELF object files as a
|
||||
// result.
|
||||
//
|
||||
// 3. Parse the returned ELF files and overwrite IR object symbols with
|
||||
// the returned ones, discarding IR object files.
|
||||
//
|
||||
// 4. Continue the rest of the linking process as usual.
|
||||
//
|
||||
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
|
||||
// the linker. The given .so file provides a way to call the compiler
|
||||
// backend.
|
||||
//
|
||||
// The linker plugin API is documented at
|
||||
// https://gcc.gnu.org/wiki/whopr/driver, though the document is a bit
|
||||
// outdated.
|
||||
//
|
||||
// Frankly, the linker plugin API is peculiar and is not very easy to use.
|
||||
// For some reason, the API functions don't return the result of a
|
||||
// function call as a return value but instead calls other function with
|
||||
// the result as its argument to "return" the result.
|
||||
//
|
||||
// For example, the first thing you need to do after dlopen()'ing a linker
|
||||
// plugin .so is to call `onload` function with a list of callback
|
||||
// functions. `onload` calls callbacks to notify about the pointers to
|
||||
// other functions the linker plugin provides. I don't know why `onload`
|
||||
// can't just return a list of functions or why the linker plugin can't
|
||||
// define not only `onload` but other functions, but that's how it works.
|
||||
//
|
||||
// Here is the steps to use the linker plugin:
|
||||
//
|
||||
// 1. dlopen() the linker plugin .so and call `onload` to obtain pointers
|
||||
// to other functions provided by the plugin.
|
||||
//
|
||||
// 2. Call `claim_file_hook` with an IR object file to read its symbol
|
||||
// table. `claim_file_hook` calls the `add_symbols` callback to
|
||||
// "return" a list of symbols.
|
||||
//
|
||||
// 3. `claim_file_hook` returns LDPT_OK only when the plugin wants to
|
||||
// handle a given file. Since we pass only IR object files to the
|
||||
// plugin in mold, it always returns LDPT_OK in our case.
|
||||
//
|
||||
// 4. Once we made a decision as to which object file to include into the
|
||||
// output file, we call `all_symbols_read_hook` to compile IR objects
|
||||
// into a few big ELF files. That function calls the `get_symbols`
|
||||
// callback to ask us about the symbol resolution results. (The
|
||||
// compiler backend needs to know whether an undefined symbol in an IR
|
||||
// object was resolved to a regular object file or a shared object to
|
||||
// do whole program optimization, for example.)
|
||||
//
|
||||
// 5. `all_symbols_read_hook` "returns" the result by calling the
|
||||
// `add_input_file` callback. The callback is called with a path to an
|
||||
// LTO'ed ELF file. We parse that ELF file and override symbols
|
||||
// defined by IR objects with the ELF file's ones.
|
||||
//
|
||||
// 6. Lastly, we call `cleanup_hook` to remove temporary files created by
|
||||
// the compiler backend.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../lto.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
|
||||
#if 0
|
||||
# define LOG std::cerr
|
||||
#else
|
||||
# define LOG std::ostringstream()
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Global variables
|
||||
// We store LTO-related information to global variables,
|
||||
// as the LTO plugin is not thread-safe by design anyway.
|
||||
|
||||
template <typename E> static Context<E> *gctx;
|
||||
template <typename E> static std::vector<ObjectFile<E> *> lto_objects;
|
||||
|
||||
static int phase = 0;
|
||||
static std::vector<PluginSymbol> plugin_symbols;
|
||||
static ClaimFileHandler *claim_file_hook;
|
||||
static AllSymbolsReadHandler *all_symbols_read_hook;
|
||||
static CleanupHandler *cleanup_hook;
|
||||
|
||||
// Event handlers
|
||||
|
||||
static PluginStatus message(int level, const char *fmt, ...) {
|
||||
LOG << "message\n";
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "mold: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n");
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus register_claim_file_hook(ClaimFileHandler fn) {
|
||||
LOG << "register_claim_file_hook\n";
|
||||
claim_file_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
register_all_symbols_read_hook(AllSymbolsReadHandler fn) {
|
||||
LOG << "register_all_symbols_read_hook\n";
|
||||
all_symbols_read_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus register_cleanup_hook(CleanupHandler fn) {
|
||||
LOG << "register_cleanup_hook\n";
|
||||
cleanup_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
add_symbols(void *handle, int nsyms, const PluginSymbol *psyms) {
|
||||
LOG << "add_symbols: " << nsyms << "\n";
|
||||
assert(phase == 1);
|
||||
plugin_symbols = {psyms, psyms + nsyms};
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus add_input_file(const char *path) {
|
||||
LOG << "add_input_file: " << path << "\n";
|
||||
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
static i64 file_priority = 100;
|
||||
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, "", false);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
lto_objects<E>.push_back(file);
|
||||
|
||||
file->priority = file_priority++;
|
||||
file->is_alive = true;
|
||||
file->parse(ctx);
|
||||
file->resolve_symbols(ctx);
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_file(const void *handle, struct PluginInputFile *file) {
|
||||
LOG << "get_input_file\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus release_input_file(const void *handle) {
|
||||
LOG << "release_input_file\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
if (file.mf->fd != -1) {
|
||||
close(file.mf->fd);
|
||||
file.mf->fd = -1;
|
||||
}
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus add_input_library(const char *path) {
|
||||
LOG << "add_input_library\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus set_extra_library_path(const char *path) {
|
||||
LOG << "set_extra_library_path\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus get_view(const void *handle, const void **view) {
|
||||
LOG << "get_view\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
*view = (void *)file.mf->data;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_count(const void *handle, int *count) {
|
||||
LOG << "get_input_section_count\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_type(const PluginSection section, int *type) {
|
||||
LOG << "get_input_section_type\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_name(const PluginSection section,
|
||||
char **section_name) {
|
||||
LOG << "get_input_section_name\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_contents(const PluginSection section,
|
||||
const char **section_contents,
|
||||
size_t *len) {
|
||||
LOG << "get_input_section_contents\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
update_section_order(const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
LOG << "update_section_order\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus allow_section_ordering() {
|
||||
LOG << "allow_section_ordering\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_symbols_v1(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
unreachable();
|
||||
}
|
||||
|
||||
// get_symbols teaches the LTO plugin as to how we resolved symbols.
|
||||
// The plugin uses the symbol resolution info to optimizes the program.
|
||||
//
|
||||
// For example, if a definition in an IR file is not referenced by
|
||||
// non-IR objects at all, the plugin may choose to completely inline
|
||||
// that definition within the IR objects and remove the symbol from the
|
||||
// LTO result. On the other hand, if a definition is referenced by a
|
||||
// non-IR object, it has to keep the symbol in the LTO result.
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
assert(file.is_lto_obj);
|
||||
|
||||
// If file is an archive member which was not chose to be included in
|
||||
// to the final result, we need to make the plugin to ignore all
|
||||
// symbols.
|
||||
if (!file.is_alive) {
|
||||
assert(!is_v2);
|
||||
for (int i = 0; i < nsyms; i++)
|
||||
psyms[i].resolution = LDPR_PREEMPTED_REG;
|
||||
return LDPS_NO_SYMS;
|
||||
}
|
||||
|
||||
auto get_resolution = [&](ElfSym<E> &esym, Symbol<E> &sym) {
|
||||
if (!sym.file)
|
||||
return LDPR_UNDEF;
|
||||
|
||||
if (sym.file == &file) {
|
||||
if (sym.referenced_by_regular_obj)
|
||||
return LDPR_PREVAILING_DEF;
|
||||
if (sym.is_exported)
|
||||
return is_v2 ? LDPR_PREVAILING_DEF : LDPR_PREVAILING_DEF_IRONLY_EXP;
|
||||
return LDPR_PREVAILING_DEF_IRONLY;
|
||||
}
|
||||
|
||||
if (sym.file->is_dso)
|
||||
return LDPR_RESOLVED_DYN;
|
||||
if (((ObjectFile<E> *)sym.file)->is_lto_obj)
|
||||
return esym.is_undef() ? LDPR_RESOLVED_IR : LDPR_PREEMPTED_IR;
|
||||
return esym.is_undef() ? LDPR_RESOLVED_EXEC : LDPR_PREEMPTED_REG;
|
||||
};
|
||||
|
||||
// Set the symbol resolution results to psyms.
|
||||
for (i64 i = 0; i < nsyms; i++) {
|
||||
ElfSym<E> &esym = file.elf_syms[i + 1];
|
||||
Symbol<E> &sym = *file.symbols[i + 1];
|
||||
psyms[i].resolution = get_resolution(esym, sym);
|
||||
}
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v2(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
LOG << "get_symbols_v2\n";
|
||||
return get_symbols<E>(handle, nsyms, psyms, true);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v3(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
LOG << "get_symbols_v3\n";
|
||||
return get_symbols<E>(handle, nsyms, psyms, false);
|
||||
}
|
||||
|
||||
static PluginStatus allow_unique_segment_for_sections() {
|
||||
LOG << "allow_unique_segment_for_sections\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
unique_segment_for_sections(const char *segment_name,
|
||||
uint64_t flags,
|
||||
uint64_t align,
|
||||
const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
LOG << "unique_segment_for_sections\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_alignment(const PluginSection section,
|
||||
int *addralign) {
|
||||
LOG << "get_input_section_alignment\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_size(const PluginSection section, uint64_t *size) {
|
||||
LOG << "get_input_section_size\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
register_new_input_hook(NewInputHandler fn) {
|
||||
LOG << "register_new_input_hook\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_wrap_symbols(uint64_t *num_symbols, const char ***wrap_symbols) {
|
||||
LOG << "get_wrap_symbols\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void load_plugin(Context<E> &ctx) {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message);
|
||||
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
|
||||
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_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_NULL, 0);
|
||||
|
||||
onload(tv.data());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
ElfSym<E> esym = {};
|
||||
|
||||
switch (psym.def) {
|
||||
case LDPK_DEF:
|
||||
esym.st_shndx = SHN_ABS;
|
||||
break;
|
||||
case LDPK_WEAKDEF:
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_WEAK;
|
||||
break;
|
||||
case LDPK_UNDEF:
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
break;
|
||||
case LDPK_WEAKUNDEF:
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
esym.st_bind = STB_WEAK;
|
||||
break;
|
||||
case LDPK_COMMON:
|
||||
esym.st_shndx = SHN_COMMON;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (psym.symbol_type) {
|
||||
case LDST_UNKNOWN:
|
||||
break;
|
||||
case LDST_FUNCTION:
|
||||
esym.st_type = STT_FUNC;
|
||||
break;
|
||||
case LDST_VARIABLE:
|
||||
esym.st_type = STT_OBJECT;
|
||||
break;
|
||||
};
|
||||
|
||||
switch (psym.visibility) {
|
||||
case LDPV_DEFAULT:
|
||||
break;
|
||||
case LDPV_PROTECTED:
|
||||
esym.st_visibility = STV_PROTECTED;
|
||||
break;
|
||||
case LDPV_INTERNAL:
|
||||
esym.st_visibility = STV_INTERNAL;
|
||||
break;
|
||||
case LDPV_HIDDEN:
|
||||
esym.st_visibility = STV_HIDDEN;
|
||||
break;
|
||||
}
|
||||
|
||||
esym.st_size = psym.size;
|
||||
return esym;
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin looks like LLVM's one.
|
||||
// Returns false if it's GCC.
|
||||
template <typename E>
|
||||
static bool is_llvm(Context<E> &ctx) {
|
||||
return ctx.arg.plugin.ends_with("LLVMgold.so");
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin supports the get_symbols_v3 API.
|
||||
// Currently, we simply assume that LLVM supports it and GCC does not.
|
||||
template <typename E>
|
||||
static bool suppots_v3_api(Context<E> &ctx) {
|
||||
return is_llvm(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
LOG << "read_lto_object: " << mf->name << "\n";
|
||||
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
|
||||
<< "because no -plugin option was given. Please make sure you "
|
||||
<< "added -flto not only for creating object files but also for "
|
||||
<< "creating the final executable.";
|
||||
|
||||
// dlopen the linker plugin file
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&] { load_plugin(ctx); });
|
||||
|
||||
// Create mold's object instance
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
|
||||
obj->filename = mf->name;
|
||||
obj->symbols.push_back(new Symbol<E>);
|
||||
obj->first_global = 1;
|
||||
obj->is_lto_obj = true;
|
||||
obj->mf = mf;
|
||||
|
||||
// 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;
|
||||
file.handle = (void *)obj;
|
||||
|
||||
LOG << "read_lto_symbols: "<< mf->name << "\n";
|
||||
|
||||
// claim_file_hook() calls add_symbols() which initializes `plugin_symbols`
|
||||
int claimed = false;
|
||||
claim_file_hook(&file, &claimed);
|
||||
if (!claimed)
|
||||
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin";
|
||||
|
||||
// It looks like GCC doesn't need fd after claim_file_hook() while
|
||||
// LLVM needs it and takes the ownership of fd. To prevent "too many
|
||||
// open files" issue, we close fd only for GCC. This is ugly, though.
|
||||
if (!is_llvm(ctx)) {
|
||||
close(mf2->fd);
|
||||
mf2->fd = -1;
|
||||
}
|
||||
|
||||
// Initialize object symbols
|
||||
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
|
||||
|
||||
for (PluginSymbol &psym : plugin_symbols) {
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->sym_fragments.resize(esyms->size());
|
||||
obj->symvers.resize(esyms->size());
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
}
|
||||
|
||||
// This function restarts mold itself with `--:lto-pass2` and
|
||||
// `--:ignore-ir-file` flags. We do this as a workaround for the old
|
||||
// linker plugins that do not support the get_symbols_v3 API.
|
||||
//
|
||||
// get_symbols_v1 and get_symbols_v2 don't provide a way to ignore an
|
||||
// object file we previously passed to the linker plugin. So we can't
|
||||
// "unload" object files in archives that we ended up not choosing to
|
||||
// include into the final output.
|
||||
//
|
||||
// As a workaround, we restart the linker with a list of object files
|
||||
// the linker has to ignore, so that it won't read the object files
|
||||
// from archives next time.
|
||||
//
|
||||
// This is an ugly hack and should be removed once GCC adopts the v3 API.
|
||||
template <typename E>
|
||||
static void restart_process(Context<E> &ctx) {
|
||||
std::vector<const char *> args;
|
||||
|
||||
for (std::string_view arg : ctx.cmdline_args)
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
args.push_back("--:lto-pass2");
|
||||
args.push_back(nullptr);
|
||||
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
execv(self.c_str(), (char * const *)args.data());
|
||||
std::cerr << "execv failed: " << errno_string() << "\n";
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_lto");
|
||||
|
||||
if (!ctx.arg.lto_pass2 && !suppots_v3_api(ctx))
|
||||
restart_process(ctx);
|
||||
|
||||
assert(phase == 1);
|
||||
phase = 2;
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_lto_obj)
|
||||
continue;
|
||||
|
||||
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
|
||||
ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
|
||||
if (esym.is_undef() && sym.file && sym.file != file &&
|
||||
!sym.file->is_dso && ((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.referenced_by_regular_obj = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
if (PluginStatus st = all_symbols_read_hook(); st != LDPS_OK)
|
||||
Fatal(ctx) << "LTO: all_symbols_read_hook returns " << st;
|
||||
|
||||
return lto_objects<E>;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {
|
||||
Timer t(ctx, "lto_cleanup");
|
||||
|
||||
if (cleanup_hook)
|
||||
cleanup_hook();
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template ObjectFile<E> * \
|
||||
read_lto_object(Context<E> &, MappedFile<Context<E>> *); \
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &); \
|
||||
template void lto_cleanup(Context<E> &)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+122
-81
@@ -28,7 +28,26 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *m
|
||||
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_lto_objs");
|
||||
count++;
|
||||
|
||||
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
|
||||
return new ObjectFile<E>;
|
||||
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
@@ -42,7 +61,7 @@ new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
@@ -64,18 +83,27 @@ 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))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, child, mf->name));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
Warn(ctx) << mf->name << ": looks like this is an LLVM bitcode, "
|
||||
<< "but mold does not support LTO";
|
||||
ctx.llvm_lto = true;
|
||||
ctx.objs.push_back(new_lto_obj(ctx, mf, ""));
|
||||
return;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type: " << type;
|
||||
@@ -89,6 +117,7 @@ static i64 get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return ((ElfEhdr<E> *)mf->data)->e_machine;
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
@@ -275,7 +304,7 @@ static void show_stats(Context<E> &ctx) {
|
||||
static Counter alloc("reloc_alloc");
|
||||
static Counter nonalloc("reloc_nonalloc");
|
||||
|
||||
if (sec->shdr.sh_flags & SHF_ALLOC)
|
||||
if (sec->shdr().sh_flags & SHF_ALLOC)
|
||||
alloc += sec->get_rels(ctx).size();
|
||||
else
|
||||
nonalloc += sec->get_rels(ctx).size();
|
||||
@@ -313,7 +342,7 @@ static void show_stats(Context<E> &ctx) {
|
||||
static Counter num_objs("num_objs", ctx.objs.size());
|
||||
static Counter num_dsos("num_dsos", ctx.dsos.size());
|
||||
|
||||
if constexpr (E::e_machine == EM_AARCH64) {
|
||||
if constexpr (std::is_same_v<E, ARM64>) {
|
||||
static Counter num_thunks("num_thunks");
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
|
||||
@@ -321,10 +350,13 @@ static void show_stats(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
Counter::print();
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &sec : ctx.merged_sections)
|
||||
sec->print_stats(ctx);
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale above 32 threads.
|
||||
// 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);
|
||||
@@ -355,6 +387,10 @@ static int elf_main(int argc, char **argv) {
|
||||
return elf_main<I386>(argc, argv);
|
||||
case EM_AARCH64:
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
case EM_ARM:
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
case EM_RISCV:
|
||||
return elf_main<RISCV64>(argc, argv);
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
@@ -416,13 +452,6 @@ static int elf_main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Timer t(ctx, "register_section_pieces");
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->register_section_pieces(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
@@ -437,31 +466,12 @@ static int elf_main(int argc, char **argv) {
|
||||
// Apply -exclude-libs
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Create instances of linker-synthesized sections such as
|
||||
// .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Resolve symbols and fix the set of object files that are
|
||||
// included to the final output.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// We currently do not natively support LTO.
|
||||
// If LLVM LTO is in use, fallback to the lld linker by invoking
|
||||
// it with the exact same command line arguments as we got.
|
||||
if (ctx.llvm_lto) {
|
||||
Warn(ctx) << "LLVM LTO is detected, so falling back to ld.lld";
|
||||
argv[0] = (char *)"ld.lld";
|
||||
execvp("ld.lld", argv);
|
||||
Fatal(ctx) << "execvp failed: ld.lld: " << errno_string();
|
||||
}
|
||||
|
||||
// Do the same for GCC LTO.
|
||||
if (Symbol<E> *sym = get_symbol(ctx, "__gnu_lto_slim"); sym->file) {
|
||||
Warn(ctx) << *sym->file
|
||||
<< "GCC LTO is detected, so falling back to ld.bfd";
|
||||
execvp("ld.bfd", argv);
|
||||
Fatal(ctx) << "execvp failed: ld.bfd: " << errno_string();
|
||||
}
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
register_section_pieces(ctx);
|
||||
|
||||
// Remove redundant comdat sections (e.g. duplicate inline functions).
|
||||
eliminate_comdats(ctx);
|
||||
@@ -489,6 +499,10 @@ static int elf_main(int argc, char **argv) {
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// Create instances of linker-synthesized sections such as
|
||||
// .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Bin input sections into output sections.
|
||||
bin_sections(ctx);
|
||||
|
||||
@@ -498,7 +512,6 @@ static int elf_main(int argc, char **argv) {
|
||||
// Create a dummy file containing linker-synthesized symbols
|
||||
// (e.g. `__bss_start`).
|
||||
ctx.internal_obj = create_internal_file(ctx);
|
||||
ctx.internal_obj->resolve_symbols(ctx);
|
||||
ctx.objs.push_back(ctx.internal_obj);
|
||||
|
||||
// Beyond this point, no new files will be added to ctx.objs
|
||||
@@ -519,6 +532,10 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Make sure that all symbols have been resolved.
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
@@ -532,6 +549,14 @@ static int elf_main(int argc, char **argv) {
|
||||
// a special rule. Sort them.
|
||||
sort_init_fini(ctx);
|
||||
|
||||
// Likewise, .ctors and .dtors have to be sorted. They are rare
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
@@ -570,43 +595,41 @@ static int elf_main(int argc, char **argv) {
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
ctx.verdef->construct(ctx);
|
||||
if (ctx.verdef)
|
||||
ctx.verdef->construct(ctx);
|
||||
|
||||
// Fill .gnu.version_r section contents.
|
||||
ctx.verneed->construct(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
{
|
||||
Timer t(ctx, "compute_symtab");
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->compute_symtab(ctx);
|
||||
});
|
||||
}
|
||||
create_output_symtab(ctx);
|
||||
|
||||
// .eh_frame is a special section from the linker's point of view,
|
||||
// as its contents are parsed and reconstructed by the linker,
|
||||
// unlike other sections that are regarded as opaque bytes.
|
||||
// Here, we transplant .eh_frame sections from a regular output
|
||||
// section to the special EHFrameSection.
|
||||
{
|
||||
Timer t(ctx, "eh_frame");
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return chunk->kind == Chunk<E>::REGULAR && chunk->name == ".eh_frame";
|
||||
});
|
||||
ctx.eh_frame->construct(ctx);
|
||||
}
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Update shdr.sh_size for each chunk and remove empty ones.
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// Update sh_size for each chunk and remove empty ones.
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return chunk->kind == Chunk<E>::SYNTHETIC && chunk->shdr.sh_size == 0;
|
||||
return !chunk->is_output_section() && chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (ctx.chunks[i]->kind != Chunk<E>::HEADER)
|
||||
if (!ctx.chunks[i]->is_header())
|
||||
ctx.chunks[i]->shndx = shndx++;
|
||||
|
||||
// Some types of section header refer other section by index.
|
||||
@@ -620,32 +643,28 @@ static int elf_main(int argc, char **argv) {
|
||||
// On ARM64, we may need to create so-called "range extension thunks"
|
||||
// to extend branch instructions reach, as they can jump only to
|
||||
// ±128 MiB.
|
||||
if constexpr (E::e_machine == EM_AARCH64)
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
filesize = create_range_extension_thunks(ctx);
|
||||
|
||||
// On RISC-V, branches are encode using multiple instructions so
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
// are close enough. Do this optimization.
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
|
||||
// Fix linker-synthesized symbol addresses.
|
||||
fix_synthetic_symbols(ctx);
|
||||
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
|
||||
compress_debug_sections(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// At this point, file layout is fixed.
|
||||
// Beyond this, you can assume that symbol addresses including their
|
||||
// GOT or PLT addresses have a correct final value.
|
||||
|
||||
// Some types of TLS relocations are defined relative to the beginning
|
||||
// or the end of the TLS segment address. Find these addresses now.
|
||||
for (ElfPhdr<E> phdr : create_phdr(ctx)) {
|
||||
if (phdr.p_type == PT_TLS) {
|
||||
ctx.tls_begin = phdr.p_vaddr;
|
||||
ctx.tls_end = align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
|
||||
filesize = compress_debug_sections(ctx);
|
||||
|
||||
// At this point, file layout is fixed.
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
@@ -660,20 +679,27 @@ static int elf_main(int argc, char **argv) {
|
||||
Timer t(ctx, "copy_buf");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
std::string name(chunk->name);
|
||||
if (name.empty())
|
||||
name = "(header)";
|
||||
std::string name =
|
||||
chunk->name.empty() ? "(header)" : std::string(chunk->name);
|
||||
Timer t2(ctx, name, &t);
|
||||
|
||||
chunk->copy_buf(ctx);
|
||||
});
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
if constexpr (E::e_machine == EM_AARCH64)
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
write_thunks(ctx);
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
sort_arm_exidx(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
@@ -691,6 +717,19 @@ static int elf_main(int argc, char **argv) {
|
||||
// Close the output file. This is the end of the linker's main job.
|
||||
ctx.output_file->close(ctx);
|
||||
|
||||
// Handle --dependency-file
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
@@ -727,5 +766,7 @@ int main(int argc, char **argv) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+18
-11
@@ -28,12 +28,13 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file && sym->input_section &&
|
||||
sym->get_type() != STT_SECTION) {
|
||||
assert(file == &sym->input_section->file);
|
||||
if (sym->file != file || sym->get_type() == STT_SECTION)
|
||||
continue;
|
||||
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
assert(file == &isec->file);
|
||||
typename Map<E>::accessor acc;
|
||||
map.insert(acc, {sym->input_section, {}});
|
||||
map.insert(acc, {isec, {}});
|
||||
acc->second.push_back(sym);
|
||||
}
|
||||
}
|
||||
@@ -62,15 +63,16 @@ void print_map(Context<E> &ctx) {
|
||||
Map<E> map = get_map(ctx);
|
||||
|
||||
// Print a mapfile.
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
|
||||
for (Chunk<E> *osec : ctx.chunks) {
|
||||
*out << std::setw(16) << (u64)osec->shdr.sh_addr
|
||||
*out << std::showbase
|
||||
<< std::setw(18) << std::hex << (u64)osec->shdr.sh_addr << std::dec
|
||||
<< std::setw(11) << (u64)osec->shdr.sh_size
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
|
||||
if (osec->kind != Chunk<E>::REGULAR)
|
||||
if (!osec->is_output_section())
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
|
||||
@@ -80,16 +82,19 @@ void print_map(Context<E> &ctx) {
|
||||
InputSection<E> *mem = members[i];
|
||||
std::ostringstream ss;
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
u64 addr = osec->shdr.sh_addr + mem->offset;
|
||||
|
||||
ss << std::setw(16) << (osec->shdr.sh_addr + mem->offset)
|
||||
<< std::setw(11) << (u64)mem->shdr.sh_size
|
||||
<< std::setw(6) << (u64)mem->shdr.sh_addralign
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << addr << std::dec
|
||||
<< std::setw(11) << (u64)mem->sh_size
|
||||
<< std::setw(6) << (1 << (u64)mem->p2align)
|
||||
<< " " << *mem << "\n";
|
||||
|
||||
typename Map<E>::const_accessor acc;
|
||||
if (map.find(acc, mem))
|
||||
for (Symbol<E> *sym : acc->second)
|
||||
ss << std::setw(16) << sym->get_addr(ctx)
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << sym->get_addr(ctx) << std::dec
|
||||
<< " 0 0 "
|
||||
<< *sym << "\n";
|
||||
|
||||
@@ -107,5 +112,7 @@ void print_map(Context<E> &ctx) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+1
-1
@@ -46,7 +46,7 @@ static bool is_ld(const char *path) {
|
||||
ptr--;
|
||||
|
||||
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
|
||||
!strcmp(ptr, "ld.gold");
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
|
||||
+731
-274
File diff suppressed because it is too large.
Load diff
+1074
-328
File diff suppressed because it is too large.
Load diff
@@ -138,5 +138,7 @@ OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+735
-174
File diff suppressed because it is too large.
Load diff
@@ -591,5 +591,7 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+11
-15
@@ -1,4 +1,5 @@
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
@@ -11,13 +12,6 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define COMMON_DIGEST_FOR_OPENSSL
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
#else
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
#define DAEMON_TIMEOUT 30
|
||||
|
||||
namespace mold::elf {
|
||||
@@ -59,7 +53,7 @@ std::function<void()> fork_child() {
|
||||
// Child
|
||||
close(pipefd[0]);
|
||||
|
||||
return [=]() {
|
||||
return [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(pipefd[1], buf, 1);
|
||||
assert(n == 1);
|
||||
@@ -218,7 +212,7 @@ void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
|
||||
static i64 conn = -1;
|
||||
|
||||
*wait_for_client = [=, &ctx]() {
|
||||
*wait_for_client = [=, &ctx] {
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sock, &rfds);
|
||||
@@ -245,7 +239,7 @@ void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
dup2(recv_fd(ctx, conn), STDERR_FILENO);
|
||||
};
|
||||
|
||||
*on_complete = [=]() {
|
||||
*on_complete = [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(conn, buf, 1);
|
||||
assert(n == 1);
|
||||
@@ -256,11 +250,6 @@ static std::string get_self_path() {
|
||||
return std::filesystem::read_symlink("/proc/self/exe");
|
||||
}
|
||||
|
||||
static bool is_regular_file(const std::string &path) {
|
||||
struct stat st;
|
||||
return !stat(path.c_str(), &st) && (st.st_mode & S_IFMT) == S_IFREG;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
// Look for mold-wrapper.so from the same directory as the executable is.
|
||||
@@ -277,6 +266,11 @@ std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
return path;
|
||||
#endif
|
||||
|
||||
// Look for ../lib/mold/mold-wrapper.so
|
||||
path = self.parent_path() / "../lib/mold/mold-wrapper.so";
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
Fatal(ctx) << "mold-wrapper.so is missing";
|
||||
}
|
||||
|
||||
@@ -321,5 +315,7 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -27,7 +27,7 @@ std::optional<u16> VersionMatcher::find(std::string_view str) {
|
||||
u32 idx = UINT32_MAX;
|
||||
|
||||
if (root) {
|
||||
std::call_once(once_flag, [&]() { compile(); });
|
||||
std::call_once(once_flag, [&] { compile(); });
|
||||
|
||||
// Match against simple glob patterns
|
||||
TrieNode *node = root.get();
|
||||
|
||||
+53
-1
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "elf/elf.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -16,6 +17,7 @@ enum class FileType {
|
||||
THIN_AR,
|
||||
TAPI,
|
||||
TEXT,
|
||||
GCC_LTO_OBJ,
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
|
||||
@@ -26,6 +28,43 @@ bool is_text_file(MappedFile<C> *mf) {
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
using namespace mold::elf;
|
||||
|
||||
const char *data = mf->get_contents().data();
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
|
||||
|
||||
for (ElfShdr<E> &sec : shdrs) {
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
continue;
|
||||
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
auto skip = [](u8 type) {
|
||||
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
|
||||
};
|
||||
|
||||
// GCC LTO object contains only sections symbols followed by a common
|
||||
// symbol whose name is `__gnu_lto_v1` or `__gnu_lto_slim`.
|
||||
i64 i = 1;
|
||||
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
|
||||
i++;
|
||||
|
||||
if (i < elf_syms.size() && elf_syms[i].st_shndx == SHN_COMMON) {
|
||||
std::string_view name =
|
||||
data + shdrs[sec.sh_link].sh_offset + elf_syms[i].st_name;
|
||||
if (name.starts_with("__gnu_lto_"))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf) {
|
||||
std::string_view data = mf->get_contents();
|
||||
@@ -35,8 +74,20 @@ FileType get_file_type(MappedFile<C> *mf) {
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
switch (*(u16 *)(data.data() + 16)) {
|
||||
case 1: // ET_REL
|
||||
case 1: {
|
||||
// ET_REL
|
||||
elf::Elf32Ehdr &ehdr = *(elf::Elf32Ehdr *)data.data();
|
||||
|
||||
if (ehdr.e_ident[elf::EI_CLASS] == elf::ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<elf::I386>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<elf::X86_64>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
case 3: // ET_DYN
|
||||
return FileType::ELF_DSO;
|
||||
}
|
||||
@@ -83,6 +134,7 @@ inline std::string filetype_to_string(FileType type) {
|
||||
case FileType::THIN_AR: return "THIN_AR";
|
||||
case FileType::TAPI: return "TAPI";
|
||||
case FileType::TEXT: return "TEXT";
|
||||
case FileType::GCC_LTO_OBJ: return "GCC_LTO_OBJ";
|
||||
case FileType::LLVM_BITCODE: return "LLVM_BITCODE";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum PluginStatus {
|
||||
LDPS_OK,
|
||||
LDPS_NO_SYMS,
|
||||
LDPS_BAD_HANDLE,
|
||||
LDPS_ERR,
|
||||
};
|
||||
|
||||
enum PluginTag {
|
||||
LDPT_NULL,
|
||||
LDPT_API_VERSION,
|
||||
LDPT_GOLD_VERSION,
|
||||
LDPT_LINKER_OUTPUT,
|
||||
LDPT_OPTION,
|
||||
LDPT_REGISTER_CLAIM_FILE_HOOK,
|
||||
LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
LDPT_REGISTER_CLEANUP_HOOK,
|
||||
LDPT_ADD_SYMBOLS,
|
||||
LDPT_GET_SYMBOLS,
|
||||
LDPT_ADD_INPUT_FILE,
|
||||
LDPT_MESSAGE,
|
||||
LDPT_GET_INPUT_FILE,
|
||||
LDPT_RELEASE_INPUT_FILE,
|
||||
LDPT_ADD_INPUT_LIBRARY,
|
||||
LDPT_OUTPUT_NAME,
|
||||
LDPT_SET_EXTRA_LIBRARY_PATH,
|
||||
LDPT_GNU_LD_VERSION,
|
||||
LDPT_GET_VIEW,
|
||||
LDPT_GET_INPUT_SECTION_COUNT,
|
||||
LDPT_GET_INPUT_SECTION_TYPE,
|
||||
LDPT_GET_INPUT_SECTION_NAME,
|
||||
LDPT_GET_INPUT_SECTION_CONTENTS,
|
||||
LDPT_UPDATE_SECTION_ORDER,
|
||||
LDPT_ALLOW_SECTION_ORDERING,
|
||||
LDPT_GET_SYMBOLS_V2,
|
||||
LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
LDPT_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
LDPT_GET_SYMBOLS_V3,
|
||||
LDPT_GET_INPUT_SECTION_ALIGNMENT,
|
||||
LDPT_GET_INPUT_SECTION_SIZE,
|
||||
LDPT_REGISTER_NEW_INPUT_HOOK,
|
||||
LDPT_GET_WRAP_SYMBOLS,
|
||||
LDPT_ADD_SYMBOLS_V2,
|
||||
};
|
||||
|
||||
enum PluginApiVersion {
|
||||
LD_PLUGIN_API_VERSION = 1,
|
||||
};
|
||||
|
||||
struct PluginTagValue {
|
||||
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
|
||||
|
||||
template <typename T>
|
||||
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
|
||||
PluginTag tag;
|
||||
union {
|
||||
int val;
|
||||
void *ptr;
|
||||
};
|
||||
};
|
||||
|
||||
enum PluginOutputFileType {
|
||||
LDPO_REL,
|
||||
LDPO_EXEC,
|
||||
LDPO_DYN,
|
||||
LDPO_PIE,
|
||||
};
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
int fd;
|
||||
off_t offset;
|
||||
off_t filesize;
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct PluginSection {
|
||||
const void *handle;
|
||||
int shndx;
|
||||
};
|
||||
|
||||
struct PluginSymbol {
|
||||
char *name;
|
||||
char *version;
|
||||
char def;
|
||||
char symbol_type;
|
||||
char section_kind;
|
||||
int visibility;
|
||||
uint64_t size;
|
||||
char *comdat_key;
|
||||
int resolution;
|
||||
};
|
||||
|
||||
enum PluginSymbolKind {
|
||||
LDPK_DEF,
|
||||
LDPK_WEAKDEF,
|
||||
LDPK_UNDEF,
|
||||
LDPK_WEAKUNDEF,
|
||||
LDPK_COMMON,
|
||||
};
|
||||
|
||||
enum PluginSymbolVisibility {
|
||||
LDPV_DEFAULT,
|
||||
LDPV_PROTECTED,
|
||||
LDPV_INTERNAL,
|
||||
LDPV_HIDDEN,
|
||||
};
|
||||
|
||||
enum PluginSymbolType {
|
||||
LDST_UNKNOWN,
|
||||
LDST_FUNCTION,
|
||||
LDST_VARIABLE,
|
||||
};
|
||||
|
||||
enum PluginSymbolSectionKind {
|
||||
LDSSK_DEFAULT,
|
||||
LDSSK_BSS,
|
||||
};
|
||||
|
||||
enum PluginSymbolResolution {
|
||||
LDPR_UNKNOWN,
|
||||
LDPR_UNDEF,
|
||||
LDPR_PREVAILING_DEF,
|
||||
LDPR_PREVAILING_DEF_IRONLY,
|
||||
LDPR_PREEMPTED_REG,
|
||||
LDPR_PREEMPTED_IR,
|
||||
LDPR_RESOLVED_IR,
|
||||
LDPR_RESOLVED_EXEC,
|
||||
LDPR_RESOLVED_DYN,
|
||||
LDPR_PREVAILING_DEF_IRONLY_EXP,
|
||||
};
|
||||
|
||||
enum PluginLevel {
|
||||
LDPL_INFO,
|
||||
LDPL_WARNING,
|
||||
LDPL_ERROR,
|
||||
LDPL_FATAL,
|
||||
};
|
||||
|
||||
typedef PluginStatus OnloadFn(PluginTagValue *tv);
|
||||
typedef PluginStatus ClaimFileHandler(const PluginInputFile *, int *);
|
||||
typedef PluginStatus AllSymbolsReadHandler();
|
||||
typedef PluginStatus CleanupHandler();
|
||||
typedef PluginStatus NewInputHandler(const PluginInputFile *);
|
||||
|
||||
} // namespace mold
|
||||
+1
-1
@@ -159,7 +159,7 @@ void Subsection<ARM64>::scan_relocations(Context<ARM64> &ctx) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib) {
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<ARM64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
|
||||
@@ -138,19 +138,19 @@ void Subsection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (sym->file && sym->file->is_dylib)
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (sym->file && sym->file->is_dylib)
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib) {
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<X86_64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -194,7 +194,7 @@ void dump_file(std::string path) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
|
||||
auto print_dylib = [&]() {
|
||||
auto print_dylib = [&] {
|
||||
DylibCommand &cmd = *(DylibCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n nameoff: 0x" << cmd.nameoff
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../byteorder.h"
|
||||
#include "../big-endian.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
+10
-1
@@ -23,7 +23,7 @@ split_string(std::string_view str, char sep) {
|
||||
template <typename E>
|
||||
static void create_internal_file(Context<E> &ctx) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.push_back(std::unique_ptr<ObjectFile<E>>(obj));
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
ctx.objs.push_back(obj);
|
||||
|
||||
auto add = [&](std::string_view name) {
|
||||
@@ -474,6 +474,15 @@ static int do_main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (!getenv("MOLD_SUPPRESS_MACHO_WARNING")) {
|
||||
std::cerr <<
|
||||
R"(********************************************************************************
|
||||
mold for macOS is pre-alpha. Do not use unless you know what you are doing.
|
||||
Do not report bugs because it's too early to manage missing features as bugs.
|
||||
********************************************************************************
|
||||
)";
|
||||
}
|
||||
|
||||
return do_main<ARM64>(argc, argv);
|
||||
}
|
||||
|
||||
|
||||
+19
-13
@@ -57,13 +57,16 @@ struct UnwindRecord {
|
||||
template <typename E>
|
||||
class InputFile {
|
||||
public:
|
||||
virtual ~InputFile() = default;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
std::vector<Symbol<E> *> syms;
|
||||
i64 priority = 0;
|
||||
bool is_dylib = false;
|
||||
std::atomic_bool is_alive = false;
|
||||
std::string archive_name;
|
||||
|
||||
virtual bool is_dylib() const = 0;
|
||||
|
||||
protected:
|
||||
InputFile() = default;
|
||||
};
|
||||
@@ -75,6 +78,7 @@ public:
|
||||
|
||||
static ObjectFile *create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name);
|
||||
bool is_dylib() const override { return false; }
|
||||
void parse(Context<E> &ctx);
|
||||
Subsection<E> *find_subsection(Context<E> &ctx, u32 addr);
|
||||
void parse_compact_unwind(Context<E> &ctx, MachSection &hdr);
|
||||
@@ -121,13 +125,14 @@ public:
|
||||
i64 dylib_idx = 0;
|
||||
std::atomic_bool is_needed = false;
|
||||
|
||||
bool is_dylib() const override { return true; }
|
||||
|
||||
private:
|
||||
void parse_dylib(Context<E> &ctx);
|
||||
void read_trie(Context<E> &ctx, u8 *start, i64 offset = 0,
|
||||
const std::string &prefix = "");
|
||||
|
||||
DylibFile() {
|
||||
this->is_dylib = true;
|
||||
this->is_alive = true;
|
||||
}
|
||||
};
|
||||
@@ -382,7 +387,7 @@ public:
|
||||
OutputLazyBindSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__lazy_binding") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = __builtin_ctz(8);
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> &sym, i64 flags);
|
||||
@@ -462,7 +467,7 @@ public:
|
||||
OutputSymtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__symbol_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = __builtin_ctz(8);
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
@@ -484,7 +489,7 @@ public:
|
||||
OutputStrtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__string_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = __builtin_ctz(8);
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
@@ -522,7 +527,7 @@ public:
|
||||
CodeSignatureSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__code_signature") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = __builtin_ctz(16);
|
||||
this->hdr.p2align = std::countr_zero(16U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
@@ -537,7 +542,7 @@ public:
|
||||
DataInCodeSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__data_in_code") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = __builtin_ctz(alignof(DataInCodeEntry));
|
||||
this->hdr.p2align = std::countr_zero(alignof(DataInCodeEntry));
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
@@ -550,7 +555,7 @@ template <typename E>
|
||||
class StubsSection : public Chunk<E> {
|
||||
public:
|
||||
StubsSection(Context<E> &ctx) : Chunk<E>(ctx, "__TEXT", "__stubs") {
|
||||
this->hdr.p2align = __builtin_ctz(2);
|
||||
this->hdr.p2align = std::countr_zero(2U);
|
||||
this->hdr.type = S_SYMBOL_STUBS;
|
||||
this->hdr.attr = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
this->hdr.reserved2 = E::stub_size;
|
||||
@@ -568,7 +573,7 @@ class StubHelperSection : public Chunk<E> {
|
||||
public:
|
||||
StubHelperSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__stub_helper") {
|
||||
this->hdr.p2align = __builtin_ctz(4);
|
||||
this->hdr.p2align = std::countr_zero(4U);
|
||||
this->hdr.attr = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
}
|
||||
|
||||
@@ -594,7 +599,7 @@ class UnwindInfoSection : public Chunk<E> {
|
||||
public:
|
||||
UnwindInfoSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__unwind_info") {
|
||||
this->hdr.p2align = __builtin_ctz(4);
|
||||
this->hdr.p2align = std::countr_zero(4U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
@@ -607,7 +612,7 @@ template <typename E>
|
||||
class GotSection : public Chunk<E> {
|
||||
public:
|
||||
GotSection(Context<E> &ctx) : Chunk<E>(ctx, "__DATA_CONST", "__got") {
|
||||
this->hdr.p2align = __builtin_ctz(8);
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_NON_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
@@ -622,7 +627,7 @@ class LazySymbolPtrSection : public Chunk<E> {
|
||||
public:
|
||||
LazySymbolPtrSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__la_symbol_ptr") {
|
||||
this->hdr.p2align = __builtin_ctz(8);
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
@@ -634,7 +639,7 @@ class ThreadPtrsSection : public Chunk<E> {
|
||||
public:
|
||||
ThreadPtrsSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__thread_ptrs") {
|
||||
this->hdr.p2align = __builtin_ctz(8);
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_THREAD_LOCAL_VARIABLE_POINTERS;
|
||||
}
|
||||
|
||||
@@ -774,6 +779,7 @@ struct Context {
|
||||
bool dylib = false;
|
||||
bool dynamic = true;
|
||||
bool fatal_warnings = false;
|
||||
bool noinhibit_exec = false;
|
||||
bool trace = false;
|
||||
i64 arch = CPU_TYPE_ARM64;
|
||||
i64 headerpad = 256;
|
||||
|
||||
+10
-9
@@ -21,7 +21,7 @@ ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
obj->mf = mf;
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty();
|
||||
ctx.obj_pool.push_back(std::unique_ptr<ObjectFile<E>>(obj));
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -107,7 +107,8 @@ struct SplitRegion {
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
InputSection<E> *isec = nullptr;
|
||||
SplitInfo(InputSection<E> *isec) : isec(isec) {}
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
@@ -185,7 +186,7 @@ void ObjectFile<E>::split_subsections(Context<E> &ctx) {
|
||||
.input_addr = (u32)(isec.hdr.addr + r.offset),
|
||||
.p2align = (u8)isec.hdr.p2align,
|
||||
};
|
||||
subsections.push_back(std::unique_ptr<Subsection<E>>(subsec));
|
||||
subsections.emplace_back(subsec);
|
||||
}
|
||||
|
||||
if (r.symidx != -1)
|
||||
@@ -264,7 +265,7 @@ void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&]() {
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
};
|
||||
|
||||
@@ -343,7 +344,7 @@ static u64 get_rank(InputFile<E> *file, MachSym &msym, bool is_lazy) {
|
||||
return (6 << 24) + file->priority;
|
||||
if (is_lazy)
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib)
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
@@ -357,7 +358,7 @@ static u64 get_rank(Symbol<E> &sym) {
|
||||
return (6 << 24) + file->priority;
|
||||
if (!file->archive_name.empty())
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib)
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
@@ -481,7 +482,7 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
.p2align = (u8)msym.p2align,
|
||||
};
|
||||
|
||||
subsections.push_back(std::unique_ptr<Subsection<E>>(subsec));
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
@@ -513,7 +514,7 @@ InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr);
|
||||
sections.push_back(std::unique_ptr<InputSection<E>>(common_sec));
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
@@ -522,7 +523,7 @@ template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *dylib = new DylibFile<E>;
|
||||
dylib->mf = mf;
|
||||
ctx.dylib_pool.push_back(std::unique_ptr<DylibFile<E>>(dylib));
|
||||
ctx.dylib_pool.emplace_back(dylib);
|
||||
return dylib;
|
||||
};
|
||||
|
||||
|
||||
+15
-22
@@ -1,16 +1,9 @@
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <shared_mutex>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define COMMON_DIGEST_FOR_OPENSSL
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
# define SHA256(data, len, md) CC_SHA256(data, len, md)
|
||||
#else
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
@@ -375,7 +368,7 @@ OutputSection<E>::get_instance(Context<E> &ctx, std::string_view segname,
|
||||
return osec;
|
||||
|
||||
OutputSection<E> *osec = new OutputSection<E>(ctx, segname, sectname);
|
||||
ctx.osec_pool.push_back(std::unique_ptr<OutputSection<E>>(osec));
|
||||
ctx.osec_pool.emplace_back(osec);
|
||||
return osec;
|
||||
}
|
||||
|
||||
@@ -433,7 +426,7 @@ OutputSegment<E>::get_instance(Context<E> &ctx, std::string_view name) {
|
||||
return seg;
|
||||
|
||||
OutputSegment<E> *seg = new OutputSegment<E>(name);
|
||||
ctx.segments.push_back(std::unique_ptr<OutputSegment<E>>(seg));
|
||||
ctx.segments.emplace_back(seg);
|
||||
return seg;
|
||||
}
|
||||
|
||||
@@ -570,12 +563,12 @@ void OutputRebaseSection<E>::compute_size(Context<E> &ctx) {
|
||||
ctx.data_seg->cmd.vmaddr);
|
||||
|
||||
for (Symbol<E> *sym : ctx.got.syms)
|
||||
if (!sym->file->is_dylib)
|
||||
if (!sym->file->is_dylib())
|
||||
enc.add(ctx.data_const_seg->seg_idx,
|
||||
sym->get_got_addr(ctx) - ctx.data_const_seg->cmd.vmaddr);
|
||||
|
||||
for (Symbol<E> *sym : ctx.thread_ptrs.syms)
|
||||
if (!sym->file->is_dylib)
|
||||
if (!sym->file->is_dylib())
|
||||
enc.add(ctx.data_seg->seg_idx,
|
||||
sym->get_tlv_addr(ctx) - ctx.data_seg->cmd.vmaddr);
|
||||
|
||||
@@ -644,13 +637,13 @@ void OutputBindSection<E>::compute_size(Context<E> &ctx) {
|
||||
BindEncoder enc;
|
||||
|
||||
for (Symbol<E> *sym : ctx.got.syms)
|
||||
if (sym->file->is_dylib)
|
||||
if (sym->file->is_dylib())
|
||||
enc.add(((DylibFile<E> *)sym->file)->dylib_idx, sym->name, 0,
|
||||
ctx.data_const_seg->seg_idx,
|
||||
sym->get_got_addr(ctx) - ctx.data_const_seg->cmd.vmaddr);
|
||||
|
||||
for (Symbol<E> *sym : ctx.thread_ptrs.syms)
|
||||
if (sym->file->is_dylib)
|
||||
if (sym->file->is_dylib())
|
||||
enc.add(((DylibFile<E> *)sym->file)->dylib_idx, sym->name, 0,
|
||||
ctx.data_seg->seg_idx,
|
||||
sym->get_tlv_addr(ctx) - ctx.data_seg->cmd.vmaddr);
|
||||
@@ -901,15 +894,15 @@ void OutputSymtabSection<E>::copy_buf(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *ent.sym;
|
||||
|
||||
msym.stroff = ent.stroff;
|
||||
msym.type = (sym.file->is_dylib ? N_UNDF : N_SECT);
|
||||
msym.type = (sym.file->is_dylib() ? N_UNDF : N_SECT);
|
||||
msym.ext = sym.is_extern;
|
||||
|
||||
if (!sym.file->is_dylib)
|
||||
if (!sym.file->is_dylib())
|
||||
msym.value = sym.get_addr(ctx);
|
||||
if (sym.subsec)
|
||||
msym.sect = sym.subsec->isec.osec.sect_idx;
|
||||
|
||||
if (sym.file->is_dylib)
|
||||
if (sym.file->is_dylib())
|
||||
msym.desc = ((DylibFile<E> *)sym.file)->dylib_idx << 8;
|
||||
else if (sym.referenced_dynamically)
|
||||
msym.desc = REFERENCED_DYNAMICALLY;
|
||||
@@ -1011,7 +1004,7 @@ void CodeSignatureSection<E>::write_signature(Context<E> &ctx) {
|
||||
dir.code_limit = this->hdr.offset;
|
||||
dir.hash_size = SHA256_SIZE;
|
||||
dir.hash_type = CS_HASHTYPE_SHA256;
|
||||
dir.page_size = __builtin_ctz(BLOCK_SIZE);
|
||||
dir.page_size = std::countr_zero<u64>(BLOCK_SIZE);
|
||||
dir.exec_seg_base = ctx.text_seg->cmd.fileoff;
|
||||
dir.exec_seg_limit = ctx.text_seg->cmd.filesize;
|
||||
if (ctx.output_type == MH_EXECUTE)
|
||||
@@ -1175,13 +1168,13 @@ u32 UnwindEncoder<E>::encode_personality(Context<E> &ctx, Symbol<E> *sym) {
|
||||
|
||||
for (i64 i = 0; i < personalities.size(); i++)
|
||||
if (personalities[i] == sym)
|
||||
return (i + 1) << __builtin_ctz(UNWIND_PERSONALITY_MASK);
|
||||
return (i + 1) << std::countr_zero(UNWIND_PERSONALITY_MASK);
|
||||
|
||||
if (personalities.size() == 3)
|
||||
Fatal(ctx) << ": too many personality functions";
|
||||
|
||||
personalities.push_back(sym);
|
||||
return personalities.size() << __builtin_ctz(UNWIND_PERSONALITY_MASK);
|
||||
return personalities.size() << std::countr_zero(UNWIND_PERSONALITY_MASK);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1247,7 +1240,7 @@ template <typename E>
|
||||
void GotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 *buf = (u64 *)(ctx.buf + this->hdr.offset);
|
||||
for (i64 i = 0; i < syms.size(); i++)
|
||||
if (!syms[i]->file->is_dylib)
|
||||
if (!syms[i]->file->is_dylib())
|
||||
buf[i] = syms[i]->get_addr(ctx);
|
||||
}
|
||||
|
||||
@@ -1272,7 +1265,7 @@ template <typename E>
|
||||
void ThreadPtrsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 *buf = (u64 *)(ctx.buf + this->hdr.offset);
|
||||
for (i64 i = 0; i < syms.size(); i++)
|
||||
if (!syms[i]->file->is_dylib)
|
||||
if (!syms[i]->file->is_dylib())
|
||||
buf[i] = syms[i]->get_addr(ctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <cstring>
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
#ifdef GIT_HASH
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "byteorder.h"
|
||||
#include "big-endian.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -83,11 +84,18 @@ private:
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
static std::string add_color(C &ctx, std::string msg) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
|
||||
return "mold: " + msg + ": ";
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(C &ctx) : out(ctx, std::cerr) {
|
||||
out << "mold: ";
|
||||
out << add_color(ctx, "fatal");
|
||||
}
|
||||
|
||||
[[noreturn]] ~Fatal() {
|
||||
@@ -109,12 +117,12 @@ template <typename C>
|
||||
class Error {
|
||||
public:
|
||||
Error(C &ctx) : out(ctx, std::cerr) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
out << "mold: \033[0;1;31merror:\033[0m ";
|
||||
else
|
||||
out << "mold: error: ";
|
||||
|
||||
ctx.has_error = true;
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << add_color(ctx, "warning");
|
||||
} else {
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> Error &operator<<(T &&val) {
|
||||
@@ -131,16 +139,10 @@ class Warn {
|
||||
public:
|
||||
Warn(C &ctx) : out(ctx, std::cerr) {
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
out << "mold: \033[0;1;31merror:\033[0m ";
|
||||
else
|
||||
out << "mold: error: ";
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
} else {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
out << "mold: \033[0;1;35mwarning:\033[0m ";
|
||||
else
|
||||
out << "mold: warning: ";
|
||||
out << add_color(ctx, "warning");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,12 +162,12 @@ private:
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
if (align == 0)
|
||||
return val;
|
||||
assert(__builtin_popcount(align) == 1);
|
||||
assert(std::popcount(align) == 1);
|
||||
return (val + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 align_down(u64 val, u64 align) {
|
||||
assert(__builtin_popcount(align) == 1);
|
||||
assert(std::popcount(align) == 1);
|
||||
return val & ~(align - 1);
|
||||
}
|
||||
|
||||
@@ -173,20 +175,18 @@ inline u64 next_power_of_two(u64 val) {
|
||||
assert(val >> 63 == 0);
|
||||
if (val == 0 || val == 1)
|
||||
return 1;
|
||||
return (u64)1 << (64 - __builtin_clzl(val - 1));
|
||||
return (u64)1 << (64 - std::countl_zero(val - 1));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val,
|
||||
Compare cmp = {}) {
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
while (cmp(new_val, old_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_maximum(std::atomic<T> &atomic, u64 new_val,
|
||||
Compare cmp = {}) {
|
||||
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
while (cmp(old_val, new_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val));
|
||||
@@ -280,6 +280,11 @@ std::string_view save_string(C &ctx, const std::string &str) {
|
||||
// Concurrent Map
|
||||
//
|
||||
|
||||
// This is an implementation of a fast concurrent hash map. Unlike
|
||||
// ordinary hash tables, this impl just aborts if it becomes full.
|
||||
// So you need to give a correct estimation of the final size before
|
||||
// using it. We use this hash map to uniquify pieces of data in
|
||||
// mergeable sections.
|
||||
template <typename T>
|
||||
class ConcurrentMap {
|
||||
public:
|
||||
@@ -292,7 +297,7 @@ public:
|
||||
~ConcurrentMap() {
|
||||
if (keys) {
|
||||
free((void *)keys);
|
||||
free((void *)sizes);
|
||||
free((void *)key_sizes);
|
||||
free((void *)values);
|
||||
}
|
||||
}
|
||||
@@ -304,7 +309,7 @@ public:
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
|
||||
sizes = (u32 *)calloc(nbuckets, sizeof(sizes[0]));
|
||||
key_sizes = (u32 *)calloc(nbuckets, sizeof(key_sizes[0]));
|
||||
values = (T *)calloc(nbuckets, sizeof(values[0]));
|
||||
}
|
||||
|
||||
@@ -312,29 +317,28 @@ public:
|
||||
if (!keys)
|
||||
return {nullptr, false};
|
||||
|
||||
assert(__builtin_popcount(nbuckets) == 1);
|
||||
assert(std::popcount<u64>(nbuckets) == 1);
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
|
||||
while (retry < MAX_RETRY) {
|
||||
const char *ptr = keys[idx];
|
||||
if (ptr == locked) {
|
||||
#ifdef __x86_64__
|
||||
asm volatile("pause" ::: "memory");
|
||||
#endif
|
||||
if (ptr == marker) {
|
||||
pause();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr == nullptr) {
|
||||
if (!keys[idx].compare_exchange_weak(ptr, locked))
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker))
|
||||
continue;
|
||||
new (values + idx) T(val);
|
||||
sizes[idx] = key.size();
|
||||
key_sizes[idx] = key.size();
|
||||
keys[idx] = key.data();
|
||||
return {values + idx, true};
|
||||
}
|
||||
|
||||
if (key.size() == sizes[idx] && memcmp(ptr, key.data(), sizes[idx]) == 0)
|
||||
if (key.size() == key_sizes[idx] &&
|
||||
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
|
||||
return {values + idx, false};
|
||||
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
@@ -347,7 +351,7 @@ public:
|
||||
}
|
||||
|
||||
bool has_key(i64 idx) {
|
||||
return keys[idx];
|
||||
return keys[idx].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 2048;
|
||||
@@ -356,56 +360,19 @@ public:
|
||||
|
||||
i64 nbuckets = 0;
|
||||
std::atomic<const char *> *keys = nullptr;
|
||||
u32 *sizes = nullptr;
|
||||
u32 *key_sizes = nullptr;
|
||||
T *values = nullptr;
|
||||
|
||||
private:
|
||||
static constexpr const char *locked = "marker";
|
||||
};
|
||||
|
||||
//
|
||||
// Bit vector
|
||||
//
|
||||
|
||||
class BitVector {
|
||||
class BitRef {
|
||||
public:
|
||||
BitRef(u8 &byte, u8 bitpos) : byte(byte), bitpos(bitpos) {}
|
||||
|
||||
BitRef &operator=(bool val) {
|
||||
if (val)
|
||||
byte |= (1 << bitpos);
|
||||
else
|
||||
byte &= ~(1 << bitpos);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitRef &operator=(const BitRef &other) {
|
||||
*this = (bool)other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return byte & (1 << bitpos);
|
||||
}
|
||||
|
||||
private:
|
||||
u8 &byte;
|
||||
u8 bitpos;
|
||||
};
|
||||
|
||||
public:
|
||||
void resize(i64 size) {
|
||||
vec.reset(new u8[(size + 7) / 8]);
|
||||
memset(vec.get(), 0, (size + 7) / 8);
|
||||
static void pause() {
|
||||
#if defined(__x86_64__)
|
||||
asm volatile("pause");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
BitRef operator[](i64 i) {
|
||||
return BitRef(vec[i / 8], i % 8);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<u8[]> vec = nullptr;
|
||||
static constexpr const char *marker = "marker";
|
||||
};
|
||||
|
||||
//
|
||||
@@ -423,7 +390,7 @@ public:
|
||||
HyperLogLog() : buckets(NBUCKETS) {}
|
||||
|
||||
void insert(u32 hash) {
|
||||
update_maximum(buckets[hash & (NBUCKETS - 1)], __builtin_clz(hash) + 1);
|
||||
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
|
||||
}
|
||||
|
||||
i64 get_cardinality() const;
|
||||
@@ -571,24 +538,24 @@ private:
|
||||
|
||||
// TarFile is a class to create a tar file.
|
||||
//
|
||||
// If you pass `--reproduce=repro.tar` to mold, mold collects all
|
||||
// input files and put them into `repro.tar`, so that it is easy to
|
||||
// If you pass `--repro` to mold, mold collects all input files and
|
||||
// put them into `<output-file-path>.repro.tar`, so that it is easy to
|
||||
// run the same command with the same command line arguments.
|
||||
class TarFile {
|
||||
class TarWriter {
|
||||
public:
|
||||
TarFile(std::string basedir) : basedir(basedir) {}
|
||||
static std::unique_ptr<TarWriter>
|
||||
open(std::string output_path, std::string basedir);
|
||||
|
||||
~TarWriter();
|
||||
void append(std::string path, std::string_view data);
|
||||
void write_to(u8 *buf);
|
||||
i64 size() const { return size_; }
|
||||
|
||||
private:
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
std::string encode_path(std::string path);
|
||||
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
|
||||
|
||||
FILE *out = nullptr;
|
||||
std::string basedir;
|
||||
std::vector<std::pair<std::string, std::string_view>> contents;
|
||||
i64 size_ = BLOCK_SIZE * 2;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -611,21 +578,32 @@ public:
|
||||
return std::string_view((char *)data, size);
|
||||
}
|
||||
|
||||
i64 get_offset() const {
|
||||
return parent ? (data - parent->data + parent->get_offset()) : 0;
|
||||
}
|
||||
|
||||
// Returns a string that uniquely identify a file that is possibly
|
||||
// in an archive.
|
||||
std::string get_identifier() const {
|
||||
if (parent) {
|
||||
// We use the file offset within an archive as an identifier
|
||||
// because archive members may have the same name.
|
||||
return parent->name + ":" + std::to_string(get_offset());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
u8 *data = nullptr;
|
||||
i64 size = 0;
|
||||
i64 mtime = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
int fd = -1;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = path;
|
||||
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
@@ -637,6 +615,10 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
if (fstat(fd, &st) == -1)
|
||||
Fatal(ctx) << path << ": fstat failed: " << errno_string();
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
#ifdef __APPLE__
|
||||
@@ -659,7 +641,7 @@ template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
|
||||
if (MappedFile *mf = MappedFile::open(ctx, path))
|
||||
return mf;
|
||||
Fatal(ctx) << "cannot open " << path;
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifdef __APPLE__
|
||||
# define COMMON_DIGEST_FOR_OPENSSL
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
# define SHA256(data, len, md) CC_SHA256(data, len, md)
|
||||
#else
|
||||
# define OPENSSL_SUPPRESS_DEPRECATED 1
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// In this file, we explicitly undefine _GNU_SOURCE to always
|
||||
// use the POSIX version.
|
||||
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#undef _GNU_SOURCE
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -11,7 +11,11 @@ namespace mold {
|
||||
//
|
||||
// For simplicity, we always emit a PAX header even for a short filename.
|
||||
struct UstarHeader {
|
||||
void flush() {
|
||||
UstarHeader() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
memset(checksum, ' ', sizeof(checksum));
|
||||
memcpy(magic, "ustar", 5);
|
||||
memcpy(version, "00", 2);
|
||||
@@ -43,7 +47,7 @@ struct UstarHeader {
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
std::string TarFile::encode_path(std::string path) {
|
||||
static std::string encode_path(std::string basedir, std::string path) {
|
||||
path = path_clean(basedir + "/" + path);
|
||||
|
||||
// Construct a string which contains something like
|
||||
@@ -55,50 +59,51 @@ std::string TarFile::encode_path(std::string path) {
|
||||
return std::to_string(total) + " path=" + path + "\n";
|
||||
}
|
||||
|
||||
void TarFile::append(std::string path, std::string_view data) {
|
||||
contents.push_back({path, data});
|
||||
|
||||
size_ += BLOCK_SIZE * 2;
|
||||
size_ += align_to(encode_path(path).size(), BLOCK_SIZE);
|
||||
size_ += align_to(data.size(), BLOCK_SIZE);
|
||||
std::unique_ptr<TarWriter>
|
||||
TarWriter::open(std::string output_path, std::string basedir) {
|
||||
FILE *out = fopen(output_path.c_str(), "w");
|
||||
if (!out)
|
||||
return nullptr;
|
||||
return std::unique_ptr<TarWriter>(new TarWriter(out, basedir));
|
||||
}
|
||||
|
||||
void TarFile::write_to(u8 *buf) {
|
||||
u8 *start = buf;
|
||||
memset(buf, 0, size_);
|
||||
TarWriter::~TarWriter() {
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < contents.size(); i++) {
|
||||
assert(buf - start <= size_);
|
||||
void TarWriter::append(std::string path, std::string_view data) {
|
||||
// Write PAX header
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
UstarHeader pax;
|
||||
|
||||
const std::string &path = contents[i].first;
|
||||
std::string_view data = contents[i].second;
|
||||
std::string attr = encode_path(basedir, path);
|
||||
sprintf(pax.size, "%011zo", attr.size());
|
||||
pax.typeflag[0] = 'x';
|
||||
pax.finalize();
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
|
||||
// Write PAX header
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
UstarHeader &pax = *(UstarHeader *)buf;
|
||||
buf += BLOCK_SIZE;
|
||||
// Write pathname
|
||||
fwrite(attr.data(), attr.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
std::string attr = encode_path(path);
|
||||
sprintf(pax.size, "%011zo", attr.size());
|
||||
pax.typeflag[0] = 'x';
|
||||
pax.flush();
|
||||
// Write Ustar header
|
||||
UstarHeader ustar;
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
sprintf(ustar.size, "%011zo", data.size());
|
||||
ustar.finalize();
|
||||
fwrite(&ustar, sizeof(ustar), 1, out);
|
||||
|
||||
// Write pathname
|
||||
memcpy(buf, attr.data(), attr.size());
|
||||
buf += align_to(attr.size(), BLOCK_SIZE);
|
||||
// Write file contents
|
||||
fwrite(data.data(), data.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// Write Ustar header
|
||||
UstarHeader &ustar = *(UstarHeader *)buf;
|
||||
buf += BLOCK_SIZE;
|
||||
// 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);
|
||||
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
sprintf(ustar.size, "%011zo", data.size());
|
||||
ustar.flush();
|
||||
|
||||
// Write file contents
|
||||
memcpy(buf, data.data(), data.size());
|
||||
buf += align_to(data.size(), BLOCK_SIZE);
|
||||
}
|
||||
assert(ftell(out) % BLOCK_SIZE == 0);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ "$(uname -m)" = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
echo 'int main() {}' | aarch64-linux-gnu-gcc -o $t/exe -xc - >& /dev/null \
|
||||
|| { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | aarch64-linux-gnu-gcc -o $t/a.o -c -g -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
aarch64-linux-gnu-gcc -B"`dirname "$mold"`" -o $t/exe $t/a.o -static
|
||||
|
||||
readelf -p .comment $t/exe | grep -qw mold
|
||||
|
||||
readelf -a $t/exe > $t/log
|
||||
grep -Pq 'Machine:\s+AArch64' $t/log
|
||||
qemu-aarch64 -L /usr/aarch64-linux-gnu $t/exe | grep -q 'Hello world'
|
||||
|
||||
echo OK
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -x assembler -
|
||||
.globl foo
|
||||
foo = 0x800000
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -fno-PIC -xc -
|
||||
#define _GNU_SOURCE 1
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ucontext.h>
|
||||
|
||||
void handler(int signum, siginfo_t *info, void *ptr) {
|
||||
ucontext_t *u = (ucontext_t *)ptr;
|
||||
printf("ip=0x%llx\n", u->uc_mcontext.gregs[REG_RIP]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void foo();
|
||||
|
||||
int main() {
|
||||
struct sigaction act;
|
||||
act.sa_flags = SA_SIGINFO | SA_RESETHAND;
|
||||
act.sa_sigaction = handler;
|
||||
sigemptyset(&act.sa_mask);
|
||||
sigaction(SIGSEGV, &act, 0);
|
||||
foo();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe -no-pie $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q '^ip=0x800000$'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
+8
-4
@@ -1,21 +1,25 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ "$(uname -m)" = x86_64 ] || { echo skipped; exit; }
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
echo '.globl _start; _start: jmp loop' | $CC -o $t/a.o -c -x assembler -
|
||||
echo '.globl loop; loop: jmp loop' | $CC -o $t/b.o -c -x assembler -
|
||||
"$mold" -static -o $t/exe $t/a.o $t/b.o
|
||||
objdump -d $t/exe > /dev/null
|
||||
$OBJDUMP -d $t/exe > /dev/null
|
||||
file $t/exe | grep -q ELF
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -38,6 +42,6 @@ int main() {
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
|
||||
$t/exe | grep -q '3 3 1'
|
||||
$QEMU $t/exe | grep -q '3 3 1'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -38,6 +42,6 @@ int main() {
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
|
||||
$t/exe | grep -q '3 3 0'
|
||||
$QEMU $t/exe | grep -q '3 3 0'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -38,6 +42,6 @@ int main() {
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
|
||||
$t/exe | grep -q '3 4 0'
|
||||
$QEMU $t/exe | grep -q '3 4 0'
|
||||
|
||||
echo OK
|
||||
+6
-2
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -17,7 +21,7 @@ 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 -Pq 'GNU.*0x00000010.*NT_GNU_BUILD_ID.*Build ID: ............4...[89abcdef]'
|
||||
grep -Eq 'GNU.*0x00000010.*NT_GNU_BUILD_ID.*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'
|
||||
@@ -28,7 +32,7 @@ readelf -n $t/exe | grep -q 'GNU.*0x00000014.*NT_GNU_BUILD_ID'
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=sha256
|
||||
readelf -n $t/exe | grep -q 'GNU.*0x00000020.*NT_GNU_BUILD_ID'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=0xdeadbeef
|
||||
readelf -n $t/exe | grep -q 'Build ID: deadbeef'
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=0xdeadbeefdeadbeef
|
||||
readelf -n $t/exe | grep -q 'Build ID: deadbeefdeadbeef'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -41,6 +45,6 @@ int main() {
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/a.so $t/b.o $t/c.o
|
||||
$t/exe | grep -q '^1 1 1$'
|
||||
$QEMU $t/exe | grep -q '^1 1 1$'
|
||||
|
||||
echo OK
|
||||
+6
-2
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -16,15 +20,15 @@ int main() { foo(); }
|
||||
EOF
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics 2> $t/log
|
||||
grep -Pq '\e' $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=always 2> $t/log
|
||||
grep -Pq '\e' $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=never 2> $t/log
|
||||
! grep -Pq '\e' $t/log || false
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=auto 2> $t/log
|
||||
! grep -Pq '\e' $t/log || false
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
echo OK
|
||||
+6
-2
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -40,7 +44,7 @@ rm -f $t/e.a
|
||||
ar rcs $t/e.a $t/b.o $t/c.o $t/d.o
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/e.a
|
||||
$t/exe | grep -q '5 0 0 -1'
|
||||
$QEMU $t/exe | grep -q '5 0 0 -1'
|
||||
|
||||
cat <<EOF | $CC -fcommon -xc -c -o $t/f.o -
|
||||
int bar = 0;
|
||||
@@ -52,6 +56,6 @@ rm -f $t/f.a
|
||||
ar rcs $t/f.a $t/b.o $t/f.o
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/f.a
|
||||
$t/exe | grep -q '5 0 7 2'
|
||||
$QEMU $t/exe | grep -q '5 0 7 2'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -37,6 +41,6 @@ rm -f $t/e.a
|
||||
ar rcs $t/e.a $t/d.o
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/c.a $t/e.a
|
||||
$t/exe | grep -q 5
|
||||
$QEMU $t/exe | grep -q 5
|
||||
|
||||
echo OK
|
||||
+7
-3
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -29,7 +33,7 @@ int main() {
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$t/exe | grep -q '0 5 42'
|
||||
$QEMU $t/exe | grep -q '0 5 42'
|
||||
|
||||
readelf --sections $t/exe > $t/log
|
||||
grep -q '.common .*NOBITS' $t/log
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -12,13 +16,13 @@ mkdir -p $t
|
||||
|
||||
which dwarfdump >& /dev/null || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | g++ -c -o $t/a.o -g -gz=zlib-gnu -xc++ -
|
||||
cat <<EOF | $CXX -c -o $t/a.o -g -gz=zlib-gnu -xc++ -
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | g++ -c -o $t/b.o -g -gz=zlib -xc++ -
|
||||
cat <<EOF | $CXX -c -o $t/b.o -g -gz=zlib -xc++ -
|
||||
int foo() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -22,7 +26,7 @@ cat <<EOF | $CC -shared -o $t/b.so -xc -
|
||||
__attribute__((visibility("protected"))) int foo;
|
||||
EOF
|
||||
|
||||
! $CC -B. $t/a.o $t/b.so -o $t/exe >& $t/log || false
|
||||
! $CC -B. $t/a.o $t/b.so -o $t/exe >& $t/log -no-pie || false
|
||||
fgrep -q 'cannot make copy relocation for protected symbol' $t/log
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -11,11 +15,32 @@ t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern const char readonly[100];
|
||||
extern char readwrite[100];
|
||||
|
||||
static int segv = 0;
|
||||
static jmp_buf buf;
|
||||
|
||||
void handler(int sig) {
|
||||
segv = 1;
|
||||
longjmp(buf, 1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
return readonly[0] + readwrite[0];
|
||||
signal(SIGSEGV, handler);
|
||||
|
||||
readwrite[0] = 5;
|
||||
int x = segv;
|
||||
|
||||
if (setjmp(buf) == 0)
|
||||
*(char *)readonly = 5;
|
||||
int y = segv;
|
||||
|
||||
printf("sigsegv %d %d\n", x, y);
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -24,10 +49,7 @@ const char readonly[100] = "abc";
|
||||
char readwrite[100] = "abc";
|
||||
EOF
|
||||
|
||||
$CC -B. $t/a.o $t/b.so -o $t/exe
|
||||
readelf -a $t/exe > $t/log
|
||||
|
||||
grep -Pqz '(?s)\[(\d+)\] .dynbss.rel.ro .* \1 readonly' $t/log
|
||||
grep -Pqz '(?s)\[(\d+)\] .dynbss .* \1 readwrite' $t/log
|
||||
$CC -B. $t/a.o $t/b.so -o $t/exe -no-pie
|
||||
$QEMU $t/exe | grep -q '^sigsegv 0 1$'
|
||||
|
||||
echo OK
|
||||
+7
-3
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -35,6 +39,6 @@ EOF
|
||||
|
||||
$CC -B. -shared -o $t/c.so $t/c.o
|
||||
$CC -B. -no-pie -o $t/exe $t/a.o $t/b.o $t/c.so
|
||||
$t/exe | grep -q '42 42 1'
|
||||
$QEMU $t/exe | grep -q '42 42 1'
|
||||
|
||||
echo OK
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -g -xc++ -
|
||||
#include <iostream>
|
||||
struct Foo {
|
||||
Foo() { std::cout << "Hello world\n"; }
|
||||
};
|
||||
Foo x;
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/b.o -g -xc++ -
|
||||
#include <iostream>
|
||||
struct Foo {
|
||||
Foo() { std::cout << "Hello world\n"; }
|
||||
};
|
||||
Foo y;
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -o $t/c.o -c -xc++ -g -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CXX -B. -o $t/exe $t/a.o $t/b.o $t/c.o -g
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
|
||||
dwarfdump $t/exe > /dev/null
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
+7
-3
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -28,6 +32,6 @@ EOF
|
||||
$CC -B. -o $t/exe $t/a.o -pie -Wl,-defsym=foo=16 \
|
||||
-Wl,-defsym=bar=0x2000 -Wl,-defsym=baz=print
|
||||
|
||||
$t/exe | grep -q '^Hello 0x10 0x2000$'
|
||||
$QEMU $t/exe | grep -q '^Hello 0x10 0x2000$'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -21,10 +25,10 @@ EOF
|
||||
grep -q 'undefined symbol: .*: _Z3fooii' $t/log
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o -Wl,-demangle 2> $t/log || false
|
||||
grep -Pq 'undefined symbol: .*: foo\(int, int\)' $t/log
|
||||
grep -Eq 'undefined symbol: .*: foo\(int, int\)' $t/log
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o 2> $t/log || false
|
||||
grep -Pq 'undefined symbol: .*: foo\(int, int\)' $t/log
|
||||
grep -Eq 'undefined symbol: .*: foo\(int, int\)' $t/log
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
extern int Pi;
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-dependency-file=$t/dep
|
||||
|
||||
grep -q "dependency-file/exe:.*/a.o " $t/dep
|
||||
grep -q ".*/a.o:$" $t/dep
|
||||
|
||||
echo OK
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fPIC -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,-rpath=/foo
|
||||
readelf --dynamic $t/b.so | grep -q 'RUNPATH.*/foo'
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,-rpath=/foo -Wl,-enable-new-dtags
|
||||
readelf --dynamic $t/b.so | grep -q 'RUNPATH.*/foo'
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,-rpath=/foo -Wl,-disable-new-dtags
|
||||
readelf --dynamic $t/b.so | grep -q 'RPATH.*/foo'
|
||||
|
||||
echo OK
|
||||
+8
-2
@@ -1,15 +1,21 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = riscv64 ] && { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -x assembler -Wa,-L -
|
||||
.text
|
||||
.globl _start
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
+37
-18
@@ -1,37 +1,56 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -x assembler -
|
||||
.globl main, init, fini
|
||||
main:
|
||||
ret
|
||||
init:
|
||||
ret
|
||||
fini:
|
||||
ret
|
||||
[ $MACHINE = riscv64 ] && { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
int main() {
|
||||
keep();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
readelf -a $t/exe > $t/log
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
grep -Pqz '(?s)\(INIT\)\s+0x([0-9a-f]+)\b.*\1\s+0 \w+\s+GLOBAL \w+\s+\d+ _init\b' $t/log
|
||||
void init() {
|
||||
printf("init\n");
|
||||
}
|
||||
|
||||
grep -Pqz '(?s)\(FINI\)\s+0x([0-9a-f]+)\b.*\1\s+0 \w+\s+GLOBAL \w+\s+\d+ _fini\b' $t/log
|
||||
void fini() {
|
||||
printf("fini\n");
|
||||
}
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-init,init -Wl,-fini,fini
|
||||
readelf -a $t/exe > $t/log
|
||||
void keep() {}
|
||||
EOF
|
||||
|
||||
grep -Pqz '(?s)\(INIT\)\s+0x([0-9a-f]+)\b.*\1\s+0 NOTYPE GLOBAL DEFAULT\s+\d+ init\b' $t/log
|
||||
$CC -B. -o $t/c.so -shared $t/b.o
|
||||
$CC -B. -o $t/d.so -shared $t/b.o -Wl,-init,init -Wl,-fini,fini
|
||||
|
||||
grep -Pqz '(?s)\(FINI\)\s+0x([0-9a-f]+)\b.*\1\s+0 NOTYPE GLOBAL DEFAULT\s+\d+ fini\b' $t/log
|
||||
$CC -o $t/exe1 $t/a.o $t/c.so
|
||||
$CC -o $t/exe2 $t/a.o $t/d.so
|
||||
|
||||
$QEMU $t/exe1 > $t/log1
|
||||
$QEMU $t/exe2 > $t/log2
|
||||
|
||||
! grep -q init $t/log1 || false
|
||||
! grep -q fini $t/log1 || false
|
||||
|
||||
grep -q init $t/log2
|
||||
grep -q fini $t/log2
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -29,7 +33,7 @@ $CC -B. -o $t/exe $t/b.o -L $t -lfoo
|
||||
readelf --dynamic $t/exe | fgrep -q 'Shared library: [libfoo]'
|
||||
|
||||
$CC -B. -o $t/exe $t/b.o $t/libbar.so
|
||||
readelf --dynamic $t/exe | grep -Pq 'Shared library: \[.*dt-needed/libbar\.so\]'
|
||||
readelf --dynamic $t/exe | grep -Eq 'Shared library: \[.*dt-needed/libbar\.so\]'
|
||||
|
||||
$CC -B. -o $t/exe $t/b.o -L$t -lbar
|
||||
readelf --dynamic $t/exe | fgrep -q 'Shared library: [libbar.so]'
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -13,7 +17,6 @@ mkdir -p $t
|
||||
cat <<EOF | $CC -c -o $t/a.o -x assembler -
|
||||
.globl _start
|
||||
_start:
|
||||
ret
|
||||
EOF
|
||||
|
||||
"$mold" -o $t/exe $t/a.o
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
+8
-8
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -15,10 +19,10 @@ echo '.globl main; main:' | $CC -o $t/a.o -c -x assembler -
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
grep -Pq 'Shared library:.*\blibc.so\b' $t/log
|
||||
grep -Eq 'Shared library:.*\blibc\b' $t/log
|
||||
|
||||
readelf -W --dyn-syms --use-dynamic $t/exe > $t/log2
|
||||
grep -Pq 'FUNC\s+GLOBAL\s+DEFAULT\s+UND\s+__libc_start_main' $t/log2
|
||||
grep -Eq 'FUNC\s+GLOBAL\s+DEFAULT\s+UND\s+__libc_start_main' $t/log2
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
#include <stdio.h>
|
||||
@@ -28,8 +32,4 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe -pie $t/b.o
|
||||
count=$(readelf -W --relocs $t/exe | grep -P 'R_[\w\d_]+_RELATIVE' | wc -l)
|
||||
readelf -W --dynamic $t/exe | grep -q "RELACOUNT.*\b$count\b"
|
||||
|
||||
echo OK
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -fPIC -xc -
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
puts("Hello world");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-emit-relocs
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
|
||||
readelf -S $t/exe | grep -Pq 'rela?\.text'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -21,6 +25,6 @@ rm -f $t/b.script
|
||||
touch $t/b.script
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,--version-script,$t/b.script
|
||||
$t/exe | grep -q 'Hello world'
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
|
||||
echo OK
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
@@ -11,7 +15,7 @@ t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
# Skip if target is not x86-64
|
||||
[ "$(uname -m)" = x86_64 ] || { echo skipped; exit; }
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void _start() {}
|
||||
|
||||
+10
-17
@@ -1,27 +1,20 @@
|
||||
#!/bin/bash
|
||||
export LANG=
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
testname=$(basename -s .sh "$0")
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
case "$(uname -m)" in
|
||||
i386 | i686 | x86_64)
|
||||
base=0x201000
|
||||
;;
|
||||
aarch64)
|
||||
base=0x210000
|
||||
;;
|
||||
*)
|
||||
echo skipped
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -x assembler -
|
||||
.globl foo, bar
|
||||
@@ -33,14 +26,14 @@ EOF
|
||||
|
||||
"$mold" -e foo -static -o $t/exe $t/a.o
|
||||
readelf -e $t/exe > $t/log
|
||||
grep -q "Entry point address:.*$base" $t/log
|
||||
grep -q "Entry point address:.*0x201000" $t/log
|
||||
|
||||
"$mold" -e bar -static -o $t/exe $t/a.o
|
||||
readelf -e $t/exe > $t/log
|
||||
grep -q "$(printf 'Entry point address:.*0x%x' $((base + 8)))" $t/log
|
||||
grep -q "Entry point address:.*0x201008" $t/log
|
||||
|
||||
"$mold" -static -o $t/exe $t/a.o
|
||||
readelf -e $t/exe > $t/log
|
||||
grep -q "Entry point address:.*$base" $t/log
|
||||
grep -q "Entry point address:.*0x201000" $t/log
|
||||
|
||||
echo OK
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -xc++ -fPIC -
|
||||
int main() {
|
||||
try {
|
||||
throw 0;
|
||||
} catch (int x) {
|
||||
return x;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CXX -B. -o $t/exe $t/a.o -mcmodel=large
|
||||
$QEMU $t/exe
|
||||
|
||||
$CXX -B. -o $t/exe $t/a.o -static -mcmodel=large
|
||||
$QEMU $t/exe
|
||||
|
||||
echo OK
|
||||
Loaded 100 of 496 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user