mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 17:52:02 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edc2f852a1 | ||
|
|
348e44a9c4 | ||
|
|
42f663cac0 | ||
|
|
4dd5d2f8cb | ||
|
|
4cdfc7e72c | ||
|
|
9516da1751 | ||
|
|
f3de82b2a2 | ||
|
|
c4c506c1cb | ||
|
|
0c62a91ded | ||
|
|
2fd0a4c60a | ||
|
|
d3188e39df | ||
|
|
3229b5b7e3 | ||
|
|
d9ede96aca | ||
|
|
4b4219fc33 | ||
|
|
02ead29989 | ||
|
|
37919009ab | ||
|
|
782ff4f582 | ||
|
|
3c468c71a4 | ||
|
|
a31679cf04 | ||
|
|
9cd65ed152 | ||
|
|
79ad405319 | ||
|
|
b60a164fcc | ||
|
|
2d4ba3ac74 | ||
|
|
42eabf9f71 | ||
|
|
aa4b3f7d01 | ||
|
|
2d121b8d6d | ||
|
|
c86a59a511 | ||
|
|
f7dea3a21b | ||
|
|
651ae8a2a8 | ||
|
|
5f7aca6276 | ||
|
|
39cdf61695 | ||
|
|
5c4955763b | ||
|
|
dae070283b | ||
|
|
403655904b | ||
|
|
44942b638b | ||
|
|
ceffd4679b | ||
|
|
6fa742be67 | ||
|
|
484e357971 | ||
|
|
53a5a37370 | ||
|
|
41579888b6 | ||
|
|
1e2d84038e | ||
|
|
6c7700b051 | ||
|
|
34c53777fb | ||
|
|
7be1b66791 | ||
|
|
4bd80ec21c | ||
|
|
a9ab0ff452 | ||
|
|
206bf65c50 | ||
|
|
2b3e8eec05 | ||
|
|
9eac818254 | ||
|
|
6fe0cadfa1 | ||
|
|
1a2baee096 | ||
|
|
728b265cbd | ||
|
|
9fa61b9298 | ||
|
|
6ad942cb27 | ||
|
|
eed4496af2 | ||
|
|
17c292c2b4 | ||
|
|
ab1ab78f8a | ||
|
|
8506065fd2 | ||
|
|
a291d0d448 | ||
|
|
690fefb97d | ||
|
|
c7c1779ad7 | ||
|
|
ec095e7513 | ||
|
|
69a91e8aa0 | ||
|
|
af46000645 | ||
|
|
b1658c4702 | ||
|
|
9f85267b44 | ||
|
|
f8d20cd3ce | ||
|
|
a396fa400f | ||
|
|
d8a4f14fd0 | ||
|
|
c8b5fae2d0 | ||
|
|
0ce32d36d5 | ||
|
|
01cd5d1a32 | ||
|
|
39a25e2394 | ||
|
|
bfdd75ed38 | ||
|
|
5411f4216e | ||
|
|
3cf00e30d5 | ||
|
|
0f714710f0 | ||
|
|
83ec409426 | ||
|
|
e6ae83fbf9 | ||
|
|
00702773f4 | ||
|
|
1739cfbfb4 | ||
|
|
ff689a1253 | ||
|
|
ae427d133e | ||
|
|
d92af2456d | ||
|
|
b3e1092879 | ||
|
|
46ef71eb75 | ||
|
|
871ef8cf14 | ||
|
|
c1b1caa854 | ||
|
|
dcab76697e | ||
|
|
cf11efb147 | ||
|
|
a688b73f8e | ||
|
|
9bb50ae7a5 | ||
|
|
ead6235470 | ||
|
|
967006479f | ||
|
|
f843daee9b | ||
|
|
c7129916ee | ||
|
|
e234ce25b6 | ||
|
|
02e1b9de10 | ||
|
|
effde5ffa9 | ||
|
|
1d3706285e | ||
|
|
085a1148fd | ||
|
|
5cf3e53ab9 | ||
|
|
a5aa387cb4 | ||
|
|
f99cb33c2d | ||
|
|
e55703405b | ||
|
|
4c71174d5d | ||
|
|
2ea08ac4b3 | ||
|
|
82ace062db | ||
|
|
17f0d85efb | ||
|
|
a994d71fa1 | ||
|
|
0eea8f4015 | ||
|
|
2e3b56ee3f | ||
|
|
b8bcb8a2a3 | ||
|
|
ac3ee91cbd | ||
|
|
18df821625 | ||
|
|
79b3fc4b6a | ||
|
|
9e09e74db3 | ||
|
|
9ee10ba4bd | ||
|
|
fe7c8c26b7 | ||
|
|
23576b6e48 | ||
|
|
cfa9e404d1 | ||
|
|
641360b0aa | ||
|
|
4ccc9eb89a | ||
|
|
a801dde186 | ||
|
|
e2d53f26e2 | ||
|
|
742cdeec66 | ||
|
|
c2116aa7ca | ||
|
|
fed950837a | ||
|
|
508890d33d | ||
|
|
141556db44 | ||
|
|
8589403fe0 | ||
|
|
cd2224742f | ||
|
|
0b1d11ed01 | ||
|
|
22443b1490 | ||
|
|
a3a53e9a0c | ||
|
|
dd3d045396 | ||
|
|
673d790372 | ||
|
|
6cac0aba6a | ||
|
|
1a13c5076e | ||
|
|
c504911bf6 | ||
|
|
db5fa8a8cd | ||
|
|
c03400303f | ||
|
|
7f91a134ef | ||
|
|
d4891d5ff1 | ||
|
|
bdc2336265 | ||
|
|
2a0f65084e | ||
|
|
db146cfc37 | ||
|
|
49f38828d1 | ||
|
|
0c972cf59d | ||
|
|
f057fdadba | ||
|
|
3fb2571255 | ||
|
|
619f741ca9 | ||
|
|
713d81947a | ||
|
|
f76d3e0d0c | ||
|
|
488cd5d933 | ||
|
|
55bcb72be7 | ||
|
|
7f8872b2f1 | ||
|
|
139807080b | ||
|
|
86b37b112f | ||
|
|
d56f528c27 | ||
|
|
a1b81b249f | ||
|
|
fa0de2f03b | ||
|
|
a357445654 | ||
|
|
8f5c49c655 | ||
|
|
03b5ce7c6f | ||
|
|
63df2a3c8b | ||
|
|
9a09ead865 | ||
|
|
c2380ff7bc | ||
|
|
c49a8b5b07 | ||
|
|
51fec5f1ff | ||
|
|
ac20d8729b | ||
|
|
7e9fedd203 | ||
|
|
576cd86ecb | ||
|
|
bb1db6cf17 | ||
|
|
3529a2fcc6 | ||
|
|
e842bee049 | ||
|
|
3c8684c528 | ||
|
|
108815dbe1 | ||
|
|
d75386b545 | ||
|
|
bb1934bb9f | ||
|
|
f01817f39e | ||
|
|
fad4c1b56c | ||
|
|
3c8868280d | ||
|
|
a665ca766d | ||
|
|
0f85278c73 | ||
|
|
085727234a | ||
|
|
83fe073e97 | ||
|
|
8a25c1bfc1 | ||
|
|
a94cd68ce1 | ||
|
|
68ac03d19c | ||
|
|
b79bc35fd9 | ||
|
|
90839d747f | ||
|
|
1b1ab744e5 | ||
|
|
f02db0fa0f | ||
|
|
fbcee0b716 | ||
|
|
c105cfd35a | ||
|
|
bb402480a5 | ||
|
|
c983321fed | ||
|
|
43ebc96bfe | ||
|
|
8ae1ad57a4 | ||
|
|
bd39bfdc5b | ||
|
|
a11566795d | ||
|
|
3676c596df | ||
|
|
0947836a44 | ||
|
|
2383e42a5c | ||
|
|
049dbb73fe | ||
|
|
95894ef2e2 | ||
|
|
7f7a744ebb | ||
|
|
3833a4a457 | ||
|
|
a0516a371f | ||
|
|
29491df6f2 | ||
|
|
599ed1dde0 | ||
|
|
143111ae5a | ||
|
|
d753ea5bdb | ||
|
|
b205eb7fa6 | ||
|
|
d8baee9605 | ||
|
|
6dacc5192a | ||
|
|
4f5f88258c | ||
|
|
f5abedad01 | ||
|
|
02aee3b43e | ||
|
|
c1406860fa | ||
|
|
53759731cf | ||
|
|
b018baec8a | ||
|
|
28ddea5371 | ||
|
|
82989a8b7c | ||
|
|
d78b748696 | ||
|
|
77968a7fc1 | ||
|
|
00a68c5fcf | ||
|
|
6f9752a1bf | ||
|
|
6a32828dd8 | ||
|
|
b5e0d7a7c9 | ||
|
|
3b0b28ff97 | ||
|
|
6ce3730083 | ||
|
|
d3e7d88753 | ||
|
|
9dee80eb4f | ||
|
|
eb99c87f75 | ||
|
|
e45f2a8efb | ||
|
|
86ab3890d6 | ||
|
|
3b5ccb9342 | ||
|
|
9441170d39 | ||
|
|
ffb83247f4 | ||
|
|
13f42071b9 | ||
|
|
032931fa3b | ||
|
|
472b325349 | ||
|
|
b852d925c2 | ||
|
|
56cdba50cf | ||
|
|
cfec68e054 | ||
|
|
d9f850b032 | ||
|
|
1f012337c0 | ||
|
|
90cf642ca7 | ||
|
|
d12c48ce63 | ||
|
|
67b9d6cb8d | ||
|
|
2e12e635a0 | ||
|
|
1b5631990c | ||
|
|
acf846acc0 | ||
|
|
e1347d8f7d | ||
|
|
6d809b56e6 | ||
|
|
4528507155 | ||
|
|
da07871416 | ||
|
|
354d0254ee | ||
|
|
b711bf2009 | ||
|
|
94e2a71866 | ||
|
|
f467ad1add | ||
|
|
f631477a68 | ||
|
|
56479e724f | ||
|
|
d752bac24a | ||
|
|
a50258fdf8 | ||
|
|
710dfd01d6 | ||
|
|
03b1a1cc68 | ||
|
|
1c73fd57fc | ||
|
|
24078a4395 | ||
|
|
270cbf42df | ||
|
|
ea75dae8ec | ||
|
|
6d290babe2 | ||
|
|
428faa75b2 | ||
|
|
829c9a980e | ||
|
|
76af9907fd | ||
|
|
9893bb331f | ||
|
|
6108e60936 | ||
|
|
9e2e376f5a | ||
|
|
2db8b3fa48 | ||
|
|
e2c6f0c171 | ||
|
|
eba7cdade2 | ||
|
|
ca88e45bf3 | ||
|
|
19776686ec | ||
|
|
e0a150a22c | ||
|
|
0df6063d8a | ||
|
|
5199414933 | ||
|
|
85f1dec81d | ||
|
|
97c937a56d | ||
|
|
f1113f7c76 | ||
|
|
0ced83597f | ||
|
|
1f5fbb808b | ||
|
|
5fb6091fb7 | ||
|
|
e764557007 | ||
|
|
961cc10e0d | ||
|
|
49fc7da32e | ||
|
|
dd56734fda | ||
|
|
2015347b0c | ||
|
|
4daa9aad36 | ||
|
|
084ca55a36 | ||
|
|
e06d886252 | ||
|
|
9fe3d750cc | ||
|
|
13bb00bfa2 | ||
|
|
79a72011b7 | ||
|
|
f2b278ce10 | ||
|
|
ec10452b67 | ||
|
|
ba3fd65bcc | ||
|
|
f816c45b88 | ||
|
|
ca14cdf9b6 | ||
|
|
6d667fad45 | ||
|
|
aa644914fe | ||
|
|
1cbe4d44ae | ||
|
|
91805d1e26 | ||
|
|
9212f2ebd7 | ||
|
|
784fd27e9e | ||
|
|
b78f237a68 | ||
|
|
1582b720d5 | ||
|
|
710127977e | ||
|
|
8e07496e08 | ||
|
|
163f5db0d1 | ||
|
|
be2e1b17cd | ||
|
|
5f5e73360b | ||
|
|
b0b015bc2e | ||
|
|
7f66689e21 | ||
|
|
17a5c3e0ab | ||
|
|
3ed5b8ddf7 |
No files matched your search
+30
-11
@@ -51,26 +51,45 @@ jobs:
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha,mips,mipsel}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf {gcc,g++}-10-{mips64,mips64el}-linux-gnuabi64
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump xz-utils {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
|
||||
|
||||
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
|
||||
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
|
||||
ln -sf /usr/bin/$i-linux-gnu-g++-10 /usr/bin/$i-linux-gnu-g++
|
||||
for i in {i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu arm-linux-gnueabihf; do
|
||||
ln -sf /usr/bin/$i-gcc-10 /usr/bin/$i-gcc
|
||||
ln -sf /usr/bin/$i-g++-10 /usr/bin/$i-g++
|
||||
done
|
||||
|
||||
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++
|
||||
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
mkdir /usr/local/rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.08.26/riscv32-glibc-ubuntu-20.04-nightly-2022.08.26-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/riscv32-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
|
||||
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-objdump /usr/bin/riscv32-linux-gnu-objdump
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-gcc /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
|
||||
done
|
||||
|
||||
# Install a LoongArch toolchain
|
||||
mkdir /usr/local/larch
|
||||
wget -O- -q https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz | tar -C /usr/local/larch --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /usr/local/larch/target /usr/loongarch64-linux-gnu
|
||||
cp -r /usr/local/larch/loongarch64-unknown-linux-gnu/lib/* /usr/loongarch64-linux-gnu/lib64/
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /usr/local/larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
|
||||
done
|
||||
|
||||
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-gcc -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-gcc
|
||||
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-g++ -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-g++
|
||||
chmod 755 /usr/bin/loongarch64-linux-gnu-{gcc,g++}
|
||||
|
||||
wget -O/usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2023.08.08/qemu-loongarch64
|
||||
chmod 755 /usr/local/bin/qemu-loongarch64
|
||||
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
@@ -79,7 +98,7 @@ jobs:
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On ..
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On -DMOLD_ENABLE_QEMU_TESTS_LOONGARCH=On ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
|
||||
@@ -3,15 +3,7 @@
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
+35
-23
@@ -43,8 +43,8 @@
|
||||
# of the mold linker of the same version will have the exactly same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(mold VERSION 2.0.0)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.3.2)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
@@ -64,8 +64,12 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
@@ -97,7 +101,7 @@ if(MOLD_USE_TSAN)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
|
||||
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
|
||||
#
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
@@ -105,13 +109,12 @@ endif()
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
target_link_libraries(mold PRIVATE libcrypto.a)
|
||||
endif()
|
||||
|
||||
# Find zlib. If libz.so is not found, we compile a bundled one and
|
||||
# statically-link it to mold.
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND)
|
||||
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
@@ -120,12 +123,23 @@ else()
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. Just like zlib, if libzstd.so is not
|
||||
# found, we compile a bundled one and statically-link it to mold.
|
||||
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
|
||||
# is not found, we compile a bundled one and statically-link it to mold.
|
||||
find_package(BLAKE3 QUIET)
|
||||
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE BLAKE3::blake3)
|
||||
else()
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. If libzstd.so is not found, we compile a
|
||||
# bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
|
||||
if(HAVE_ZSTD_H)
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
@@ -146,12 +160,10 @@ endif()
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles(
|
||||
"static_assert(sizeof(void *) == 4); int main() {}" P32)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD" OFF)
|
||||
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT P32" OFF)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
@@ -236,11 +248,6 @@ if(NOT APPLE AND NOT WIN32)
|
||||
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
|
||||
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
|
||||
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
@@ -289,12 +296,14 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
|
||||
S390X SPARC64 M68K SH4 ALPHA MIPS64LE MIPS64BE)
|
||||
S390X SPARC64 M68K SH4 ALPHA LOONGARCH32 LOONGARCH64)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/arch-loongarch.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/cmdline.cc
|
||||
elf/dwarf.cc
|
||||
elf/gc-sections.cc
|
||||
elf/gdb-index.cc
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
@@ -327,10 +336,12 @@ list(APPEND MOLD_MACHO_TEMPLATE_FILES
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
if(NOT EXISTS ${PATH})
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
#define MOLD_TARGET ${TARGET}
|
||||
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
|
||||
")
|
||||
endif()
|
||||
target_sources(mold PRIVATE ${PATH})
|
||||
endfunction()
|
||||
|
||||
@@ -365,11 +376,9 @@ target_sources(mold PRIVATE
|
||||
elf/arch-arm64.cc
|
||||
elf/arch-i386.cc
|
||||
elf/arch-m68k.cc
|
||||
elf/arch-mips64.cc
|
||||
elf/arch-ppc32.cc
|
||||
elf/arch-ppc64v1.cc
|
||||
elf/arch-ppc64v2.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/arch-s390x.cc
|
||||
elf/arch-sh4.cc
|
||||
elf/arch-sparc64.cc
|
||||
@@ -442,6 +451,9 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE")
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.third-party")
|
||||
install(FILES "LICENSE.third-party" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
|
||||
endif()
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
[](https://github.com/rui314/mold/actions/workflows/ci.yml)
|
||||
[](https://build.opensuse.org/package/show/home:marxin:mold/mold)
|
||||
|
||||
<i>This repository contains a free version of the mold linker.
|
||||
If you are looking for a commercial version that supports macOS
|
||||
please visit the
|
||||
@@ -32,7 +29,8 @@ free to [file a bug report](https://github.com/rui314/mold/issues).
|
||||
|
||||
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
|
||||
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
|
||||
PowerPC ELFv2, s390x, SPARC64, m68k, SH-4, and DEC Alpha.
|
||||
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, SH-4, and DEC
|
||||
Alpha.
|
||||
|
||||
mold/macOS is commercial software. For mold/macOS, please visit
|
||||
https://github.com/bluewhalesystems/sold.
|
||||
@@ -73,11 +71,11 @@ necessary packages. You may need to run it as root.
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v2.0.0
|
||||
git checkout v2.3.2
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
sudo cmake --install .
|
||||
sudo cmake --build . --target install
|
||||
```
|
||||
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
@@ -251,8 +249,11 @@ following people and organizations who have sponsored $128/month or more:
|
||||
|
||||
### Corporate sponsors
|
||||
|
||||
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
<a href="https://cybozu-global.com/"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
|
||||
<a href="https://mercury.com"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
|
||||
<a href="https://cybozu-global.com"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
|
||||
|
||||
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
|
||||
|
||||
- [Uber](https://uber.com)
|
||||
- [G-Research](https://www.gresearch.co.uk)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ docker buildx create --use
|
||||
# $ docker buildx build --platform x86_64,aarch64,arm,ppc64le,s390x -t rui314/mold-builder:latest --push .
|
||||
|
||||
FROM ubuntu:18.04
|
||||
ENV TZ=Europe/London
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
\
|
||||
mkdir /openssl && cd /openssl && \
|
||||
wget -O- -q https://www.openssl.org/source/openssl-3.0.7.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./Configure --prefix=/usr/local --libdir=lib && \
|
||||
make -j$(nproc) && \
|
||||
make -j$(nproc) install && \
|
||||
ldconfig && \
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
|
||||
rm -rf /var/lib/apt/lists/* /cmake /openssl
|
||||
+57
-46
@@ -6,58 +6,69 @@ namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(Context &ctx, std::string_view path) {
|
||||
read_response_file(Context &ctx, std::string_view path, i64 depth) {
|
||||
if (depth > 10)
|
||||
Fatal(ctx) << path << ": response file nesting too deep";
|
||||
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mf->size && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mf->size)
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
|
||||
while (i < mf->size) {
|
||||
if (data[i] == '\\' && i + 1 < mf->size) {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[i])) {
|
||||
buf.append(1, data[i++]);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
while (!data.empty()) {
|
||||
if (isspace(data[0])) {
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
auto read_quoted = [&]() {
|
||||
char quote = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
for (i64 i = 0; i < mf->size;) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
std::string buf;
|
||||
while (!data.empty() && data[0] != quote) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
} else {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
}
|
||||
}
|
||||
if (data.empty())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
data = data.substr(1);
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
auto read_unquoted = [&] {
|
||||
std::string buf;
|
||||
while (!data.empty()) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[0])) {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
std::string_view tok;
|
||||
if (data[0] == '\'' || data[0] == '\"')
|
||||
tok = read_quoted();
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
tok = read_unquoted();
|
||||
|
||||
if (tok.starts_with('@'))
|
||||
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
|
||||
else
|
||||
vec.push_back(tok);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -68,7 +79,7 @@ std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
append(vec, read_response_file(ctx, argv[i] + 1, 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
|
||||
+259
-133
@@ -8,6 +8,7 @@
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -224,8 +226,14 @@ struct Atomic : std::atomic<T> {
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val) { std::atomic<T>::store(val, relaxed); }
|
||||
T load() const { return std::atomic<T>::load(relaxed); }
|
||||
void store(T val, std::memory_order order = relaxed) {
|
||||
std::atomic<T>::store(val, order);
|
||||
}
|
||||
|
||||
T load(std::memory_order order = relaxed) const {
|
||||
return std::atomic<T>::load(order);
|
||||
}
|
||||
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
@@ -242,6 +250,85 @@ struct Atomic : std::atomic<T> {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// Bit vector
|
||||
//
|
||||
@@ -298,7 +385,7 @@ inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
|
||||
inline i64 sign_extend(u64 val, i64 size) {
|
||||
return (i64)(val << (63 - size)) >> (63 - size);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
@@ -394,30 +481,36 @@ inline i64 write_uleb(u8 *buf, u64 val) {
|
||||
return i;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 *&buf) {
|
||||
inline u64 read_uleb(u8 **buf) {
|
||||
u64 val = 0;
|
||||
u8 shift = 0;
|
||||
u8 byte;
|
||||
do {
|
||||
byte = *buf++;
|
||||
byte = *(*buf)++;
|
||||
val |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 const*&buf) {
|
||||
return read_uleb(const_cast<u8 *&>(buf));
|
||||
inline u64 read_uleb(u8 *buf) {
|
||||
u8 *tmp = buf;
|
||||
return read_uleb(&tmp);
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view &str) {
|
||||
u8 *start = (u8 *)&str[0];
|
||||
inline u64 read_uleb(std::string_view *str) {
|
||||
u8 *start = (u8 *)str->data();
|
||||
u8 *ptr = start;
|
||||
u64 val = read_uleb(ptr);
|
||||
str = str.substr(ptr - start);
|
||||
u64 val = read_uleb(&ptr);
|
||||
*str = str->substr(ptr - start);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view str) {
|
||||
std::string_view tmp = str;
|
||||
return read_uleb(&tmp);
|
||||
}
|
||||
|
||||
inline i64 uleb_size(u64 val) {
|
||||
#if __GNUC__
|
||||
#pragma GCC unroll 8
|
||||
@@ -428,6 +521,14 @@ inline i64 uleb_size(u64 val) {
|
||||
return 9;
|
||||
}
|
||||
|
||||
inline void overwrite_uleb(u8 *loc, u64 val) {
|
||||
while (*loc & 0b1000'0000) {
|
||||
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
|
||||
val >>= 7;
|
||||
}
|
||||
*loc = val & 0b0111'1111;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
std::string_view save_string(Context &ctx, const std::string &str) {
|
||||
u8 *buf = new u8[str.size() + 1];
|
||||
@@ -464,53 +565,72 @@ public:
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
if (keys) {
|
||||
free((void *)keys);
|
||||
free((void *)key_sizes);
|
||||
free((void *)values);
|
||||
}
|
||||
free(entries);
|
||||
}
|
||||
|
||||
// In order to avoid unnecessary cache-line false sharing, we want
|
||||
// to make this object to be aligned to a reasonably large
|
||||
// power-of-two address.
|
||||
struct alignas(32) Entry {
|
||||
Atomic<const char *> key;
|
||||
T value;
|
||||
u32 keylen;
|
||||
};
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
this->~ConcurrentMap();
|
||||
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
i64 sz = sizeof(Entry) * this->nbuckets;
|
||||
free(entries);
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(char *));
|
||||
key_sizes = (u32 *)malloc(nbuckets * sizeof(u32));
|
||||
values = (T *)malloc(nbuckets * sizeof(T));
|
||||
#if _WIN32
|
||||
// Even though std::aligned_alloc is defined in C++17, MSVC doesn't
|
||||
// seem to provide that function.
|
||||
entries = (Entry *)_aligned_malloc(sz, alignof(Entry));
|
||||
#else
|
||||
entries = (Entry *)std::aligned_alloc(alignof(Entry), sz);
|
||||
#endif
|
||||
|
||||
memset(entries, 0, sz);
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
if (!keys)
|
||||
return {nullptr, false};
|
||||
|
||||
assert(has_single_bit(nbuckets));
|
||||
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
|
||||
while (retry < MAX_RETRY) {
|
||||
const char *ptr = keys[idx].load(std::memory_order_acquire);
|
||||
if (ptr == marker) {
|
||||
pause();
|
||||
Entry &ent = entries[idx];
|
||||
const char *ptr = nullptr;
|
||||
bool claimed = ent.key.compare_exchange_weak(ptr, (char *)-1,
|
||||
std::memory_order_acquire);
|
||||
|
||||
// If we successfully claimed the ownership of an unused slot,
|
||||
// copy values to it.
|
||||
if (claimed) {
|
||||
new (&ent.value) T(val);
|
||||
ent.keylen = key.size();
|
||||
ent.key.store(key.data(), std::memory_order_release);
|
||||
return {&ent.value, true};
|
||||
}
|
||||
|
||||
// Loop on a spurious failure.
|
||||
if (ptr == nullptr)
|
||||
continue;
|
||||
|
||||
// If someone is copying values to the slot, do busy wait.
|
||||
while (ptr == (char *)-1) {
|
||||
pause();
|
||||
ptr = ent.key.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
if (ptr == nullptr) {
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker,
|
||||
std::memory_order_acquire))
|
||||
continue;
|
||||
new (values + idx) T(val);
|
||||
key_sizes[idx] = key.size();
|
||||
keys[idx].store(key.data(), std::memory_order_release);
|
||||
return {values + idx, true};
|
||||
}
|
||||
|
||||
if (key.size() == key_sizes[idx] &&
|
||||
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
|
||||
return {values + idx, false};
|
||||
// If the same key is already present, this is the slot we are
|
||||
// looking for.
|
||||
if (key == std::string_view(ptr, ent.keylen))
|
||||
return {&ent.value, false};
|
||||
|
||||
// Otherwise, move on to the next slot.
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
idx = (idx & ~mask) | ((idx + 1) & mask);
|
||||
retry++;
|
||||
@@ -520,17 +640,67 @@ public:
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
const char *get_key(i64 idx) {
|
||||
return keys[idx].load(std::memory_order_relaxed);
|
||||
i64 get_idx(T *value) const {
|
||||
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
|
||||
return (Entry *)addr - entries;
|
||||
}
|
||||
|
||||
// Return a list of map entries sorted in a deterministic order.
|
||||
std::vector<Entry *> get_sorted_entries(i64 shard_idx) {
|
||||
if (nbuckets == 0)
|
||||
return {};
|
||||
|
||||
i64 shard_size = nbuckets / NUM_SHARDS;
|
||||
i64 begin = shard_idx * shard_size;
|
||||
i64 end = begin + shard_size;
|
||||
|
||||
i64 sz = 0;
|
||||
for (i64 i = begin; i < end; i++)
|
||||
if (entries[i].key)
|
||||
sz++;
|
||||
|
||||
std::vector<Entry *> vec;
|
||||
vec.reserve(sz);
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while (entries[end - 1].key)
|
||||
vec.push_back(entries + --end);
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
i64 last = 0;
|
||||
for (i64 i = begin; i < end;) {
|
||||
while (i < end && entries[i].key)
|
||||
vec.push_back(entries + i++);
|
||||
|
||||
std::sort(vec.begin() + last, vec.end(), [](Entry *a, Entry *b) {
|
||||
if (a->keylen != b->keylen)
|
||||
return a->keylen < b->keylen;
|
||||
return memcmp(a->key, b->key, a->keylen) < 0;
|
||||
});
|
||||
|
||||
last = vec.size();
|
||||
|
||||
while (i < end && !entries[i].key)
|
||||
i++;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<Entry *> get_sorted_entries_all() {
|
||||
std::vector<std::vector<Entry *>> vec(NUM_SHARDS);
|
||||
tbb::parallel_for((i64)0, NUM_SHARDS, [&](i64 i) {
|
||||
vec[i] = get_sorted_entries(i);
|
||||
});
|
||||
return flatten(vec);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 2048;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 128;
|
||||
|
||||
Entry *entries = nullptr;
|
||||
i64 nbuckets = 0;
|
||||
u32 *key_sizes = nullptr;
|
||||
T *values = nullptr;
|
||||
|
||||
private:
|
||||
static void pause() {
|
||||
@@ -540,10 +710,6 @@ private:
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<const char *> *keys = nullptr;
|
||||
static constexpr const char *marker = "marker";
|
||||
};
|
||||
|
||||
//
|
||||
@@ -560,9 +726,11 @@ public:
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::vector<u8> buf2;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
i64 fd = -1;
|
||||
i64 filesize = 0;
|
||||
bool is_mmapped = false;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
@@ -570,6 +738,43 @@ protected:
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
~MallocOutputFile() {
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
FILE *fp;
|
||||
|
||||
if (this->path == "-") {
|
||||
fp = stdout;
|
||||
} else {
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
fp = fdopen(fd, "w");
|
||||
}
|
||||
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
if (!this->buf2.empty())
|
||||
fwrite(this->buf2.data(), this->buf2.size(), 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
@@ -705,85 +910,6 @@ private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
@@ -871,9 +997,9 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
#else
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
|
||||
if (fd == -1) {
|
||||
@@ -909,7 +1035,7 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
|
||||
+12
-37
@@ -52,14 +52,13 @@ class MemoryMappedOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, true) {
|
||||
i64 fd;
|
||||
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
|
||||
std::tie(this->fd, output_tmpfile) =
|
||||
open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
MAP_SHARED, this->fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
@@ -76,6 +75,15 @@ public:
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (this->buf2.empty()) {
|
||||
::close(this->fd);
|
||||
} else {
|
||||
FILE *out = fdopen(this->fd, "w");
|
||||
fseek(out, 0, SEEK_END);
|
||||
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// If an output file already exists, open a file and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system to immediately release disk blocks occupied by the file.
|
||||
@@ -92,39 +100,6 @@ private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
@@ -6,39 +6,6 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
typedef uint8_t u8;
|
||||
static constexpr int64_t SHA256_SIZE = 32;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, we use Microsoft CNG.
|
||||
|
||||
#include <Windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <ntstatus.h>
|
||||
|
||||
inline static BCRYPT_ALG_HANDLE get_sha256_handle() {
|
||||
static std::once_flag once;
|
||||
static BCRYPT_ALG_HANDLE alg;
|
||||
|
||||
std::call_once(once, [&] {
|
||||
BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
|
||||
});
|
||||
return alg;
|
||||
}
|
||||
|
||||
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
|
||||
BCryptHash(get_sha256_handle(), nullptr, 0, in, len, out, SHA256_SIZE);
|
||||
}
|
||||
|
||||
class SHA256Hash {
|
||||
public:
|
||||
SHA256Hash() {
|
||||
BCryptCreateHash(get_sha256_handle(), &handle, nullptr, 0, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
void update(u8 *data, size_t len) {
|
||||
BCryptHashData(handle, data, len, 0);
|
||||
}
|
||||
|
||||
void finish(u8 *out) {
|
||||
BCryptFinishHash(handle, out, SHA256_SIZE, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
BCRYPT_HASH_HANDLE handle;
|
||||
};
|
||||
|
||||
#else
|
||||
// On Unix, we use OpenSSL or the Apple's OpenSSL-compatible API.
|
||||
|
||||
#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
|
||||
|
||||
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
|
||||
SHA256(in, len, out);
|
||||
}
|
||||
|
||||
class SHA256Hash {
|
||||
public:
|
||||
SHA256Hash() {
|
||||
SHA256_Init(&ctx);
|
||||
}
|
||||
|
||||
void update(u8 *data, size_t len) {
|
||||
SHA256_Update(&ctx, data, len);
|
||||
}
|
||||
|
||||
void finish(u8 *out) {
|
||||
SHA256_Final(out, &ctx);
|
||||
}
|
||||
|
||||
private:
|
||||
SHA256_CTX ctx;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -50,6 +50,8 @@ struct UstarHeader {
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UstarHeader) == 512);
|
||||
|
||||
static std::string encode_path(std::string basedir, std::string path) {
|
||||
path = path_clean(basedir + "/" + path);
|
||||
|
||||
@@ -81,6 +83,7 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
|
||||
std::string attr = encode_path(basedir, path);
|
||||
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
|
||||
pax.name[0] = '/';
|
||||
pax.typeflag[0] = 'x';
|
||||
pax.finalize();
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
@@ -7,6 +8,8 @@
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
|
||||
set -e -x
|
||||
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
@@ -16,25 +19,60 @@ case $# in
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
|
||||
image=mold-builder-$arch:1
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
set -e -x
|
||||
|
||||
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
|
||||
bash -c "mkdir /tmp/build &&
|
||||
cd /tmp/build &&
|
||||
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
|
||||
# Create a Docker image if not exists.
|
||||
#
|
||||
# We want to use a reasonably old Linux distro because we'll dynamically
|
||||
# link glibc to mold, and a binary linked against a newer version of
|
||||
# glibc won't work on a system with a older version of glibc.
|
||||
#
|
||||
# We might want to instead statically-link everything, but that'll
|
||||
# disable dlopen(), which means we can't use the linker plugin for LTO.
|
||||
# So we don't want to do that.
|
||||
if ! docker image ls $image | grep -q $image; then
|
||||
if [ $arch = riscv64 ]; then
|
||||
# Ubuntu 18 didn't support RISC-V, so use Ubuntu 20 instead
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM riscv64/ubuntu:20.04
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
else
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM ubuntu:18.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev gcc-10 g++-10 python3 libssl-dev && \
|
||||
\
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
|
||||
rm -rf /var/lib/apt/lists /cmake
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build mold in a container.
|
||||
docker run --platform linux/$arch -i --rm -v "$(realpath $(dirname $0)):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" $image \
|
||||
bash -c "mkdir /build &&
|
||||
cd /build &&
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On /mold &&
|
||||
cmake --build . -j\$(nproc) &&
|
||||
[ $arch = arm ] || ctest -j\$(nproc) &&
|
||||
cmake --install . --prefix $dest --strip &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold mold-wrapper.so /mold &&
|
||||
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
|
||||
chown \$OWNER /mold/$dest.tar.gz"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
+4
-1
@@ -1,6 +1,6 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "MOLD" "1" "March 2023" ""
|
||||
.TH "MOLD" "1" "July 2023" ""
|
||||
.SH "NAME"
|
||||
\fBmold\fR \- a modern linker
|
||||
.SH "SYNOPSIS"
|
||||
@@ -425,6 +425,9 @@ Some sections such as \fB\.dynamic\fR have to be writable only during an executa
|
||||
.IP
|
||||
By default, \fBmold\fR generates a relro segment\. \fB\-z norelro\fR disables the feature\.
|
||||
.TP
|
||||
\fB\-z sectionheader\fR, \fB\-z nosectionheader\fR
|
||||
\fB\-z nosectionheader\fR tell the linker to omit the section header\. By default, the linker does not omit the section header\.
|
||||
.TP
|
||||
\fB\-z separate\-loadable\-segments\fR, \fB\-z separate\-code\fR, \fB\-z noseparate\-code\fR
|
||||
If one memory page contains multiple segments, the page protection bits are set in such a way that the needed attributes (writable or executable) are satisfied for all segments\. This usually happens at a boundary of two segments with two different attributes\.
|
||||
.IP
|
||||
|
||||
+19
-13
@@ -697,6 +697,10 @@ arguments.
|
||||
By default, `mold` generates a relro segment. `-z norelro` disables the
|
||||
feature.
|
||||
|
||||
* `-z sectionheader`, `-z nosectionheader`:
|
||||
`-z nosectionheader` tell the linker to omit the section header.
|
||||
By default, the linker does not omit the section header.
|
||||
|
||||
* `-z separate-loadable-segments`, `-z separate-code`, `-z noseparate-code`:
|
||||
If one memory page contains multiple segments, the page protection bits are
|
||||
set in such a way that the needed attributes (writable or executable) are
|
||||
@@ -760,22 +764,24 @@ arguments.
|
||||
## ENVIRONMENT VARIABLES
|
||||
|
||||
* `MOLD_JOBS`:
|
||||
If this variable is set to `1`, only one process of `mold` runs actively. A
|
||||
mold process invoked while another active mold process is running will wait
|
||||
before doing anything until the active process exits.
|
||||
If this variable is set to `1`, only one `mold` process will run at a time.
|
||||
If a new mold process is initiated while another is already active, the new
|
||||
process will wait until the active one completes before starting.
|
||||
|
||||
The purpose of this environment variable is to reduce peak memory usage.
|
||||
Since mold is highly parallelized, there's no point in running it
|
||||
simultaneously. If you run N instances of mold in parallel, it would take N
|
||||
times more time and N times more memory. If you run them serially, it would
|
||||
still take N times more to finish, but their peak memory usage is reduced to
|
||||
normal.
|
||||
The primary reason for this environment variable is to minimize peak memory
|
||||
usage. Since mold is designed to operate with high parallelism, running
|
||||
multiple mold instances simultaneously may not be beneficial. If you execute
|
||||
N instances of mold concurrently, it could require N times the time and N
|
||||
times the memory. On the other hand, running them one after the other might
|
||||
still take N times longer, but the peak memory usage would be the same as
|
||||
running just a single instance.
|
||||
|
||||
If your build system tends to invoke multiple linker processes
|
||||
simultaneously, you may want to try to set this environment variable to
|
||||
`1` to see if it could improve overall performance.
|
||||
If your build system invokes multiple linker processes simultaneously and
|
||||
some of them often get killed due to out-of-memory errors, you might
|
||||
consider setting this environment variable to `1` to see if it addresses the
|
||||
OOM issue.
|
||||
|
||||
Currently, any value other than 1 is silently ignored.
|
||||
Currently, any value other than `1` is silently ignored.
|
||||
|
||||
* `MOLD_DEBUG`:
|
||||
If this variable is set to a non-empty string, `mold` embeds its
|
||||
|
||||
+3
-3
@@ -62,8 +62,8 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -103,7 +103,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFQUAD:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
*(ul32 *)loc = S + A - GP;
|
||||
|
||||
+73
-42
@@ -195,14 +195,14 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
@@ -211,8 +211,8 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
static const ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0x0000'0000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
@@ -225,14 +225,14 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
// ARM and Thumb branch instructions can jump within ±16 MiB.
|
||||
static bool is_jump_reachable(i64 val) {
|
||||
@@ -286,7 +286,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
@@ -336,12 +336,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like THM_CALL, ARM_CALL relocation refers either BL or
|
||||
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
|
||||
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
@@ -364,7 +364,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
|
||||
// These relocs refers a B (unconditional branch) instruction.
|
||||
// These relocs refers to a B (unconditional branch) instruction.
|
||||
// Unlike BL or BLX, we can't rewrite B to BX in place when the
|
||||
// processor mode switch is required because BX doesn't takes an
|
||||
// immediate; it takes only a register. So if mode switch is
|
||||
@@ -467,12 +467,33 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in r0.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: bl foo
|
||||
// R_ARM_TLS_CALL
|
||||
// .L2: .word foo + . - .L1
|
||||
// R_ARM_TLS_GOTDESC
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: ldr r0, [pc, r0]
|
||||
// ...
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: nop
|
||||
// ...
|
||||
// .L2: .word foo(tpoff)
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
if (A & 1)
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
|
||||
else
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(ul32 *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
@@ -481,9 +502,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_tls_trampoline_addr(P + 8), 25, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0xe320'f000;
|
||||
*(ul32 *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
@@ -491,9 +513,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 val = align_to(get_tls_trampoline_addr(P + 4), 4);
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
|
||||
// we use two instructions instead.
|
||||
*(ul16 *)loc = 0x4478; // add r0, pc
|
||||
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
// BL -> NOP.W
|
||||
*(ul32 *)loc = 0x8000'f3af;
|
||||
*(ul32 *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -594,9 +620,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
@@ -611,9 +637,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
case R_ARM_V4BX:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -623,8 +648,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
@@ -632,36 +655,44 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
const u8 entry[] = {
|
||||
// .thumb
|
||||
0xfc, 0x46, // mov ip, pc
|
||||
0x60, 0x47, // bx ip # jumps to the following `ldr` insn
|
||||
0x78, 0x47, // bx pc # jumps to 1f
|
||||
0xc0, 0x46, // nop
|
||||
// .arm
|
||||
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, 2f
|
||||
0x0f, 0xc0, 0x8c, 0xe0, // 1: add ip, ip, pc
|
||||
0x1c, 0xff, 0x2f, 0xe1, // bx ip
|
||||
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
|
||||
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
|
||||
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
|
||||
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
memcpy(buf, hdr, sizeof(hdr));
|
||||
buf += sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
|
||||
memcpy(loc, entry, sizeof(entry));
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
|
||||
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr) + i * sizeof(entry);
|
||||
*(ul32 *)(loc + 16) = S - P - 16;
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
|
||||
buf += sizeof(entry);
|
||||
P += sizeof(entry);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return EF_ARM_EABI_VER5;
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
+85
-50
@@ -93,7 +93,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
@@ -102,8 +102,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -173,7 +173,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
@@ -232,15 +232,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
|
||||
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
|
||||
// :lo12: foo` instruction pair to materialize a PC-relative address
|
||||
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
|
||||
// if foo is in PC ± 1 MiB.
|
||||
if (ctx.arg.relax && i + 1 < rels.size() &&
|
||||
sign_extend(S + A - P - 4, 20) == S + A - P - 4) {
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
i64 val = S + A - P - 4;
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
|
||||
if (sign_extend(val, 20) == val &&
|
||||
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_addend == rel.r_addend &&
|
||||
@@ -251,7 +254,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (reg1 == reg2) {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
|
||||
write_adr(loc + 4, S + A - P - 4);
|
||||
write_adr(loc + 4, val);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
@@ -259,7 +262,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
@@ -367,38 +371,65 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
// ARM64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
//
|
||||
// adrp x0, 0
|
||||
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
|
||||
// ldr x1, [x0]
|
||||
// R_AARCH64_TLSDESC_LD64_LO12 foo
|
||||
// add x0, x0, #0
|
||||
// R_AARCH64_TLSDESC_ADD_LO12 foo
|
||||
// blr x1
|
||||
// R_AARCH64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// adrp x0, :gottprel:foo
|
||||
// ldr x0, [x0, :gottprel_lo12:foo]
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// movz x0, :tls_offset_hi:foo, lsl #16
|
||||
// movk x0, :tls_offset_lo:foo
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tp_addr);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
if (sym.has_tlsdesc(ctx))
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
|
||||
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
|
||||
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
|
||||
}
|
||||
else
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
|
||||
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
|
||||
} else {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// blr x2 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -477,13 +508,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_ABS64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
|
||||
// symbol's address from GOT. If the GOT value is a link-time
|
||||
// constant, we may be able to rewrite the ADRP+LDR instruction pair
|
||||
// with an ADRP+ADD, eliminating a GOT memory load.
|
||||
if (ctx.arg.relax && sym.is_relative() && !sym.is_imported &&
|
||||
!sym.is_ifunc() && i + 1 < rels.size()) {
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
// ADRP+LDR must be consecutive and use the same register to relax.
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
|
||||
@@ -519,23 +553,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
@@ -555,12 +582,18 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
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_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -570,24 +603,26 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const ul32 data[] = {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f'0200, // br x16
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(data));
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
u8 *loc = buf + i * E::thunk_size;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adrp(loc, page(S) - page(P));
|
||||
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
memcpy(buf, insn, E::thunk_size);
|
||||
write_adrp(buf, page(S) - page(P));
|
||||
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-46
@@ -164,7 +164,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
@@ -172,13 +172,13 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -294,7 +294,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
@@ -338,20 +338,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
@@ -360,13 +356,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
// i386 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %eax.
|
||||
//
|
||||
// lea 0(%ebx), %eax
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// mov foo@GOTTPOFF(%ebx), %eax
|
||||
// nop
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x83;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
loc[-2] = 0x90;
|
||||
loc[-1] = 0xb8;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
@@ -476,6 +491,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
@@ -493,15 +518,15 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_GOT32X: {
|
||||
// We always want to relax GOT32X because static PIE doesn't
|
||||
// work without it.
|
||||
bool do_relax = !sym.is_imported && sym.is_relative() &&
|
||||
relax_got32x(loc - 2);
|
||||
if (!do_relax)
|
||||
case R_386_GOT32X:
|
||||
// We always want to relax GOT32X even if --no-relax is given
|
||||
// because static PIE doesn't work without it.
|
||||
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
@@ -511,31 +536,15 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && !ctx.arg.shared && !sym.is_imported))
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
@@ -544,8 +553,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
|
||||
@@ -0,0 +1,686 @@
|
||||
// LoongArch is a new RISC ISA announced in 2021 by Loongson. The ISA
|
||||
// feels like a modernized MIPS with a hint of RISC-V flavor, although
|
||||
// it's not compatible with either one.
|
||||
//
|
||||
// While LoongArch is a fresh and clean ISA, its technological advantage
|
||||
// over other modern RISC ISAs such as RISC-V doesn't seem to be very
|
||||
// significant. It appears that the real selling point of LoongArch is
|
||||
// that the ISA is developed and controlled by a Chinese company,
|
||||
// reflecting a desire for domestic CPUs. Loongson is actively working on
|
||||
// bootstrapping the entire ecosystem for LoongArch, sending patches to
|
||||
// Linux, GCC, LLVM, etc.
|
||||
//
|
||||
// All instructions are 4 bytes long in LoongArch and aligned to 4-byte
|
||||
// boundaries. It has 32 general-purpose registers. Among these, $t0 - $t8
|
||||
// (aliases for $r12 - $r20) are temporary registers that we can use in
|
||||
// our PLT and range extension thunks.
|
||||
//
|
||||
// The psABI defines a few linker relaxations. We haven't supported them
|
||||
// yet.
|
||||
//
|
||||
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
|
||||
|
||||
#if MOLD_LOONGARCH64 || MOLD_LOONGARCH32
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
static u64 hi20(u64 val, u64 pc) {
|
||||
// A PC-relative address with a 32 bit offset is materialized in a
|
||||
// register with the following instructions:
|
||||
//
|
||||
// pcalau12i $rN, %pc_hi20(sym)
|
||||
// addi.d $rN, $rN, %lo12(sym)
|
||||
//
|
||||
// PCALAU12I materializes bits [63:12] by computing (pc + imm << 12)
|
||||
// and zero-clear [11:0]. ADDI.D sign-extends its 12 bit immediate and
|
||||
// add it to the register. To compensate the sign-extension, PCALAU12I
|
||||
// needs to materialize a 0x1000 larger value than the desired [63:12]
|
||||
// if [11:0] is sign-extended.
|
||||
//
|
||||
// This is similar but different from RISC-V because RISC-V's AUIPC
|
||||
// doesn't zero-clear [11:0].
|
||||
return bits(page(val + 0x800) - page(pc), 31, 12);
|
||||
}
|
||||
|
||||
static u64 hi64(u64 val, u64 pc) {
|
||||
// A PC-relative 64-bit address is materialized with the following
|
||||
// instructions for the large code model:
|
||||
//
|
||||
// pcalau12i $rN, %pc_hi20(sym)
|
||||
// addi.d $rM, $zero, %lo12(sym)
|
||||
// lu32i.d $rM, %pc64_lo20(sym)
|
||||
// lu52i.d $rM, $r12, %pc64_hi12(sym)
|
||||
// add.d $rN, $rN, $rM
|
||||
//
|
||||
// PCALAU12I computes (pc + imm << 12) to materialize a 64-bit value.
|
||||
// ADDI.D adds a sign-extended 12 bit value to a register. LU32I.D and
|
||||
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
|
||||
//
|
||||
// Compensating all the sign-extensions is a bit complicated.
|
||||
u64 x = page(val) - page(pc);
|
||||
if (val & 0x800)
|
||||
x += 0x1000 - 0x1'0000'0000;
|
||||
if (x & 0x8000'0000)
|
||||
x += 0x1'0000'0000;
|
||||
return x;
|
||||
}
|
||||
|
||||
static u64 higher20(u64 val, u64 pc) {
|
||||
return bits(hi64(val, pc), 51, 32);
|
||||
}
|
||||
|
||||
static u64 highest12(u64 val, u64 pc) {
|
||||
return bits(hi64(val, pc), 63, 52);
|
||||
}
|
||||
|
||||
static void write_k12(u8 *loc, u32 val) {
|
||||
// opcode, [11:0], rj, rd
|
||||
*(ul32 *)loc &= 0b1111111111'000000000000'11111'11111;
|
||||
*(ul32 *)loc |= bits(val, 11, 0) << 10;
|
||||
}
|
||||
|
||||
static void write_k16(u8 *loc, u32 val) {
|
||||
// opcode, [15:0], rj, rd
|
||||
*(ul32 *)loc &= 0b111111'0000000000000000'11111'11111;
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 10;
|
||||
}
|
||||
|
||||
static void write_j20(u8 *loc, u32 val) {
|
||||
// opcode, [19:0], rd
|
||||
*(ul32 *)loc &= 0b1111111'00000000000000000000'11111;
|
||||
*(ul32 *)loc |= bits(val, 19, 0) << 5;
|
||||
}
|
||||
|
||||
static void write_d5k16(u8 *loc, u32 val) {
|
||||
// opcode, [15:0], rj, [20:16]
|
||||
*(ul32 *)loc &= 0b111111'0000000000000000'11111'00000;
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 10;
|
||||
*(ul32 *)loc |= bits(val, 20, 16);
|
||||
}
|
||||
|
||||
static void write_d10k16(u8 *loc, u32 val) {
|
||||
// opcode, [15:0], [25:16]
|
||||
*(ul32 *)loc &= 0b111111'0000000000000000'0000000000;
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 10;
|
||||
*(ul32 *)loc |= bits(val, 25, 16);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn_64[] = {
|
||||
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
|
||||
0x0011'bdad, // sub.d $t1, $t1, $t3
|
||||
0x28c0'01cf, // ld.d $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
|
||||
0x02ff'51ad, // addi.d $t1, $t1, -44 # .plt entry
|
||||
0x02c0'01cc, // addi.d $t0, $t2, %lo12(.got.plt) # &.got.plt
|
||||
0x0045'05ad, // srli.d $t1, $t1, 1 # .plt entry offset
|
||||
0x28c0'218c, // ld.d $t0, $t0, 8 # link map
|
||||
0x4c00'01e0, // jr $t3
|
||||
};
|
||||
|
||||
static const ul32 insn_32[] = {
|
||||
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
|
||||
0x0011'3dad, // sub.w $t1, $t1, $t3
|
||||
0x2880'01cf, // ld.w $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
|
||||
0x02bf'51ad, // addi.w $t1, $t1, -44 # .plt entry
|
||||
0x0280'01cc, // addi.w $t0, $t2, %lo12(.got.plt) # &.got.plt
|
||||
0x0044'89ad, // srli.w $t1, $t1, 2 # .plt entry offset
|
||||
0x2880'118c, // ld.w $t0, $t0, 4 # link map
|
||||
0x4c00'01e0, // jr $t3
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, E::is_64 ? insn_64 : insn_32, E::plt_hdr_size);
|
||||
write_j20(buf, hi20(gotplt, plt));
|
||||
write_k12(buf + 8, gotplt);
|
||||
write_k12(buf + 16, gotplt);
|
||||
}
|
||||
|
||||
static const ul32 plt_entry_64[] = {
|
||||
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
|
||||
0x28c0'01ef, // ld.d $t3, $t3, %lo12([email protected])
|
||||
0x4c00'01ed, // jirl $t1, $t3, 0
|
||||
0x0340'0000, // nop
|
||||
};
|
||||
|
||||
static const ul32 plt_entry_32[] = {
|
||||
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
|
||||
0x2880'01ef, // ld.w $t3, $t3, %lo12([email protected])
|
||||
0x4c00'01ed, // jirl $t1, $t3, 0
|
||||
0x0340'0000, // nop
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
write_j20(buf, hi20(gotplt, plt));
|
||||
write_k12(buf + 4, gotplt);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
write_j20(buf, hi20(got, plt));
|
||||
write_k12(buf + 4, got);
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_LARCH_ADD6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc + val) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_ADD8:
|
||||
*loc += val;
|
||||
break;
|
||||
case R_LARCH_ADD16:
|
||||
*(ul16 *)loc += val;
|
||||
break;
|
||||
case R_LARCH_ADD32:
|
||||
*(ul32 *)loc += val;
|
||||
break;
|
||||
case R_LARCH_ADD64:
|
||||
*(ul64 *)loc += val;
|
||||
break;
|
||||
case R_LARCH_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_SUB8:
|
||||
*loc -= val;
|
||||
break;
|
||||
case R_LARCH_SUB16:
|
||||
*(ul16 *)loc -= val;
|
||||
break;
|
||||
case R_LARCH_SUB32:
|
||||
*(ul32 *)loc -= val;
|
||||
break;
|
||||
case R_LARCH_SUB64:
|
||||
*(ul64 *)loc -= val;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_LARCH_64_PCREL:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_LARCH_RELAX ||
|
||||
rel.r_type == R_LARCH_MARK_LA || rel.r_type == R_LARCH_MARK_PCREL ||
|
||||
rel.r_type == R_LARCH_ALIGN)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto check_branch = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val & 0b11)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
check(val, lo, hi);
|
||||
};
|
||||
|
||||
// Unlike other psABIs, the LoongArch ABI uses the same relocation
|
||||
// types to refer to GOT entries for thread-local symbols and regular
|
||||
// ones. Therefore, G may refer to a TLSGD or a regular GOT slot
|
||||
// depending on the symbol type.
|
||||
//
|
||||
// Note that as of August 2023, both GCC and Clang treat TLSLD relocs
|
||||
// as if they were TLSGD relocs for LoongArch, which is a clear bug.
|
||||
// We need to handle TLSLD relocs as synonyms for TLSGD relocs for the
|
||||
// sake of bug compatibility.
|
||||
auto get_got_idx = [&] {
|
||||
if (sym.has_tlsgd(ctx))
|
||||
return sym.get_tlsgd_idx(ctx);
|
||||
return sym.get_got_idx(ctx);
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = get_got_idx() * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_32:
|
||||
if constexpr (E::is_64)
|
||||
*(ul32 *)loc = S + A;
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
assert(E::is_64);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_LARCH_B16:
|
||||
check_branch(S + A - P, -(1 << 17), 1 << 17);
|
||||
write_k16(loc, (S + A - P) >> 2);
|
||||
break;
|
||||
case R_LARCH_B21:
|
||||
check_branch(S + A - P, -(1 << 22), 1 << 22);
|
||||
write_d5k16(loc, (S + A - P) >> 2);
|
||||
break;
|
||||
case R_LARCH_B26: {
|
||||
i64 val = S + A - P;
|
||||
if (val < -(1 << 27) || (1 << 27) <= val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
write_d10k16(loc, val >> 2);
|
||||
break;
|
||||
}
|
||||
case R_LARCH_ABS_LO12:
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
case R_LARCH_ABS_HI20:
|
||||
write_j20(loc, (S + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_ABS64_LO20:
|
||||
write_j20(loc, (S + A) >> 32);
|
||||
break;
|
||||
case R_LARCH_ABS64_HI12:
|
||||
write_k12(loc, (S + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_PCALA_LO12:
|
||||
// It looks like R_LARCH_PCALA_LO12 is sometimes used for JIRL even
|
||||
// though the instruction takes a 16 bit immediate rather than 12 bits.
|
||||
// It is contrary to the psABI document, but GNU ld has special
|
||||
// code to handle it, so we accept it too.
|
||||
if ((*(ul32 *)loc & 0xfc00'0000) == 0x4c00'0000)
|
||||
write_k16(loc, sign_extend(S + A, 11) >> 2);
|
||||
else
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
case R_LARCH_PCALA_HI20:
|
||||
write_j20(loc, hi20(S + A, P));
|
||||
break;
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
write_j20(loc, higher20(S + A, P));
|
||||
break;
|
||||
case R_LARCH_PCALA64_HI12:
|
||||
write_k12(loc, highest12(S + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT_PC_LO12:
|
||||
write_k12(loc, GOT + G + A);
|
||||
break;
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
write_j20(loc, hi20(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT64_PC_LO20:
|
||||
write_j20(loc, higher20(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT64_PC_HI12:
|
||||
write_k12(loc, highest12(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT_LO12:
|
||||
write_k12(loc, GOT + G + A);
|
||||
break;
|
||||
case R_LARCH_GOT_HI20:
|
||||
write_j20(loc, (GOT + G + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_GOT64_LO20:
|
||||
write_j20(loc, (GOT + G + A) >> 32);
|
||||
break;
|
||||
case R_LARCH_GOT64_HI12:
|
||||
write_k12(loc, (GOT + G + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_TLS_LE_LO12:
|
||||
write_k12(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_LARCH_TLS_LE_HI20:
|
||||
write_j20(loc, (S + A - ctx.tp_addr) >> 12);
|
||||
break;
|
||||
case R_LARCH_TLS_LE64_LO20:
|
||||
write_j20(loc, (S + A - ctx.tp_addr) >> 32);
|
||||
break;
|
||||
case R_LARCH_TLS_LE64_HI12:
|
||||
write_k12(loc, (S + A - ctx.tp_addr) >> 52);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_PC_LO12:
|
||||
write_k12(loc, sym.get_gottp_addr(ctx) + A);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_PC_HI20:
|
||||
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_PC_LO20:
|
||||
write_j20(loc, higher20(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_PC_HI12:
|
||||
write_k12(loc, highest12(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE_LO12:
|
||||
write_k12(loc, sym.get_gottp_addr(ctx) + A);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_HI20:
|
||||
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_LO20:
|
||||
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 32);
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_HI12:
|
||||
write_k12(loc, (sym.get_gottp_addr(ctx) + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
case R_LARCH_TLS_GD_PC_HI20:
|
||||
check(sym.get_tlsgd_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(sym.get_tlsgd_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
case R_LARCH_TLS_GD_HI20:
|
||||
write_j20(loc, (sym.get_tlsgd_addr(ctx) + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_ADD6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc + S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD16:
|
||||
*(ul16 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD32:
|
||||
*(ul32 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD64:
|
||||
*(ul64 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB16:
|
||||
*(ul16 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB32:
|
||||
*(ul32 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB64:
|
||||
*(ul64 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_LARCH_64_PCREL:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) + S + A);
|
||||
break;
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) - S - A);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_LARCH_ADD6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc + S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD16:
|
||||
*(ul16 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD32:
|
||||
*(ul32 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD64:
|
||||
*(ul64 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB16:
|
||||
*(ul16 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB32:
|
||||
*(ul32 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB64:
|
||||
*(ul64 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_TLS_DTPREL32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_LARCH_TLS_DTPREL64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) + S + A);
|
||||
break;
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) - S - A);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_LARCH_RELAX ||
|
||||
rel.r_type == R_LARCH_MARK_LA || rel.r_type == R_LARCH_MARK_PCREL ||
|
||||
rel.r_type == R_LARCH_ALIGN)
|
||||
continue;
|
||||
|
||||
if (record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_32:
|
||||
if constexpr (E::is_64)
|
||||
scan_absrel(ctx, sym, rel);
|
||||
else
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
assert(E::is_64);
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_B26:
|
||||
case R_LARCH_PCALA_HI20:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_LARCH_GOT_HI20:
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_LARCH_TLS_IE_HI20:
|
||||
case R_LARCH_TLS_IE_PC_HI20:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
case R_LARCH_TLS_GD_PC_HI20:
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
case R_LARCH_TLS_GD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
case R_LARCH_64_PCREL:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_TLS_LE_HI20:
|
||||
case R_LARCH_TLS_LE_LO12:
|
||||
case R_LARCH_TLS_LE64_LO20:
|
||||
case R_LARCH_TLS_LE64_HI12:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_B16:
|
||||
case R_LARCH_B21:
|
||||
case R_LARCH_ABS_HI20:
|
||||
case R_LARCH_ABS_LO12:
|
||||
case R_LARCH_ABS64_LO20:
|
||||
case R_LARCH_ABS64_HI12:
|
||||
case R_LARCH_PCALA_LO12:
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
case R_LARCH_PCALA64_HI12:
|
||||
case R_LARCH_GOT_PC_LO12:
|
||||
case R_LARCH_GOT64_PC_LO20:
|
||||
case R_LARCH_GOT64_PC_HI12:
|
||||
case R_LARCH_GOT_LO12:
|
||||
case R_LARCH_GOT64_LO20:
|
||||
case R_LARCH_GOT64_HI12:
|
||||
case R_LARCH_TLS_IE_PC_LO12:
|
||||
case R_LARCH_TLS_IE64_PC_LO20:
|
||||
case R_LARCH_TLS_IE64_PC_HI12:
|
||||
case R_LARCH_TLS_IE_LO12:
|
||||
case R_LARCH_TLS_IE64_LO20:
|
||||
case R_LARCH_TLS_IE64_HI12:
|
||||
case R_LARCH_ADD6:
|
||||
case R_LARCH_SUB6:
|
||||
case R_LARCH_ADD8:
|
||||
case R_LARCH_SUB8:
|
||||
case R_LARCH_ADD16:
|
||||
case R_LARCH_SUB16:
|
||||
case R_LARCH_ADD32:
|
||||
case R_LARCH_SUB32:
|
||||
case R_LARCH_ADD64:
|
||||
case R_LARCH_SUB64:
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x1e00'000c, // pcaddu18i $t0, 0
|
||||
0x4c00'0180, // jirl $zero, $t0, 0
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_j20(buf, (S - P + 0x20000) >> 18);
|
||||
write_k16(buf + 4, (S - P) >> 2);
|
||||
|
||||
buf += sizeof(insn);
|
||||
P += sizeof(insn);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+4
-4
@@ -52,12 +52,12 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -126,7 +126,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static constexpr i64 BIAS = 0x8000;
|
||||
|
||||
template <typename E>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {}
|
||||
|
||||
template <typename E>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const U32<E> insn[] = {
|
||||
0xdf99'0000, // ld t9, 0(gp)
|
||||
0x03e0'7825, // move t3, ra
|
||||
0x0320'f809, // jalr t9
|
||||
0x6418'0000, // daddiu t8, zero, 0
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(U32<E> *)buf |= sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx);
|
||||
*(U32<E> *)(buf + 12) |= sym.get_dynsym_idx(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <typename E>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_MIPS_64:
|
||||
*(U64<E> *)loc = val;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 GP = ctx._gp->get_addr(ctx);
|
||||
u64 GP0 = file.mips_gp0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write_combined = [&](u64 val) {
|
||||
if (rel.r_type2 == R_MIPS_64 && rel.r_type3 == R_NONE) {
|
||||
*(U64<E> *)loc = val;
|
||||
} else if (rel.r_type2 == R_MIPS_SUB && rel.r_type3 == R_MIPS_HI16) {
|
||||
*(U32<E> *)loc |= ((-val + BIAS) >> 16) & 0xffff;
|
||||
} else if (rel.r_type2 == R_MIPS_SUB && rel.r_type3 == R_MIPS_LO16) {
|
||||
*(U32<E> *)loc |= -val & 0xffff;
|
||||
} else {
|
||||
Error(ctx) << *this << ": unsupported relocation combination: "
|
||||
<< rel_to_string<E>(rel.r_type) << " "
|
||||
<< rel_to_string<E>(rel.r_type2) << " "
|
||||
<< rel_to_string<E>(rel.r_type3);
|
||||
}
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
|
||||
*(U32<E> *)loc = val;
|
||||
else
|
||||
write_combined(val);
|
||||
};
|
||||
|
||||
auto write_hi16 = [&](u64 val) {
|
||||
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
|
||||
*(U32<E> *)loc |= ((val + BIAS) >> 16) & 0xffff;
|
||||
else
|
||||
write_combined(val);
|
||||
};
|
||||
|
||||
auto write_lo16 = [&](u64 val) {
|
||||
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
|
||||
*(U32<E> *)loc |= val & 0xffff;
|
||||
else
|
||||
write_combined(val);
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_MIPS_64:
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
case R_MIPS_GPREL16:
|
||||
if (sym.is_local(ctx))
|
||||
write_lo16(S + A + GP0 - GP);
|
||||
else
|
||||
write_lo16(S + A - GP);
|
||||
break;
|
||||
case R_MIPS_GPREL32:
|
||||
write32(S + A + GP0 - GP);
|
||||
break;
|
||||
case R_MIPS_GOT_DISP:
|
||||
case R_MIPS_CALL16:
|
||||
write_lo16(ctx.extra.got->get_got_addr(ctx, sym, A) - GP);
|
||||
break;
|
||||
case R_MIPS_GOT_PAGE:
|
||||
write_lo16(ctx.extra.got->get_gotpage_got_addr(ctx, sym, A) - GP);
|
||||
break;
|
||||
case R_MIPS_GOT_OFST:
|
||||
write_lo16(S + A - ctx.extra.got->get_gotpage_page_addr(ctx, sym, A));
|
||||
break;
|
||||
case R_MIPS_JALR:
|
||||
break;
|
||||
case R_MIPS_TLS_TPREL_HI16:
|
||||
write_hi16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_TPREL_LO16:
|
||||
write_lo16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_GOTTPREL:
|
||||
write_lo16(ctx.extra.got->get_gottp_addr(ctx, sym) - GP);
|
||||
break;
|
||||
case R_MIPS_TLS_DTPREL_HI16:
|
||||
write_hi16(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_DTPREL_LO16:
|
||||
write_lo16(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_GD:
|
||||
write_lo16(ctx.extra.got->get_tlsgd_addr(ctx, sym) + A - GP);
|
||||
break;
|
||||
case R_MIPS_TLS_LDM:
|
||||
write_lo16(ctx.extra.got->get_tlsld_addr(ctx) + A - GP);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(loc, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_MIPS_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_MIPS_GOT_DISP:
|
||||
case R_MIPS_CALL16: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
ctx.extra.got->got_syms.push_back({&sym, rel.r_addend});
|
||||
break;
|
||||
}
|
||||
case R_MIPS_GOT_PAGE: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
ctx.extra.got->gotpage_syms.push_back({&sym, rel.r_addend});
|
||||
break;
|
||||
}
|
||||
case R_MIPS_TLS_GOTTPREL: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
assert(rel.r_addend == 0);
|
||||
ctx.extra.got->gottp_syms.push_back(&sym);
|
||||
break;
|
||||
}
|
||||
case R_MIPS_TLS_TPREL_HI16:
|
||||
case R_MIPS_TLS_TPREL_LO16:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_MIPS_TLS_GD: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
assert(rel.r_addend == 0);
|
||||
ctx.extra.got->tlsgd_syms.push_back(&sym);
|
||||
break;
|
||||
}
|
||||
case R_MIPS_TLS_LDM:
|
||||
ctx.extra.got->needs_tlsld = true;
|
||||
break;
|
||||
case R_MIPS_64:
|
||||
case R_MIPS_GPREL16:
|
||||
case R_MIPS_GPREL32:
|
||||
case R_MIPS_GOT_OFST:
|
||||
case R_MIPS_JALR:
|
||||
case R_MIPS_TLS_DTPREL_HI16:
|
||||
case R_MIPS_TLS_DTPREL_LO16:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool MipsGotSection<E>::SymbolAddend::operator<(const SymbolAddend &other) const {
|
||||
return std::tuple(sym->file->priority, sym->sym_idx, addend) <
|
||||
std::tuple(other.sym->file->priority, other.sym->sym_idx, other.addend);
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static bool compare(const Symbol<E> *a, const Symbol<E> *b) {
|
||||
return std::tuple(a->file->priority, a->sym_idx) <
|
||||
std::tuple(b->file->priority, b->sym_idx);
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::SymbolAddend::get_addr(Context<E> &ctx, i64 flags) const {
|
||||
return sym->get_addr(ctx, flags) + addend;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64
|
||||
MipsGotSection<E>::get_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const {
|
||||
auto it = std::lower_bound(got_syms.begin(), got_syms.end(),
|
||||
SymbolAddend{&sym, addend});
|
||||
assert(it != got_syms.end());
|
||||
i64 idx = it - got_syms.begin();
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_gotpage_got_addr(Context<E> &ctx, Symbol<E> &sym,
|
||||
i64 addend) const {
|
||||
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
|
||||
SymbolAddend{&sym, addend});
|
||||
assert(it != gotpage_syms.end());
|
||||
i64 idx = got_syms.size() + (it - gotpage_syms.begin());
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_gotpage_page_addr(Context<E> &ctx, Symbol<E> &sym,
|
||||
i64 addend) const {
|
||||
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
|
||||
SymbolAddend{&sym, addend});
|
||||
assert(it != gotpage_syms.end());
|
||||
return it->get_addr(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const {
|
||||
auto it = std::lower_bound(tlsgd_syms.begin(), tlsgd_syms.end(),
|
||||
&sym, compare<E>);
|
||||
assert(it != tlsgd_syms.end());
|
||||
i64 idx = got_syms.size() + gotpage_syms.size() + (it - tlsgd_syms.begin()) * 2;
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const {
|
||||
auto it = std::lower_bound(gottp_syms.begin(), gottp_syms.end(),
|
||||
&sym, compare<E>);
|
||||
assert(it != gottp_syms.end());
|
||||
i64 idx = got_syms.size() + gotpage_syms.size() + tlsgd_syms.size() * 2 +
|
||||
(it - gottp_syms.begin());
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_tlsld_addr(Context<E> &ctx) const {
|
||||
assert(needs_tlsld);
|
||||
i64 idx = got_syms.size() + gotpage_syms.size() + tlsgd_syms.size() * 2 +
|
||||
gottp_syms.size();
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<typename MipsGotSection<E>::GotEntry>
|
||||
MipsGotSection<E>::get_got_entries(Context<E> &ctx) const {
|
||||
std::vector<GotEntry> entries;
|
||||
auto add = [&](GotEntry ent) { entries.push_back(ent); };
|
||||
|
||||
// Create GOT entries for ordinary symbols
|
||||
for (const SymbolAddend &ent : got_syms) {
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
if (ent.sym->is_imported) {
|
||||
add({0, E::R_GLOB_DAT, ent.sym});
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we know an address at link-time, fill that GOT entry now.
|
||||
// It may need a base relocation, though.
|
||||
if (ctx.arg.pic && ent.sym->is_relative())
|
||||
add({ent.get_addr(ctx, NO_PLT), E::R_RELATIVE});
|
||||
else
|
||||
add({ent.get_addr(ctx, NO_PLT)});
|
||||
}
|
||||
|
||||
// Create GOT entries for GOT_PAGE and GOT_OFST relocs
|
||||
for (const SymbolAddend &ent : gotpage_syms) {
|
||||
if (ctx.arg.pic && ent.sym->is_relative())
|
||||
add({ent.get_addr(ctx), E::R_RELATIVE});
|
||||
else
|
||||
add({ent.get_addr(ctx)});
|
||||
}
|
||||
|
||||
// Create GOT entries for TLVs
|
||||
for (Symbol<E> *sym : tlsgd_syms) {
|
||||
if (sym->is_imported) {
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
add({0, E::R_DTPMOD, sym});
|
||||
add({0, E::R_DTPOFF, sym});
|
||||
} else if (ctx.arg.shared) {
|
||||
// If we are creating a shared library, we know the TLV's offset
|
||||
// within the current TLS block. We don't know the module ID though.
|
||||
add({0, E::R_DTPMOD});
|
||||
add({sym->get_addr(ctx) - ctx.dtp_addr});
|
||||
} else {
|
||||
// If we are creating an executable, we know both the module ID and the
|
||||
// offset. Module ID 1 indicates the main executable.
|
||||
add({1});
|
||||
add({sym->get_addr(ctx) - ctx.dtp_addr});
|
||||
}
|
||||
}
|
||||
|
||||
for (Symbol<E> *sym : gottp_syms) {
|
||||
if (sym->is_imported) {
|
||||
// If we know nothing about the symbol, let the dynamic linker
|
||||
// to fill the GOT entry.
|
||||
add({0, E::R_TPOFF, sym});
|
||||
} else if (ctx.arg.shared) {
|
||||
// If we know the offset within the current thread vector,
|
||||
// let the dynamic linker to adjust it.
|
||||
add({sym->get_addr(ctx) - ctx.tls_begin, E::R_TPOFF});
|
||||
} else {
|
||||
// Otherwise, we know the offset from the thread pointer (TP) at
|
||||
// link-time, so we can fill the GOT entry directly.
|
||||
add({sym->get_addr(ctx) - ctx.tp_addr});
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_tlsld) {
|
||||
if (ctx.arg.shared)
|
||||
add({0, E::R_DTPMOD});
|
||||
else
|
||||
add({1}); // 1 means the main executable
|
||||
add({0});
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void MipsGotSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// Finalize got_syms
|
||||
sort(got_syms);
|
||||
remove_duplicates(got_syms);
|
||||
this->shdr.sh_size = got_syms.size() * sizeof(Word<E>);
|
||||
|
||||
// Finalize gotpage_syms
|
||||
sort(gotpage_syms);
|
||||
remove_duplicates(gotpage_syms);
|
||||
this->shdr.sh_size += gotpage_syms.size() * sizeof(Word<E>);
|
||||
|
||||
// Finalize tlsgd_syms
|
||||
sort(tlsgd_syms, compare<E>);
|
||||
remove_duplicates(tlsgd_syms);
|
||||
this->shdr.sh_size += tlsgd_syms.size() * 2 * sizeof(Word<E>);
|
||||
|
||||
// Finalize gottp_syms
|
||||
sort(gottp_syms, compare<E>);
|
||||
remove_duplicates(gottp_syms);
|
||||
this->shdr.sh_size += gottp_syms.size() * sizeof(Word<E>);
|
||||
|
||||
if (needs_tlsld)
|
||||
this->shdr.sh_size += 2 * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 MipsGotSection<E>::get_reldyn_size(Context<E> &ctx) const {
|
||||
i64 n = 0;
|
||||
for (GotEntry &ent : get_got_entries(ctx))
|
||||
if (ent.r_type != R_NONE)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void MipsGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
U64<E> *buf = (U64<E> *)(ctx.buf + this->shdr.sh_offset);
|
||||
memset(buf, 0, this->shdr.sh_size);
|
||||
|
||||
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; GotEntry &ent : get_got_entries(ctx)) {
|
||||
if (ent.r_type != R_NONE)
|
||||
*dynrel++ = ElfRel<E>(this->shdr.sh_addr + i * sizeof(Word<E>),
|
||||
ent.r_type,
|
||||
ent.sym ? ent.sym->get_dynsym_idx(ctx) : 0,
|
||||
ent.val);
|
||||
buf[i++] = ent.val;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void write_plt_header(Context<E> &, u8 *); \
|
||||
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void \
|
||||
EhFrameSection<E>::apply_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
|
||||
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::scan_relocations(Context<E> &); \
|
||||
template class MipsGotSection<E>;
|
||||
|
||||
INSTANTIATE(MIPS64LE);
|
||||
INSTANTIATE(MIPS64BE);
|
||||
|
||||
} // namespace mold::elf
|
||||
+22
-20
@@ -120,14 +120,14 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 offset = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
i64 offset = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
loc[4] |= higha(offset);
|
||||
loc[5] |= lo(offset);
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -153,7 +153,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 GOT2 = file.ppc32_got2 ? file.ppc32_got2->get_addr() : 0;
|
||||
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -172,7 +172,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC_ADDR14:
|
||||
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
|
||||
@@ -409,8 +409,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const ub32 local_thunk[] = {
|
||||
// Get this thunk's address
|
||||
0x7c08'02a6, // mflr r0
|
||||
@@ -429,22 +427,26 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(plt_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_entry, sizeof(plt_entry));
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
i64 val = got - get_addr(i) - 8;
|
||||
loc[4] |= higha(val);
|
||||
loc[5] |= lo(val);
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_plt(ctx)) {
|
||||
u64 got =
|
||||
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
|
||||
i64 val = got - P - 8;
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx) - get_addr(i) - 8;
|
||||
loc[4] |= higha(val);
|
||||
loc[5] |= lo(val);
|
||||
i64 val = sym->get_addr(ctx) - P - 8;
|
||||
memcpy(buf, local_thunk, sizeof(local_thunk));
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-31
@@ -72,22 +72,22 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0xe84b'0024, // ld r2,36(r11)
|
||||
0x7d88'03a6, // mtlr r12
|
||||
0x7d62'5a14, // add r11,r2,r11
|
||||
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
||||
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
||||
0xe98b'0000, // ld r12,0(r11)
|
||||
0xe84b'0008, // ld r2,8(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0010, // ld r11,16(r11)
|
||||
0x4e80'0420, // bctr
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
|
||||
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -129,8 +129,8 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -183,10 +183,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, dynrel);
|
||||
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ub16 *)loc = ha(S + A - TOC);
|
||||
@@ -410,8 +410,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is .plt.got, we save the current r2, read an
|
||||
// address of a function descriptor from .got, restore %r2 and jump
|
||||
// to the function.
|
||||
@@ -466,26 +464,27 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
if (sym.has_got(ctx)) {
|
||||
memcpy(loc, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
i64 val = sym.get_got_addr(ctx) - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else if(sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
i64 val = sym.get_gotplt_addr(ctx) - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_got(ctx)) {
|
||||
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
} else if(sym->has_plt(ctx)) {
|
||||
i64 val = sym->get_gotplt_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, plt_thunk, sizeof(plt_thunk));
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
|
||||
memcpy(buf, local_thunk, sizeof(local_thunk));
|
||||
*(ub32 *)buf |= higha(val);
|
||||
*(ub32 *)(buf + 4) |= lo(val);
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +519,7 @@ struct OpdSymbol {
|
||||
};
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
@@ -615,7 +614,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
if (sym.get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
|
||||
Symbol<E> *real_sym = get_opd_sym_at(opd_syms, r.r_addend);
|
||||
if (!real_sym)
|
||||
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
|
||||
<< " at offset 0x" << std::hex << (u64)r.r_addend;
|
||||
|
||||
+59
-41
@@ -92,8 +92,10 @@ static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
static u64 prefix34(u64 x) {
|
||||
return bits(x, 33, 16) | (bits(x, 15, 0) << 32);
|
||||
static void write34(u8 *loc, u64 x) {
|
||||
ul32 *buf = (ul32 *)loc;
|
||||
buf[0] = (buf[0] & 0xfffc'0000) | bits(x, 33, 16);
|
||||
buf[1] = (buf[1] & 0xffff'0000) | bits(x, 15, 0);
|
||||
}
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
@@ -112,25 +114,26 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Compute the PLT entry index
|
||||
0xe80b'002c, // ld r0, 44(r11)
|
||||
0x7d8b'6050, // subf r12, r11, r12
|
||||
0x7d60'5a14, // add r11, r0, r11
|
||||
0x380c'ffcc, // addi r0, r12, -52
|
||||
0x398c'ffd4, // addi r12, r12, -44
|
||||
0x7c0b'6050, // subf r0, r11, r12
|
||||
0x7800'f082, // rldicl r0, r0, 62, 2
|
||||
|
||||
// Compute the address of .got.plt
|
||||
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
||||
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
||||
|
||||
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
|
||||
0xe98b'0000, // ld r12, 0(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0008, // ld r11, 8(r11)
|
||||
0x4e80'0420, // bctr
|
||||
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul64 *)(buf + 52) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
|
||||
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
*(ul32 *)(buf + 28) |= higha(val);
|
||||
*(ul32 *)(buf + 32) |= lo(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -147,8 +150,8 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -204,14 +207,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ul16 *)loc = ha(S + A - TOC);
|
||||
@@ -278,10 +281,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(G + GOT - P);
|
||||
write34(loc, G + GOT - P);
|
||||
break;
|
||||
case R_PPC64_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - P);
|
||||
write34(loc, S + A - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
@@ -290,7 +293,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(sym.get_gottp_addr(ctx) - P);
|
||||
write34(loc, sym.get_gottp_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
@@ -299,7 +302,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(sym.get_tlsgd_addr(ctx) - P);
|
||||
write34(loc, sym.get_tlsgd_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
@@ -308,7 +311,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(ctx.got->get_tlsld_addr(ctx) - P);
|
||||
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
@@ -317,7 +320,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - ctx.dtp_addr);
|
||||
write34(loc, S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
@@ -325,6 +328,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL34:
|
||||
write34(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
@@ -442,6 +448,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL34:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
@@ -478,12 +485,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is PLT, we read an address from .got.plt or .got
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -492,6 +498,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
static const ul32 plt_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
||||
0xe580'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -502,6 +509,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -510,6 +518,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
static const ul32 local_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0610'0000, // pla r12, foo@pcrel
|
||||
0x3980'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -521,34 +530,43 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk_power10));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_plt(ctx)) {
|
||||
u64 got =
|
||||
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
|
||||
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(loc, plt_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(loc + 1) |= prefix34(got - get_addr(i) - 4);
|
||||
memcpy(buf, plt_thunk_power10, E::thunk_size);
|
||||
write34(buf + 8, got - P - 8);
|
||||
} else {
|
||||
i64 val = got - ctx.extra.TOC->value;
|
||||
memcpy(loc, plt_thunk, E::thunk_size);
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
memcpy(buf, plt_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 8) |= higha(got - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(got - TOC);
|
||||
}
|
||||
} else {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(loc, local_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(loc + 1) |= prefix34(sym.get_addr(ctx) - get_addr(i) - 4);
|
||||
memcpy(buf, local_thunk_power10, E::thunk_size);
|
||||
write34(buf + 8, S - P - 8);
|
||||
} else {
|
||||
i64 val = sym.get_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(loc, local_thunk, E::thunk_size);
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
memcpy(buf, local_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 8) |= higha(S - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(S - TOC);
|
||||
}
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+483
-124
@@ -67,13 +67,19 @@
|
||||
//
|
||||
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
|
||||
|
||||
#if MOLD_RV64LE || MOLD_RV64BE || MOLD_RV32LE || MOLD_RV32BE
|
||||
|
||||
#include "elf.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <regex>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static void write_itype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
|
||||
*(ul32 *)loc |= bits(val, 11, 0) << 20;
|
||||
@@ -107,6 +113,11 @@ static void write_jtype(u8 *loc, u32 val) {
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static void write_citype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'0'11111'00000'11;
|
||||
*(ul16 *)loc |= bit(val, 5) << 12 | bits(val, 4, 0) << 2;
|
||||
}
|
||||
|
||||
static void write_cbtype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'000'111'00000'11;
|
||||
*(ul16 *)loc |= bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
@@ -122,27 +133,14 @@ static void write_cjtype(u8 *loc, u32 val) {
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void overwrite_uleb(u8 *loc, u64 val) {
|
||||
while (*loc & 0b1000'0000) {
|
||||
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
|
||||
val >>= 7;
|
||||
}
|
||||
*loc = val & 0b0111'1111;
|
||||
}
|
||||
|
||||
// Returns the rd register of an R/I/U/J-type instruction.
|
||||
static u32 get_rd(u32 val) {
|
||||
return bits(val, 11, 7);
|
||||
}
|
||||
|
||||
static void set_rs1(u8 *loc, u32 rs1) {
|
||||
assert(rs1 < 32);
|
||||
*(ul32 *)loc &= 0b111111'11111'00000'111'11111'1111111;
|
||||
*(ul32 *)loc |= rs1 << 15;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
template <>
|
||||
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn_64[] = {
|
||||
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
@@ -165,13 +163,10 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x000e'0067, // jr t3
|
||||
};
|
||||
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, insn_64, sizeof(insn_64));
|
||||
else
|
||||
memcpy(buf, insn_32, sizeof(insn_32));
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, E::is_64 ? insn_64 : insn_32, E::plt_hdr_size);
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 8, gotplt - plt);
|
||||
write_itype(buf + 16, gotplt - plt);
|
||||
@@ -191,35 +186,29 @@ static const ul32 plt_entry_32[] = {
|
||||
0x0000'0013, // nop
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
|
||||
else
|
||||
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
|
||||
|
||||
template <>
|
||||
void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 4, gotplt - plt);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
|
||||
else
|
||||
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
template <>
|
||||
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
write_utype(buf, got - plt);
|
||||
write_itype(buf + 4, got - plt);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -260,9 +249,17 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static inline bool is_hi20(const ElfRel<E> &rel) {
|
||||
u32 ty = rel.r_type;
|
||||
return ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
|
||||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20 ||
|
||||
ty == R_RISCV_TLSDESC_HI20;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
u64 GP = ctx.__global_pointer ? ctx.__global_pointer->get_addr(ctx) : 0;
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
@@ -291,28 +288,24 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
};
|
||||
|
||||
auto find_paired_reloc = [&] {
|
||||
Symbol<E> &sym = *file.symbols[rels[i].r_sym];
|
||||
assert(sym.get_input_section() == this);
|
||||
|
||||
if (sym.value < r_offset) {
|
||||
if (sym.value <= rels[i].r_offset - get_r_delta(i)) {
|
||||
for (i64 j = i - 1; j >= 0; j--)
|
||||
if (u32 ty = rels[j].r_type;
|
||||
ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
|
||||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20)
|
||||
if (sym.value == rels[j].r_offset - get_r_delta(j))
|
||||
return j;
|
||||
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset - get_r_delta(j))
|
||||
return j;
|
||||
} else {
|
||||
for (i64 j = i + 1; j < rels.size(); j++)
|
||||
if (u32 ty = rels[j].r_type;
|
||||
ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
|
||||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20)
|
||||
if (sym.value == rels[j].r_offset - get_r_delta(j))
|
||||
return j;
|
||||
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset - get_r_delta(j))
|
||||
return j;
|
||||
}
|
||||
|
||||
Fatal(ctx) << *this << ": paired relocation is missing: " << i;
|
||||
};
|
||||
|
||||
auto get_rd = [&](i64 offset) {
|
||||
// Returns the rd register of an R/I/U/J-type instruction.
|
||||
return bits(*(ul32 *)(contents.data() + offset), 11, 7);
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + r_offset;
|
||||
@@ -324,11 +317,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if constexpr (E::is_64)
|
||||
*(U32<E> *)loc = S + A;
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
assert(E::is_64);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
check(S + A - P, -(1 << 12), 1 << 12);
|
||||
@@ -340,27 +333,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
|
||||
i64 val = S + A - P;
|
||||
i64 rd = get_rd(rel.r_offset + 4);
|
||||
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
if (sym.esym().is_undef_weak())
|
||||
val = 0;
|
||||
|
||||
if (removed_bytes == 4) {
|
||||
// auipc + jalr -> jal
|
||||
*(ul32 *)loc = (rd << 7) | 0b1101111;
|
||||
write_jtype(loc, S + A - P);
|
||||
write_jtype(loc, val);
|
||||
} else if (removed_bytes == 6 && rd == 0) {
|
||||
// auipc + jalr -> c.j
|
||||
*(ul16 *)loc = 0b101'00000000000'01;
|
||||
write_cjtype(loc, S + A - P);
|
||||
write_cjtype(loc, val);
|
||||
} else if (removed_bytes == 6 && rd == 1) {
|
||||
// auipc + jalr -> c.jal
|
||||
assert(!E::is_64);
|
||||
*(ul16 *)loc = 0b001'00000000000'01;
|
||||
write_cjtype(loc, S + A - P);
|
||||
write_cjtype(loc, val);
|
||||
} else {
|
||||
assert(removed_bytes == 0);
|
||||
// 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.
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, val);
|
||||
write_itype(loc + 4, val);
|
||||
@@ -380,7 +376,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_utype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S: {
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_GPREL_LO12_I:
|
||||
case R_RISCV_GPREL_LO12_S: {
|
||||
i64 idx2 = find_paired_reloc();
|
||||
const ElfRel<E> &rel2 = rels[idx2];
|
||||
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
|
||||
@@ -404,19 +402,27 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_PCREL_HI20:
|
||||
val = S + A - P;
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
val = S + A - GP;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
if (rel.r_type == R_RISCV_PCREL_LO12_I)
|
||||
if (rel.r_type == R_RISCV_PCREL_LO12_I ||
|
||||
rel.r_type == R_RISCV_GPREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_HI20:
|
||||
assert(removed_bytes == 0 || removed_bytes == 4);
|
||||
if (removed_bytes == 0) {
|
||||
if (removed_bytes == 2) {
|
||||
// Rewrite LUI with C.LUI
|
||||
i64 rd = get_rd(rel.r_offset);
|
||||
*(ul16 *)loc = 0b011'0'00000'00000'01 | (rd << 7);
|
||||
write_citype(loc, (S + A + 0x800) >> 12);
|
||||
} else if (removed_bytes == 0) {
|
||||
check(S + A, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, S + A);
|
||||
}
|
||||
@@ -429,9 +435,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_stype(loc, S + A);
|
||||
|
||||
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
|
||||
// accessible relative to the zero register. If the upper 20 bits
|
||||
// are all zero, the corresponding LUI might have been removed.
|
||||
if (bits(S + A, 31, 12) == 0)
|
||||
// accessible relative to the zero register because if that's the
|
||||
// case, corresponding LUI might have been removed by relaxation.
|
||||
if (sign_extend(S + A, 11) == S + A)
|
||||
set_rs1(loc, 0);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
@@ -459,6 +465,58 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
set_rs1(loc, 4);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TLSDESC_HI20:
|
||||
if (removed_bytes == 0)
|
||||
write_utype(loc, sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
case R_RISCV_TLSDESC_ADD_LO12:
|
||||
case R_RISCV_TLSDESC_CALL: {
|
||||
i64 idx2 = find_paired_reloc();
|
||||
const ElfRel<E> &rel2 = rels[idx2];
|
||||
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
|
||||
|
||||
u64 S = sym2.get_addr(ctx);
|
||||
u64 A = rel2.r_addend;
|
||||
u64 P = get_addr() + rel2.r_offset - get_r_delta(idx2);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
if (sym2.has_tlsdesc(ctx))
|
||||
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_RISCV_TLSDESC_ADD_LO12:
|
||||
if (sym2.has_tlsdesc(ctx)) {
|
||||
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else if (sym2.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x517; // auipc a0,<hi20>
|
||||
write_utype(loc, sym2.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
if (removed_bytes == 0) {
|
||||
*(ul32 *)loc = 0x537; // lui a0,<hi20>
|
||||
write_utype(loc, S + A - ctx.tp_addr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R_RISCV_TLSDESC_CALL:
|
||||
if (sym2.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym2.has_gottp(ctx)) {
|
||||
// {ld,lw} a0, <lo12>(a0)
|
||||
*(ul32 *)loc = E::is_64 ? 0x53503 : 0x52503;
|
||||
write_itype(loc, sym2.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
if (sign_extend(val, 11) == val)
|
||||
*(ul32 *)loc = 0x513; // addi a0,zero,<lo12>
|
||||
else
|
||||
*(ul32 *)loc = 0x50513; // addi a0,a0,<lo12>
|
||||
write_itype(loc, val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_ADD8:
|
||||
loc += S + A;
|
||||
break;
|
||||
@@ -509,6 +567,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
write_cjtype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
check(S + A - GP, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, S + A - GP);
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
@@ -531,19 +593,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_SET_ULEB128:
|
||||
overwrite_uleb(loc, S + A);
|
||||
break;
|
||||
case R_RISCV_SUB_ULEB128: {
|
||||
u8 *p = loc;
|
||||
u64 val = read_uleb(p);
|
||||
overwrite_uleb(loc, val - (S + A));
|
||||
case R_RISCV_SUB_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) - S - A);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
@@ -614,12 +673,9 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_SET_ULEB128:
|
||||
overwrite_uleb(loc, S + A);
|
||||
break;
|
||||
case R_RISCV_SUB_ULEB128: {
|
||||
u8 *p = loc;
|
||||
u64 val = read_uleb(p);
|
||||
overwrite_uleb(loc, val - S - A);
|
||||
case R_RISCV_SUB_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) - S - A);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
@@ -628,7 +684,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
template <>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// If a section is not relaxed, we can copy it as a one big chunk.
|
||||
if (extra.r_deltas.empty()) {
|
||||
@@ -655,7 +711,7 @@ void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
memcpy(buf, contents.data() + pos, contents.size() - pos);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
@@ -703,7 +759,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_RISCV_TLSDESC_HI20:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
@@ -712,13 +772,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_TPREL_ADD:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
if (ctx.arg.shared)
|
||||
Fatal(ctx) << *this << ": R_RISCV_GPREL_HI20 may not be used with -shared";
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
case R_RISCV_TLSDESC_ADD_LO12:
|
||||
case R_RISCV_TLSDESC_CALL:
|
||||
case R_RISCV_ADD8:
|
||||
case R_RISCV_ADD16:
|
||||
case R_RISCV_ADD32:
|
||||
@@ -730,6 +796,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_ALIGN:
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
case R_RISCV_GPREL_LO12_I:
|
||||
case R_RISCV_GPREL_LO12_S:
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
@@ -745,14 +813,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
std::vector<ObjectFile<E> *> objs = ctx.objs;
|
||||
std::erase(objs, ctx.internal_obj);
|
||||
|
||||
if (objs.empty())
|
||||
return 0;
|
||||
|
||||
u32 ret = objs[0]->get_ehdr().e_flags;
|
||||
for (i64 i = 1; i < objs.size(); i++) {
|
||||
u32 flags = objs[i]->get_ehdr().e_flags;
|
||||
if (flags & EF_RISCV_RVC)
|
||||
ret |= EF_RISCV_RVC;
|
||||
|
||||
if ((flags & EF_RISCV_FLOAT_ABI) != (ret & EF_RISCV_FLOAT_ABI))
|
||||
Error(ctx) << *objs[i] << ": cannot link object files with different"
|
||||
<< " floating-point ABI from " << *objs[0];
|
||||
|
||||
if ((flags & EF_RISCV_RVE) != (ret & EF_RISCV_RVE))
|
||||
Error(ctx) << *objs[i] << ": cannot link object files with different"
|
||||
<< " EF_RISCV_RVE from " << *objs[0];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool is_resizable(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
|
||||
(isec->shdr().sh_flags & SHF_EXECINSTR);
|
||||
}
|
||||
|
||||
// Returns the distance between a relocated place and a symbol.
|
||||
template <typename E>
|
||||
static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We handle absolute symbols as if they were infinitely far away
|
||||
@@ -774,11 +865,14 @@ static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
}
|
||||
|
||||
// Scan relocations to shrink sections.
|
||||
template <typename E>
|
||||
static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
isec.extra.r_deltas.resize(rels.size() + 1);
|
||||
|
||||
auto get_rd = [&](i64 offset) {
|
||||
return bits(*(ul32 *)(isec.contents.data() + offset), 11, 7);
|
||||
};
|
||||
|
||||
i64 delta = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -815,6 +909,20 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
if (sym.file == ctx.internal_obj)
|
||||
continue;
|
||||
|
||||
auto find_paired_reloc = [&] {
|
||||
if (sym.value <= rels[i].r_offset) {
|
||||
for (i64 j = i - 1; j >= 0; j--)
|
||||
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset)
|
||||
return j;
|
||||
} else {
|
||||
for (i64 j = i + 1; j < rels.size(); j++)
|
||||
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset)
|
||||
return j;
|
||||
}
|
||||
|
||||
Fatal(ctx) << isec << ": paired relocation is missing: " << i;
|
||||
};
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
@@ -825,13 +933,13 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
if (dist & 1)
|
||||
break;
|
||||
|
||||
i64 rd = get_rd(*(ul32 *)(isec.contents.data() + r.r_offset + 4));
|
||||
i64 rd = get_rd(r.r_offset + 4);
|
||||
|
||||
if (rd == 0 && sign_extend(dist, 11) == dist && use_rvc) {
|
||||
if (use_rvc && rd == 0 && sign_extend(dist, 11) == dist) {
|
||||
// If rd is x0 and the jump target is within ±2 KiB, we can use
|
||||
// C.J, saving 6 bytes.
|
||||
delta += 6;
|
||||
} else if (rd == 1 && sign_extend(dist, 11) == dist && use_rvc && !E::is_64) {
|
||||
} else if (use_rvc && !E::is_64 && rd == 1 && sign_extend(dist, 11) == dist) {
|
||||
// If rd is x1 and the jump target is within ±2 KiB, we can use
|
||||
// C.JAL. This is RV32 only because C.JAL is RV32-only instruction.
|
||||
delta += 6;
|
||||
@@ -841,38 +949,68 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_HI20:
|
||||
// If the upper 20 bits are all zero, we can remove LUI.
|
||||
// The corresponding instructions referred to by LO12_I/LO12_S
|
||||
// relocations will use the zero register instead.
|
||||
if (bits(sym.get_addr(ctx), 31, 12) == 0)
|
||||
case R_RISCV_HI20: {
|
||||
u64 val = sym.get_addr(ctx) + r.r_addend;
|
||||
i64 rd = get_rd(r.r_offset);
|
||||
|
||||
if (sign_extend(val, 11) == val) {
|
||||
// We can replace `lui t0, %hi(foo)` and `add t0, t0, %lo(foo)`
|
||||
// instruction pair with `add t0, x0, %lo(foo)` if foo's bits
|
||||
// [32:11] are all one or all zero.
|
||||
delta += 4;
|
||||
} else if (use_rvc && rd != 0 && rd != 2 && sign_extend(val, 17) == val) {
|
||||
// If the upper 20 bits can actually be represented in 6 bits,
|
||||
// we can use C.LUI instead of LUI.
|
||||
delta += 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
// These relocations are used to add a high 20-bit value to the
|
||||
// thread pointer. The following two instructions materializes
|
||||
// TP + HI20(foo) in %r5, for example.
|
||||
// TP + %tprel_hi20(foo) in %t0, for example.
|
||||
//
|
||||
// lui a5,%tprel_hi(foo) # R_RISCV_TPREL_HI20 (symbol)
|
||||
// add a5,a5,tp,%tprel_add(foo) # R_RISCV_TPREL_ADD (symbol)
|
||||
// lui t0, %tprel_hi(foo) # R_RISCV_TPREL_HI20
|
||||
// add t0, t0, tp # R_RISCV_TPREL_ADD
|
||||
//
|
||||
// Then thread-local variable `foo` is accessed with a low 12-bit
|
||||
// offset like this:
|
||||
// Then thread-local variable `foo` is accessed with the low
|
||||
// 12-bit offset like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
|
||||
// sw t0, %tprel_lo(foo)(t0) # R_RISCV_TPREL_LO12_S
|
||||
//
|
||||
// However, if the variable is at TP ±2 KiB, TP + HI20(foo) is the
|
||||
// same as TP, so we can instead access the thread-local variable
|
||||
// directly using TP like this:
|
||||
// However, if the variable is at TP ± 2 KiB, TP + %tprel_hi20(foo)
|
||||
// is the same as TP, so we can instead access the thread-local
|
||||
// variable directly using TP like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(tp)
|
||||
// sw t0, %tprel_lo(foo)(tp)
|
||||
//
|
||||
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
|
||||
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
|
||||
sign_extend(val, 11) == val)
|
||||
delta += 4;
|
||||
break;
|
||||
case R_RISCV_TLSDESC_HI20:
|
||||
if (!sym.has_tlsdesc(ctx))
|
||||
delta += 4;
|
||||
break;
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
case R_RISCV_TLSDESC_ADD_LO12: {
|
||||
const ElfRel<E> &rel2 = rels[find_paired_reloc()];
|
||||
Symbol<E> &sym2 = *isec.file.symbols[rel2.r_sym];
|
||||
|
||||
if (r.r_type == R_RISCV_TLSDESC_LOAD_LO12) {
|
||||
if (!sym2.has_tlsdesc(ctx))
|
||||
delta += 4;
|
||||
} else {
|
||||
assert(r.r_type == R_RISCV_TLSDESC_ADD_LO12);
|
||||
if (!sym2.has_tlsdesc(ctx) && !sym2.has_gottp(ctx))
|
||||
if (i64 val = sym2.get_addr(ctx) + rel2.r_addend - ctx.tp_addr;
|
||||
sign_extend(val, 11) == val)
|
||||
delta += 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,8 +1026,8 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
// linker to align the location referred to by the relocation to a
|
||||
// specified byte boundary. We at least have to interpret them to satisfy
|
||||
// the alignment constraints.
|
||||
template <typename E>
|
||||
i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
template <>
|
||||
i64 riscv_resize_sections<E>(Context<E> &ctx) {
|
||||
Timer t(ctx, "riscv_resize_sections");
|
||||
|
||||
// True if we can use the 2-byte instructions. This is usually true on
|
||||
@@ -900,7 +1038,7 @@ i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
// 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()))
|
||||
if (is_resizable(isec.get()))
|
||||
shrink_section(ctx, *isec, use_rvc);
|
||||
});
|
||||
|
||||
@@ -929,21 +1067,242 @@ i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void write_plt_header(Context<E> &, u8 *); \
|
||||
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void \
|
||||
EhFrameSection<E>::apply_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
|
||||
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::copy_contents_riscv(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::scan_relocations(Context<E> &); \
|
||||
template i64 riscv_resize_sections(Context<E> &);
|
||||
// ISA name handlers
|
||||
//
|
||||
// An example of ISA name is "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0".
|
||||
// An ISA name starts with the base name (e.g. "rv64i2p1") followed by
|
||||
// ISA extensions separated by underscores.
|
||||
//
|
||||
// There are lots of ISA extensions defined for RISC-V, and they are
|
||||
// identified by name. Some extensions are of single-letter alphabet such
|
||||
// as "m" or "q". Newer extension names start with "z" followed by one or
|
||||
// more alphabets (i.e. "zicsr"). "s" and "x" prefixes are reserved
|
||||
// for supervisor-level extensions and private extensions, respectively.
|
||||
//
|
||||
// Each extension consists of a name, a major version and a minor version.
|
||||
// For example, "m2p0" indicates the "m" extension of version 2.0. "p" is
|
||||
// just a separator. Versions are often omitted in documents, but they are
|
||||
// mandatory in .riscv.attributes. Likewise, abbreviations as "g" (which
|
||||
// is short for "IMAFD") are not allowed in .riscv.attributes.
|
||||
//
|
||||
// Each RISC-V object file contains an ISA string enumerating extensions
|
||||
// used by the object file. We need to merge input objects' ISA strings
|
||||
// into a single ISA string.
|
||||
//
|
||||
// In order to guarantee string uniqueness, extensions have to be ordered
|
||||
// in a specific manner. The exact rule is unfortunately a bit complicated.
|
||||
//
|
||||
// The following functions takes care of ISA strings.
|
||||
|
||||
INSTANTIATE(RV64LE);
|
||||
INSTANTIATE(RV64BE);
|
||||
INSTANTIATE(RV32LE);
|
||||
INSTANTIATE(RV32BE);
|
||||
namespace {
|
||||
struct Extn {
|
||||
std::string name;
|
||||
i64 major;
|
||||
i64 minor;
|
||||
};
|
||||
}
|
||||
|
||||
// As per the RISC-V spec, the extension names must be sorted in a very
|
||||
// specific way, and unfortunately that's not just an alphabetical order.
|
||||
// For example, rv64imafd is a legal ISA string, whereas rv64iafdm is not.
|
||||
// The exact rule is somewhat arbitrary.
|
||||
//
|
||||
// This function returns true if the first extension name should precede
|
||||
// the second one as per the rule.
|
||||
static bool extn_name_less(const Extn &e1, const Extn &e2) {
|
||||
auto get_single_letter_rank = [](char c) -> i64 {
|
||||
std::string_view exts = "iemafdqlcbkjtpvnh";
|
||||
size_t pos = exts.find_first_of(c);
|
||||
if (pos != exts.npos)
|
||||
return pos;
|
||||
return c - 'a' + exts.size();
|
||||
};
|
||||
|
||||
auto get_rank = [&](std::string_view str) -> i64 {
|
||||
switch (str[0]) {
|
||||
case 'x':
|
||||
return 1 << 20;
|
||||
case 's':
|
||||
return 1 << 19;
|
||||
case 'z':
|
||||
return (1 << 18) + get_single_letter_rank(str[1]);
|
||||
default:
|
||||
return get_single_letter_rank(str[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return std::tuple{get_rank(e1.name), e1.name} <
|
||||
std::tuple{get_rank(e2.name), e2.name};
|
||||
}
|
||||
|
||||
static bool extn_version_less(const Extn &e1, const Extn &e2) {
|
||||
return std::tuple{e1.major, e1.minor} <
|
||||
std::tuple{e2.major, e2.minor};
|
||||
}
|
||||
|
||||
static std::optional<Extn> read_extn_string(std::string_view &str) {
|
||||
auto flags = std::regex_constants::optimize | std::regex_constants::ECMAScript;
|
||||
static std::regex re(R"(^([a-z]+)(\d+)p(\d+))", flags);
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_search(str.data(), str.data() + str.size(), m, re)) {
|
||||
str = str.substr(m.length());
|
||||
return Extn{m[1], (i64)std::stoul(m[2]), (i64)std::stoul(m[3])};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::vector<Extn> parse_arch_string(std::string_view str) {
|
||||
if (str.size() < 5)
|
||||
return {};
|
||||
|
||||
// Parse the base part
|
||||
std::string_view base = str.substr(0, 5);
|
||||
if (base != "rv32i" && base != "rv32e" && base != "rv64i" && base != "rv64e")
|
||||
return {};
|
||||
str = str.substr(4);
|
||||
|
||||
std::optional<Extn> extn = read_extn_string(str);
|
||||
if (!extn)
|
||||
return {};
|
||||
|
||||
std::vector<Extn> vec;
|
||||
extn->name = base;
|
||||
vec.push_back(*extn);
|
||||
|
||||
// Parse extensions
|
||||
while (!str.empty()) {
|
||||
if (str[0] != '_')
|
||||
return {};
|
||||
str = str.substr(1);
|
||||
|
||||
std::optional<Extn> extn = read_extn_string(str);
|
||||
if (!extn)
|
||||
return {};
|
||||
vec.push_back(*extn);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::vector<Extn> merge_extensions(std::span<Extn> x, std::span<Extn> y) {
|
||||
std::vector<Extn> vec;
|
||||
|
||||
// The base part (i.e. "rv64i" or "rv32i") must match.
|
||||
if (x[0].name != y[0].name)
|
||||
return {};
|
||||
|
||||
// Merge ISA extension strings
|
||||
while (!x.empty() && !y.empty()) {
|
||||
if (x[0].name == y[0].name) {
|
||||
vec.push_back(extn_version_less(x[0], y[0]) ? y[0] : x[0]);
|
||||
x = x.subspan(1);
|
||||
y = y.subspan(1);
|
||||
} else if (extn_name_less(x[0], y[0])) {
|
||||
vec.push_back(x[0]);
|
||||
x = x.subspan(1);
|
||||
} else {
|
||||
vec.push_back(y[0]);
|
||||
y = y.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
vec.insert(vec.end(), x.begin(), x.end());
|
||||
vec.insert(vec.end(), y.begin(), y.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::string to_string(std::span<Extn> v) {
|
||||
std::string str = v[0].name + std::to_string(v[0].major) + "p" +
|
||||
std::to_string(v[0].minor);
|
||||
|
||||
for (i64 i = 1; i < v.size(); i++)
|
||||
str += "_" + v[i].name + std::to_string(v[i].major) + "p" +
|
||||
std::to_string(v[i].minor);
|
||||
return str;
|
||||
}
|
||||
|
||||
//
|
||||
// Output .riscv.attributes class
|
||||
//
|
||||
|
||||
template <>
|
||||
void RiscvAttributesSection<E>::update_shdr(Context<E> &ctx) {
|
||||
if (!contents.empty())
|
||||
return;
|
||||
|
||||
i64 stack = -1;
|
||||
std::vector<Extn> arch;
|
||||
bool unaligned = false;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->extra.stack_align) {
|
||||
i64 val = *file->extra.stack_align;
|
||||
if (stack != -1 && stack != val)
|
||||
Error(ctx) << *file << ": stack alignment requirement mistmatch";
|
||||
stack = val;
|
||||
}
|
||||
|
||||
if (file->extra.arch) {
|
||||
std::vector<Extn> arch2 = parse_arch_string(*file->extra.arch);
|
||||
if (arch2.empty())
|
||||
Error(ctx) << *file << ": corrupted .riscv.attributes ISA string: "
|
||||
<< *file->extra.arch;
|
||||
|
||||
if (arch.empty()) {
|
||||
arch = arch2;
|
||||
} else {
|
||||
arch = merge_extensions(arch, arch2);
|
||||
if (arch.empty())
|
||||
Error(ctx) << *file << ": incompatible .riscv.attributes ISA string: "
|
||||
<< *file->extra.arch;
|
||||
}
|
||||
}
|
||||
|
||||
if (file->extra.unaligned_access)
|
||||
unaligned = true;
|
||||
}
|
||||
|
||||
if (arch.empty())
|
||||
return;
|
||||
|
||||
std::string arch_str = to_string(arch);
|
||||
contents.resize(arch_str.size() + 100);
|
||||
|
||||
u8 *p = (u8 *)contents.data();
|
||||
*p++ = 'A'; // Format version
|
||||
U32<E> *sub_sz = (U32<E> *)p; // Sub-section length
|
||||
p += 4;
|
||||
p += write_string(p, "riscv"); // Vendor name
|
||||
u8 *sub_sub_start = p;
|
||||
*p++ = ELF_TAG_FILE; // Sub-section tag
|
||||
U32<E> *sub_sub_sz = (U32<E> *)p; // Sub-sub-section length
|
||||
p += 4;
|
||||
|
||||
if (stack != -1) {
|
||||
p += write_uleb(p, ELF_TAG_RISCV_STACK_ALIGN);
|
||||
p += write_uleb(p, stack);
|
||||
}
|
||||
|
||||
p += write_uleb(p, ELF_TAG_RISCV_ARCH);
|
||||
p += write_string(p, arch_str);
|
||||
|
||||
if (unaligned) {
|
||||
p += write_uleb(p, ELF_TAG_RISCV_UNALIGNED_ACCESS);
|
||||
p += write_uleb(p, 1);
|
||||
}
|
||||
|
||||
i64 sz = p - (u8 *)contents.data();
|
||||
*sub_sz = sz - 1;
|
||||
*sub_sub_sz = p - sub_sub_start;
|
||||
contents.resize(sz);
|
||||
this->shdr.sh_size = sz;
|
||||
}
|
||||
|
||||
template <>
|
||||
void RiscvAttributesSection<E>::copy_buf(Context<E> &ctx) {
|
||||
memcpy(ctx.buf + this->shdr.sh_offset, contents.data(), contents.size());
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+28
-26
@@ -48,16 +48,23 @@ static void write_mid20(u8 *loc, u64 val) {
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static u8 insn[] = {
|
||||
// Compute PLT_INDEX
|
||||
0xb9, 0x09, 0x00, 0x01, // sgr %r0, %r1
|
||||
0xa7, 0x0b, 0xff, 0xc2, // aghi %r0, -62
|
||||
0xeb, 0x10, 0x00, 0x01, 0x00, 0x0c, // srlg %r1, %r0, 1
|
||||
0xb9, 0x08, 0x00, 0x01, // agr %r0, %r1
|
||||
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
|
||||
// Branch to _dl_runtime_resolve
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
|
||||
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
|
||||
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x07, 0x00, 0x07, 0x00, // nopr; nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 8) = (ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 6) >> 1;
|
||||
*(ub32 *)(buf + 26) =
|
||||
(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 24) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -65,15 +72,12 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
|
||||
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
|
||||
0xc0, 0x01, 0, 0, 0, 0, // lgfi %r0, PLT_INDEX
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x0d, 0x01, // basr %r0, %r1
|
||||
0x07, 0x00, // nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 14) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -86,12 +90,12 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -149,7 +153,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_390_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
@@ -356,12 +360,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
@@ -455,23 +457,23 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// We always want to relax calls to __tls_get_offset() in statically-
|
||||
// linked executables because __tls_get_offset() in libc.a just calls
|
||||
// abort().
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
|
||||
// do nothing
|
||||
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
|
||||
!ctx.arg.z_dlopen) {
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static) {
|
||||
// Do nothing
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
case R_390_TLS_LDM64: {
|
||||
bool do_relax = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
|
||||
if (!do_relax)
|
||||
case R_390_TLS_LDM64:
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
ctx.needs_tlsld = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_390_TLS_LE32:
|
||||
case R_390_TLS_LE64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
@@ -482,7 +484,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_390_TLS_LDCALL:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -169,7 +169,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
@@ -181,13 +181,13 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx);
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -229,7 +229,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
case R_SH_PLT32:
|
||||
|
||||
+24
-27
@@ -115,12 +115,12 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ub64 *)(buf + 24) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub64 *)(buf + 24) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -170,7 +170,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_SPARC_5:
|
||||
check(S + A, 0, 1 << 5);
|
||||
@@ -288,36 +288,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// We always have to relax a GOT load to a load immediate if a
|
||||
// symbol is local, because R_SPARC_GOTDATA_OP cannot represent
|
||||
// an addend for a local symbol.
|
||||
if (sym.is_imported || sym.is_ifunc()) {
|
||||
if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else {
|
||||
*(ub32 *)loc |= bits(G, 31, 10);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
}
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_LOX10: {
|
||||
if (sym.is_imported || sym.is_ifunc()) {
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else {
|
||||
*(ub32 *)loc |= bits(G, 9, 0);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
if (sym.is_imported || sym.is_ifunc())
|
||||
break;
|
||||
|
||||
if (sym.is_absolute()) {
|
||||
// ldx [ %g2 + %g1 ], %g1 → nop
|
||||
*(ub32 *)loc = 0x0100'0000;
|
||||
} else {
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
|
||||
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
|
||||
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
|
||||
@@ -568,8 +564,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (!ctx.arg.is_static && ctx.extra.tls_get_addr_sym->is_imported)
|
||||
ctx.extra.tls_get_addr_sym->flags |= NEEDS_PLT;
|
||||
if (!ctx.arg.is_static)
|
||||
if (Symbol<E> &sym = *ctx.extra.tls_get_addr_sym; sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
@@ -592,7 +589,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_SIZE32:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+119
-129
@@ -64,33 +64,46 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
// Only a canonical PLT can be address-taken; there's no way to take
|
||||
// an address of a non-canonical PLT. Therefore, a non-canonical PLT
|
||||
// doesn't have to start with an endbr64.
|
||||
if (sym.is_canonical) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 6;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -165,28 +178,6 @@ 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;
|
||||
}
|
||||
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. We can do this when we know the thread-local variable's TP-
|
||||
// relative address at link-time.
|
||||
@@ -267,7 +258,7 @@ static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
// sequence. The difference from relax_gd_to_le is that we are
|
||||
// materializing a Dynamic Thread Pointer for the current ELF module
|
||||
// instead of an address for a particular thread-local variable.
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
@@ -275,13 +266,18 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// The instructions are so short that we cannot rewrite them with
|
||||
// "mov %fs:0, %rax" which is 9 bytes long. We use a shorter code
|
||||
// sequence instead. Since "xor %eax, %eax" zero-clears %rax, the
|
||||
// meaning is equivalent.
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
@@ -297,7 +293,7 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
@@ -308,14 +304,12 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
*(ul32 *)(loc + 8) = tls_size;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -364,7 +358,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
@@ -382,7 +376,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_X86_64_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
@@ -400,32 +394,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
write32s(G + A);
|
||||
write32(G + A);
|
||||
break;
|
||||
case R_X86_64_GOT64:
|
||||
*(ul64 *)loc = G + A;
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_PLTOFF64:
|
||||
*(ul64 *)loc = S + A - GOTPLT;
|
||||
*(ul64 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOTPLT + A - P);
|
||||
write32s(GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
*(ul64 *)loc = GOTPLT + A - P;
|
||||
*(ul64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
*(ul64 *)loc = G + GOTPLT + A - P;
|
||||
*(ul64 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
// We always want to relax GOTPCRELX relocs even if --no-relax
|
||||
// was given because some static PIE runtime code depends on these
|
||||
// relaxations.
|
||||
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
@@ -435,10 +429,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
@@ -449,26 +443,21 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
if (sym.has_tlsgd(ctx))
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
relax_gd_to_ie(loc, rels[i + 1], sym.get_gottp_addr(ctx) - P);
|
||||
i++;
|
||||
} else {
|
||||
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
|
||||
i++;
|
||||
}
|
||||
else if (sym.has_gottp(ctx))
|
||||
relax_gd_to_ie(loc, rels[++i], sym.get_gottp_addr(ctx) - P);
|
||||
else
|
||||
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
} else {
|
||||
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
write32s(S + A - ctx.dtp_addr);
|
||||
@@ -495,15 +484,46 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
// x86-64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %rax.
|
||||
//
|
||||
// lea 0(%rip), %rax
|
||||
// R_X86_64_GOTPC32_TLSDESC foo
|
||||
// call *(%rax)
|
||||
// R_X86_64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// mov foo@GOTTPOFF(%rip), %rax
|
||||
// nop
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// mov $foo@TPOFF, %rax
|
||||
// nop
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// mov foo@gottpoff(%rip), %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x05;
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
// mov $foo@tpoff, %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0xc7;
|
||||
loc[-1] = 0xc0;
|
||||
write32s(S - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
@@ -512,13 +532,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_X86_64_SIZE64:
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -653,6 +666,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
if (rel.r_type == R_X86_64_TLSGD || rel.r_type == R_X86_64_TLSLD) {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": " << rel
|
||||
<< " must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": " << rel
|
||||
<< " must be followed by PLT or GOTPCREL";
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
@@ -685,25 +711,12 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
i++;
|
||||
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
|
||||
!ctx.arg.z_dlopen) {
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
i++;
|
||||
} else {
|
||||
@@ -711,18 +724,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
@@ -730,28 +731,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
|
||||
!sym.is_imported && relax_gottpoff(loc - 3);
|
||||
if (!do_relax)
|
||||
case R_X86_64_GOTTPOFF:
|
||||
if (ctx.arg.relax && relax_gottpoff(loc - 3) &&
|
||||
sym.is_tprel_linktime_const(ctx)) {
|
||||
// do nothing
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPC32_TLSDESC: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
|
||||
|
||||
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
@@ -761,7 +751,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
|
||||
+51
-34
@@ -1,6 +1,7 @@
|
||||
#include "mold.h"
|
||||
#include "../common/cmdline.h"
|
||||
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
@@ -187,6 +188,8 @@ Options:
|
||||
-z origin Mark object requiring immediate $ORIGIN processing at runtime
|
||||
-z pack-relative-relocs Alias for --pack-dyn-relocs=relr
|
||||
-z nopack-relative-relocs
|
||||
-z sectionheader Do not omit section header (default)
|
||||
-z nosectionheader Omit section header
|
||||
-z separate-loadable-segments
|
||||
Separate all loadable segments to different pages
|
||||
-z separate-code Separate code and data into different pages
|
||||
@@ -198,8 +201,8 @@ Options:
|
||||
-z notext
|
||||
-z textoff
|
||||
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha
|
||||
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha)";
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha elf64-loongarch elf32-loongarch
|
||||
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha elf64loongarch elf32loongarch)";
|
||||
|
||||
static std::vector<std::string> add_dashes(std::string name) {
|
||||
// Single-letter option
|
||||
@@ -399,13 +402,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::vector<std::string> remaining;
|
||||
std::string_view arg;
|
||||
|
||||
ctx.page_size = E::page_size;
|
||||
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
|
||||
|
||||
bool version_shown = false;
|
||||
bool warn_shared_textrel = false;
|
||||
std::optional<SeparateCodeKind> z_separate_code;
|
||||
std::optional<bool> z_relro;
|
||||
std::optional<u64> shuffle_sections_seed;
|
||||
std::unordered_set<std::string_view> rpaths;
|
||||
|
||||
auto add_rpath = [&](std::string_view arg) {
|
||||
@@ -424,7 +427,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
// It looks like the SPARC's dynamic linker takes both RELA's r_addend
|
||||
// and the value at the relocated place. So we don't want to write
|
||||
// values to relocated places.
|
||||
if (is_sparc<E>)
|
||||
if constexpr (is_sparc<E>)
|
||||
ctx.arg.apply_dynamic_relocs = false;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
@@ -521,15 +524,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
<< " aarch64linux\n armelf_linux_eabi\n elf64lriscv\n"
|
||||
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
|
||||
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
|
||||
<< " elf64_sparc\n m68kelf\n shlelf_linux\n elf64alpha";
|
||||
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
|
||||
<< " elf64alpha\n elf64loongarch\n elf32loongarch";
|
||||
version_shown = true;
|
||||
} else if (read_flag("mips32") || read_flag("mips32r2") ||
|
||||
read_flag("mips32r3") || read_flag("mips32r4") ||
|
||||
read_flag("mips32r5") || read_flag("mips32r6") ||
|
||||
read_flag("mips64") || read_flag("mips64r2") ||
|
||||
read_flag("mips64r3") || read_flag("mips64r4") ||
|
||||
read_flag("mips64r5") || read_flag("mips64r6")) {
|
||||
// Ignore useless MIPS-specific flags
|
||||
} else if (read_arg("m")) {
|
||||
if (arg == "elf_x86_64") {
|
||||
ctx.arg.emulation = X86_64::target_name;
|
||||
@@ -563,10 +560,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.emulation = SH4::target_name;
|
||||
} else if (arg == "elf64alpha") {
|
||||
ctx.arg.emulation = ALPHA::target_name;
|
||||
} else if (arg == "elf64ltsmip") {
|
||||
ctx.arg.emulation = MIPS64LE::target_name;
|
||||
} else if (arg == "elf64btsmip") {
|
||||
ctx.arg.emulation = MIPS64BE::target_name;
|
||||
} else if (arg == "elf64loongarch") {
|
||||
ctx.arg.emulation = LOONGARCH64::target_name;
|
||||
} else if (arg == "elf32loongarch") {
|
||||
ctx.arg.emulation = LOONGARCH32::target_name;
|
||||
} else {
|
||||
Fatal(ctx) << "unknown -m argument: " << arg;
|
||||
}
|
||||
@@ -635,7 +632,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
} else if (read_eq("shuffle-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
ctx.arg.shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
|
||||
shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
|
||||
} else if (read_flag("reverse-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_REVERSE;
|
||||
} else if (read_flag("rosegment")) {
|
||||
@@ -724,9 +721,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.relocatable_merge_sections = true;
|
||||
} else if (read_flag("perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_flag("pack-dyn-relocs=relr")) {
|
||||
} else if (read_flag("pack-dyn-relocs=relr") ||
|
||||
read_z_flag("pack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
} else if (read_flag("pack-dyn-relocs=none")) {
|
||||
} else if (read_flag("pack-dyn-relocs=none") ||
|
||||
read_z_flag("nopack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
} else if (read_arg("package-metadata")) {
|
||||
ctx.arg.package_metadata = arg;
|
||||
@@ -868,10 +867,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.z_origin = true;
|
||||
} else if (read_z_flag("nodefaultlib")) {
|
||||
ctx.arg.z_nodefaultlib = true;
|
||||
} else if (read_z_flag("pack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
} else if (read_z_flag("nopack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
} else if (read_z_flag("separate-loadable-segments")) {
|
||||
z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
|
||||
} else if (read_z_flag("separate-code")) {
|
||||
@@ -884,6 +879,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.z_dynamic_undefined_weak = true;
|
||||
} else if (read_z_flag("nodynamic-undefined-weak")) {
|
||||
ctx.arg.z_dynamic_undefined_weak = false;
|
||||
} else if (read_z_flag("sectionheader")) {
|
||||
ctx.arg.z_sectionheader = true;
|
||||
} else if (read_z_flag("nosectionheader")) {
|
||||
ctx.arg.z_sectionheader = false;
|
||||
} else if (read_z_flag("rewrite-endbr")) {
|
||||
ctx.arg.z_rewrite_endbr = true;
|
||||
} else if (read_flag("no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag("fatal-warnings")) {
|
||||
@@ -1009,7 +1010,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("strip-all") || read_flag("s")) {
|
||||
ctx.arg.strip_all = true;
|
||||
} else if (read_flag("strip-debug") || read_flag("S")) {
|
||||
ctx.arg.strip_all = true;
|
||||
ctx.arg.strip_debug = true;
|
||||
} else if (read_flag("warn-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_WARN;
|
||||
} else if (read_flag("error-unresolved-symbols")) {
|
||||
@@ -1104,7 +1105,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("max-cache-size")) {
|
||||
} else if (read_arg("version-script")) {
|
||||
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
|
||||
// are treated as positional arguments even if they are actually not
|
||||
// are treated as positional arguments even though they are actually not
|
||||
// positional. This is because linker scripts (a positional argument)
|
||||
// can also specify a version script, and it's better to consolidate
|
||||
// parsing in read_input_files. In particular, version scripts can
|
||||
@@ -1141,10 +1142,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
Warn(ctx) << "unknown command line option: -z " << args[1];
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-dynamic") {
|
||||
Fatal(ctx) << "unknown command line option: -dynamic;"
|
||||
<< " -dynamic is a macOS linker's option. If you are trying"
|
||||
<< " to build a binary for an Apple platform, you need to use"
|
||||
<< " ld64.mold instead of mold or ld.mold.";
|
||||
Fatal(ctx) << "unknown command line option: -dynamic; -dynamic is a "
|
||||
<< "macOS linker's option. mold does not support macOS.";
|
||||
} else {
|
||||
if (args[0][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[0];
|
||||
@@ -1181,6 +1180,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.relocatable)
|
||||
ctx.arg.is_static = true;
|
||||
|
||||
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_SHUFFLE) {
|
||||
if (shuffle_sections_seed)
|
||||
ctx.arg.shuffle_sections_seed = *shuffle_sections_seed;
|
||||
else
|
||||
ctx.arg.shuffle_sections_seed =
|
||||
((u64)std::random_device()() << 32) | std::random_device()();
|
||||
}
|
||||
|
||||
// --section-order implies `-z separate-loadable-segments`
|
||||
if (z_separate_code)
|
||||
ctx.arg.z_separate_code = *z_separate_code;
|
||||
@@ -1200,12 +1207,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
Fatal(ctx) << "-auxiliary may not be used without -shared";
|
||||
}
|
||||
|
||||
if (!ctx.arg.apply_dynamic_relocs && !E::is_rela)
|
||||
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
|
||||
<< E::target_name;
|
||||
if constexpr (!E::is_rela)
|
||||
if (!ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
|
||||
<< E::target_name;
|
||||
|
||||
if (is_sparc<E> && ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
|
||||
if constexpr (is_sparc<E>)
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
|
||||
|
||||
if (!ctx.arg.section_start.empty() && !ctx.arg.section_order.empty())
|
||||
Fatal(ctx) << "--section-start may not be used with --section-order";
|
||||
@@ -1255,6 +1264,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
ctx.overwrite_output_file = false;
|
||||
|
||||
if (!ctx.arg.chroot.empty()) {
|
||||
if (!ctx.arg.Map.empty())
|
||||
ctx.arg.Map = ctx.arg.chroot + "/" + ctx.arg.Map;
|
||||
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
ctx.arg.dependency_file = ctx.arg.chroot + "/" + ctx.arg.dependency_file;
|
||||
}
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
return remaining;
|
||||
|
||||
-554
@@ -1,554 +0,0 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, which often abbreviated as compunit
|
||||
// or cu, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using `gdb-add-index`
|
||||
// post-processing tool that comes with gdb.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name (with the same definition), while there
|
||||
// should be no two functions that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contains attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -fucntion-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4/5 and
|
||||
// .debug_rnglists/.debug_addr in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Split .debug_info into so-called "compilation units". A .debug_info
|
||||
// section usually contains one compunit unless it was created by `ld -r`.
|
||||
// This is for --gdb-index.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
file.debug_info->uncompress(ctx);
|
||||
std::string_view data = file.debug_info->contents;
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(U32<E> *)data.data() == 0xffff'ffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(U32<E> *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<GdbIndexName> vec;
|
||||
|
||||
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
|
||||
i64 off = 0;
|
||||
for (i64 i = 0; i < file.compunits.size(); i++) {
|
||||
if (offset == off)
|
||||
return file.compunits_idx + i;
|
||||
off += file.compunits[i].size();
|
||||
}
|
||||
Fatal(ctx) << isec << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
auto read = [&](InputSection<E> &isec) {
|
||||
isec.uncompress(ctx);
|
||||
std::string_view contents = isec.contents;
|
||||
|
||||
while (!contents.empty()) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(U32<E> *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(U32<E> *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
|
||||
u8 type = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string_view name = data.data();
|
||||
data = data.substr(name.size() + 1);
|
||||
|
||||
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (file.debug_pubnames)
|
||||
read(*file.debug_pubnames);
|
||||
if (file.debug_pubtypes)
|
||||
read(*file.debug_pubtypes);
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} <
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} ==
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
std::sort(vec.begin(), vec.end(), less);
|
||||
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (u8 *buf = chunk->get_uncompressed_data())
|
||||
return buf;
|
||||
return ctx.buf + chunk->shdr.sh_offset;
|
||||
}
|
||||
|
||||
// Try to find a compilation unit from .debug_info and its
|
||||
// corresponding record from .debug_abbrev and returns them.
|
||||
template <typename E>
|
||||
static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(U16<E> *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(U32<E> *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(U32<E> *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
|
||||
switch (u32 unit_type = cu[6]; unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
cu += 12;
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
cu += 20;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
|
||||
<< std::hex << unit_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
|
||||
<< dwarf_version;
|
||||
}
|
||||
|
||||
u32 abbrev_code = read_uleb(cu);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return {cu, abbrev, dwarf_version};
|
||||
}
|
||||
|
||||
// Estimate the number of address ranges contained in a given file.
|
||||
// It may over-estimate but never under-estimate.
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
// Each CU contains zero or one address area.
|
||||
i64 ret = file.compunits.size();
|
||||
|
||||
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
|
||||
// .debug_ranges contains a vector of [begin, end) address pairs.
|
||||
// The last entry must be a null terminator, so we do -1.
|
||||
if (file.debug_ranges)
|
||||
ret += file.debug_ranges->sh_size / sizeof(Word<E>) / 2 - 1;
|
||||
|
||||
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
|
||||
// contains variable-length entries. The smallest possible range entry
|
||||
// is one byte for the code and two ULEB128 values (each can be as
|
||||
// small as one byte), so 3 bytes.
|
||||
if (file.debug_rnglists)
|
||||
ret += file.debug_rnglists->sh_size / 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields. This class reads them.
|
||||
template <typename E>
|
||||
class DebugInfoReader {
|
||||
public:
|
||||
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
|
||||
: ctx(ctx), file(file), cu(cu) {}
|
||||
|
||||
u64 read(u64 form);
|
||||
|
||||
Context<E> &ctx;
|
||||
ObjectFile<E> &file;
|
||||
u8 *cu;
|
||||
};
|
||||
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *cu++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)cu;
|
||||
cu += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(cu);
|
||||
case DW_FORM_string:
|
||||
cu += strlen((char *)cu) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0) {
|
||||
// base address selection entry
|
||||
base = range[i + 1];
|
||||
} else {
|
||||
vec.push_back(range[i] + base);
|
||||
vec.push_back(range[i + 1] + base);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
Word<E> *addrx) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (;;) {
|
||||
switch (*rnglist++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return vec;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(rnglist)];
|
||||
break;
|
||||
case DW_RLE_startx_endx:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
break;
|
||||
case DW_RLE_startx_length:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)rnglist;
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
u8 *cu;
|
||||
u8 *abbrev;
|
||||
u32 dwarf_version;
|
||||
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
|
||||
|
||||
DebugInfoReader<E> reader{ctx, file, cu};
|
||||
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
std::optional<u64> rnglists_base;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = reader.read(form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(get_buffer(ctx, ctx.debug_addr) + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (dwarf_version <= 4) {
|
||||
Word<E> *range_begin =
|
||||
(Word<E> *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
|
||||
return read_debug_range(ctx, file, range_begin);
|
||||
}
|
||||
|
||||
assert(dwarf_version == 5);
|
||||
|
||||
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
|
||||
if (ranges.form == DW_FORM_sec_offset)
|
||||
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
|
||||
|
||||
if (!rnglists_base)
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {lo, high_pc.value};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {lo, addrx[high_pc.value]};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {lo, lo + high_pc.value};
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
|
||||
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+938
-904
File diff suppressed because it is too large.
Load diff
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../common/integers.h"
|
||||
|
||||
#include <concepts>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -24,8 +25,8 @@ struct SPARC64;
|
||||
struct M68K;
|
||||
struct SH4;
|
||||
struct ALPHA;
|
||||
struct MIPS64LE;
|
||||
struct MIPS64BE;
|
||||
struct LOONGARCH64;
|
||||
struct LOONGARCH32;
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
@@ -55,9 +56,9 @@ enum : u32 {
|
||||
|
||||
enum : u32 {
|
||||
SHN_UNDEF = 0,
|
||||
SHN_LORESERVE = 0xff00,
|
||||
SHN_ABS = 0xfff1,
|
||||
SHN_COMMON = 0xfff2,
|
||||
SHN_LORESERVE = 0xff00,
|
||||
SHN_XINDEX = 0xffff,
|
||||
};
|
||||
|
||||
@@ -88,6 +89,7 @@ enum : u32 {
|
||||
SHT_X86_64_UNWIND = 0x70000001,
|
||||
SHT_ARM_EXIDX = 0x70000001,
|
||||
SHT_ARM_ATTRIBUTES = 0x70000003,
|
||||
SHT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -102,7 +104,6 @@ enum : u32 {
|
||||
SHF_TLS = 0x400,
|
||||
SHF_COMPRESSED = 0x800,
|
||||
SHF_GNU_RETAIN = 0x200000,
|
||||
SHF_MIPS_GPREL = 0x10000000,
|
||||
SHF_EXCLUDE = 0x80000000,
|
||||
};
|
||||
|
||||
@@ -186,6 +187,7 @@ enum : u32 {
|
||||
PT_GNU_RELRO = 0x6474e552,
|
||||
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
|
||||
PT_ARM_EXIDX = 0x70000001,
|
||||
PT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -220,7 +222,6 @@ enum : u32 {
|
||||
EM_NONE = 0,
|
||||
EM_386 = 3,
|
||||
EM_68K = 4,
|
||||
EM_MIPS = 8,
|
||||
EM_PPC = 20,
|
||||
EM_PPC64 = 21,
|
||||
EM_S390X = 22,
|
||||
@@ -230,6 +231,7 @@ enum : u32 {
|
||||
EM_X86_64 = 62,
|
||||
EM_AARCH64 = 183,
|
||||
EM_RISCV = 243,
|
||||
EM_LOONGARCH = 258,
|
||||
EM_ALPHA = 0x9026,
|
||||
};
|
||||
|
||||
@@ -289,13 +291,7 @@ enum : u32 {
|
||||
DT_VERNEEDNUM = 0x6fffffff,
|
||||
DT_PPC_GOT = 0x70000000,
|
||||
DT_PPC64_GLINK = 0x70000000,
|
||||
DT_MIPS_RLD_VERSION = 0x70000001,
|
||||
DT_MIPS_FLAGS = 0x70000005,
|
||||
DT_MIPS_BASE_ADDRESS = 0x70000006,
|
||||
DT_MIPS_LOCAL_GOTNO = 0x7000000a,
|
||||
DT_MIPS_SYMTABNO = 0x70000011,
|
||||
DT_MIPS_GOTSYM = 0x70000013,
|
||||
DT_MIPS_OPTIONS = 0x70000029,
|
||||
DT_AARCH64_VARIANT_PCS = 0x70000005,
|
||||
DT_AUXILIARY = 0x7ffffffd,
|
||||
DT_FILTER = 0x7fffffff,
|
||||
};
|
||||
@@ -381,6 +377,24 @@ enum : u32 {
|
||||
STO_ALPHA_STD_GPLOAD = 0x22,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
ELF_TAG_FILE = 1,
|
||||
ELF_TAG_SECTION = 2,
|
||||
ELF_TAG_SYMBOL = 3,
|
||||
ELF_TAG_RISCV_STACK_ALIGN = 4,
|
||||
ELF_TAG_RISCV_ARCH = 5,
|
||||
ELF_TAG_RISCV_UNALIGNED_ACCESS = 6,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
EF_LOONGARCH_ABI_SOFT_FLOAT = 0x1,
|
||||
EF_LOONGARCH_ABI_SINGLE_FLOAT = 0x2,
|
||||
EF_LOONGARCH_ABI_DOUBLE_FLOAT = 0x3,
|
||||
EF_LOONGARCH_ABI_MODIFIER_MASK = 0x7,
|
||||
EF_LOONGARCH_OBJABI_V1 = 0x40,
|
||||
EF_LOONGARCH_OBJABI_MASK = 0xC0,
|
||||
};
|
||||
|
||||
//
|
||||
// Relocation types
|
||||
//
|
||||
@@ -731,6 +745,7 @@ enum : u32 {
|
||||
R_RISCV_TLS_DTPREL64 = 9,
|
||||
R_RISCV_TLS_TPREL32 = 10,
|
||||
R_RISCV_TLS_TPREL64 = 11,
|
||||
R_RISCV_TLSDESC = 12,
|
||||
R_RISCV_BRANCH = 16,
|
||||
R_RISCV_JAL = 17,
|
||||
R_RISCV_CALL = 18,
|
||||
@@ -760,6 +775,9 @@ enum : u32 {
|
||||
R_RISCV_RVC_BRANCH = 44,
|
||||
R_RISCV_RVC_JUMP = 45,
|
||||
R_RISCV_RVC_LUI = 46,
|
||||
R_RISCV_GPREL_LO12_I = 47,
|
||||
R_RISCV_GPREL_LO12_S = 48,
|
||||
R_RISCV_GPREL_HI20 = 49,
|
||||
R_RISCV_RELAX = 51,
|
||||
R_RISCV_SUB6 = 52,
|
||||
R_RISCV_SET6 = 53,
|
||||
@@ -771,6 +789,10 @@ enum : u32 {
|
||||
R_RISCV_PLT32 = 59,
|
||||
R_RISCV_SET_ULEB128 = 60,
|
||||
R_RISCV_SUB_ULEB128 = 61,
|
||||
R_RISCV_TLSDESC_HI20 = 62,
|
||||
R_RISCV_TLSDESC_LOAD_LO12 = 63,
|
||||
R_RISCV_TLSDESC_ADD_LO12 = 64,
|
||||
R_RISCV_TLSDESC_CALL = 65,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -1236,82 +1258,104 @@ enum : u32 {
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
R_MIPS_NONE = 0,
|
||||
R_MIPS_16 = 1,
|
||||
R_MIPS_32 = 2,
|
||||
R_MIPS_REL32 = 3,
|
||||
R_MIPS_26 = 4,
|
||||
R_MIPS_HI16 = 5,
|
||||
R_MIPS_LO16 = 6,
|
||||
R_MIPS_GPREL16 = 7,
|
||||
R_MIPS_LITERAL = 8,
|
||||
R_MIPS_GOT16 = 9,
|
||||
R_MIPS_PC16 = 10,
|
||||
R_MIPS_CALL16 = 11,
|
||||
R_MIPS_GPREL32 = 12,
|
||||
R_MIPS_UNUSED1 = 13,
|
||||
R_MIPS_UNUSED2 = 14,
|
||||
R_MIPS_UNUSED3 = 15,
|
||||
R_MIPS_SHIFT5 = 16,
|
||||
R_MIPS_SHIFT6 = 17,
|
||||
R_MIPS_64 = 18,
|
||||
R_MIPS_GOT_DISP = 19,
|
||||
R_MIPS_GOT_PAGE = 20,
|
||||
R_MIPS_GOT_OFST = 21,
|
||||
R_MIPS_GOT_HI16 = 22,
|
||||
R_MIPS_GOT_LO16 = 23,
|
||||
R_MIPS_SUB = 24,
|
||||
R_MIPS_INSERT_A = 25,
|
||||
R_MIPS_INSERT_B = 26,
|
||||
R_MIPS_DELETE = 27,
|
||||
R_MIPS_HIGHER = 28,
|
||||
R_MIPS_HIGHEST = 29,
|
||||
R_MIPS_CALL_HI16 = 30,
|
||||
R_MIPS_CALL_LO16 = 31,
|
||||
R_MIPS_SCN_DISP = 32,
|
||||
R_MIPS_REL16 = 33,
|
||||
R_MIPS_ADD_IMMEDIATE = 34,
|
||||
R_MIPS_PJUMP = 35,
|
||||
R_MIPS_RELGOT = 36,
|
||||
R_MIPS_JALR = 37,
|
||||
R_MIPS_TLS_DTPMOD32 = 38,
|
||||
R_MIPS_TLS_DTPREL32 = 39,
|
||||
R_MIPS_TLS_DTPMOD64 = 40,
|
||||
R_MIPS_TLS_DTPREL64 = 41,
|
||||
R_MIPS_TLS_GD = 42,
|
||||
R_MIPS_TLS_LDM = 43,
|
||||
R_MIPS_TLS_DTPREL_HI16 = 44,
|
||||
R_MIPS_TLS_DTPREL_LO16 = 45,
|
||||
R_MIPS_TLS_GOTTPREL = 46,
|
||||
R_MIPS_TLS_TPREL32 = 47,
|
||||
R_MIPS_TLS_TPREL64 = 48,
|
||||
R_MIPS_TLS_TPREL_HI16 = 49,
|
||||
R_MIPS_TLS_TPREL_LO16 = 50,
|
||||
R_MIPS_GLOB_DAT = 51,
|
||||
R_MIPS_PC21_S2 = 60,
|
||||
R_MIPS_PC26_S2 = 61,
|
||||
R_MIPS_PC18_S3 = 62,
|
||||
R_MIPS_PC19_S2 = 63,
|
||||
R_MIPS_PCHI16 = 64,
|
||||
R_MIPS_PCLO16 = 65,
|
||||
R_MIPS16_26 = 100,
|
||||
R_MIPS16_GPREL = 101,
|
||||
R_MIPS16_GOT16 = 102,
|
||||
R_MIPS16_CALL16 = 103,
|
||||
R_MIPS16_HI16 = 104,
|
||||
R_MIPS16_LO16 = 105,
|
||||
R_MIPS16_TLS_GD = 106,
|
||||
R_MIPS16_TLS_LDM = 107,
|
||||
R_MIPS16_TLS_DTPREL_HI16 = 108,
|
||||
R_MIPS16_TLS_DTPREL_LO16 = 109,
|
||||
R_MIPS16_TLS_GOTTPREL = 110,
|
||||
R_MIPS16_TLS_TPREL_HI16 = 111,
|
||||
R_MIPS16_TLS_TPREL_LO16 = 112,
|
||||
R_MIPS_COPY = 126,
|
||||
R_MIPS_JUMP_SLOT = 127,
|
||||
R_MIPS_NUM = 218,
|
||||
R_MIPS_PC32 = 248,
|
||||
R_MIPS_EH = 249,
|
||||
R_LARCH_NONE = 0,
|
||||
R_LARCH_32 = 1,
|
||||
R_LARCH_64 = 2,
|
||||
R_LARCH_RELATIVE = 3,
|
||||
R_LARCH_COPY = 4,
|
||||
R_LARCH_JUMP_SLOT = 5,
|
||||
R_LARCH_TLS_DTPMOD32 = 6,
|
||||
R_LARCH_TLS_DTPMOD64 = 7,
|
||||
R_LARCH_TLS_DTPREL32 = 8,
|
||||
R_LARCH_TLS_DTPREL64 = 9,
|
||||
R_LARCH_TLS_TPREL32 = 10,
|
||||
R_LARCH_TLS_TPREL64 = 11,
|
||||
R_LARCH_IRELATIVE = 12,
|
||||
R_LARCH_MARK_LA = 20,
|
||||
R_LARCH_MARK_PCREL = 21,
|
||||
R_LARCH_SOP_PUSH_PCREL = 22,
|
||||
R_LARCH_SOP_PUSH_ABSOLUTE = 23,
|
||||
R_LARCH_SOP_PUSH_DUP = 24,
|
||||
R_LARCH_SOP_PUSH_GPREL = 25,
|
||||
R_LARCH_SOP_PUSH_TLS_TPREL = 26,
|
||||
R_LARCH_SOP_PUSH_TLS_GOT = 27,
|
||||
R_LARCH_SOP_PUSH_TLS_GD = 28,
|
||||
R_LARCH_SOP_PUSH_PLT_PCREL = 29,
|
||||
R_LARCH_SOP_ASSERT = 30,
|
||||
R_LARCH_SOP_NOT = 31,
|
||||
R_LARCH_SOP_SUB = 32,
|
||||
R_LARCH_SOP_SL = 33,
|
||||
R_LARCH_SOP_SR = 34,
|
||||
R_LARCH_SOP_ADD = 35,
|
||||
R_LARCH_SOP_AND = 36,
|
||||
R_LARCH_SOP_IF_ELSE = 37,
|
||||
R_LARCH_SOP_POP_32_S_10_5 = 38,
|
||||
R_LARCH_SOP_POP_32_U_10_12 = 39,
|
||||
R_LARCH_SOP_POP_32_S_10_12 = 40,
|
||||
R_LARCH_SOP_POP_32_S_10_16 = 41,
|
||||
R_LARCH_SOP_POP_32_S_10_16_S2 = 42,
|
||||
R_LARCH_SOP_POP_32_S_5_20 = 43,
|
||||
R_LARCH_SOP_POP_32_S_0_5_10_16_S2 = 44,
|
||||
R_LARCH_SOP_POP_32_S_0_10_10_16_S2 = 45,
|
||||
R_LARCH_SOP_POP_32_U = 46,
|
||||
R_LARCH_ADD8 = 47,
|
||||
R_LARCH_ADD16 = 48,
|
||||
R_LARCH_ADD24 = 49,
|
||||
R_LARCH_ADD32 = 50,
|
||||
R_LARCH_ADD64 = 51,
|
||||
R_LARCH_SUB8 = 52,
|
||||
R_LARCH_SUB16 = 53,
|
||||
R_LARCH_SUB24 = 54,
|
||||
R_LARCH_SUB32 = 55,
|
||||
R_LARCH_SUB64 = 56,
|
||||
R_LARCH_GNU_VTINHERIT = 57,
|
||||
R_LARCH_GNU_VTENTRY = 58,
|
||||
R_LARCH_B16 = 64,
|
||||
R_LARCH_B21 = 65,
|
||||
R_LARCH_B26 = 66,
|
||||
R_LARCH_ABS_HI20 = 67,
|
||||
R_LARCH_ABS_LO12 = 68,
|
||||
R_LARCH_ABS64_LO20 = 69,
|
||||
R_LARCH_ABS64_HI12 = 70,
|
||||
R_LARCH_PCALA_HI20 = 71,
|
||||
R_LARCH_PCALA_LO12 = 72,
|
||||
R_LARCH_PCALA64_LO20 = 73,
|
||||
R_LARCH_PCALA64_HI12 = 74,
|
||||
R_LARCH_GOT_PC_HI20 = 75,
|
||||
R_LARCH_GOT_PC_LO12 = 76,
|
||||
R_LARCH_GOT64_PC_LO20 = 77,
|
||||
R_LARCH_GOT64_PC_HI12 = 78,
|
||||
R_LARCH_GOT_HI20 = 79,
|
||||
R_LARCH_GOT_LO12 = 80,
|
||||
R_LARCH_GOT64_LO20 = 81,
|
||||
R_LARCH_GOT64_HI12 = 82,
|
||||
R_LARCH_TLS_LE_HI20 = 83,
|
||||
R_LARCH_TLS_LE_LO12 = 84,
|
||||
R_LARCH_TLS_LE64_LO20 = 85,
|
||||
R_LARCH_TLS_LE64_HI12 = 86,
|
||||
R_LARCH_TLS_IE_PC_HI20 = 87,
|
||||
R_LARCH_TLS_IE_PC_LO12 = 88,
|
||||
R_LARCH_TLS_IE64_PC_LO20 = 89,
|
||||
R_LARCH_TLS_IE64_PC_HI12 = 90,
|
||||
R_LARCH_TLS_IE_HI20 = 91,
|
||||
R_LARCH_TLS_IE_LO12 = 92,
|
||||
R_LARCH_TLS_IE64_LO20 = 93,
|
||||
R_LARCH_TLS_IE64_HI12 = 94,
|
||||
R_LARCH_TLS_LD_PC_HI20 = 95,
|
||||
R_LARCH_TLS_LD_HI20 = 96,
|
||||
R_LARCH_TLS_GD_PC_HI20 = 97,
|
||||
R_LARCH_TLS_GD_HI20 = 98,
|
||||
R_LARCH_32_PCREL = 99,
|
||||
R_LARCH_RELAX = 100,
|
||||
R_LARCH_DELETE = 101,
|
||||
R_LARCH_ALIGN = 102,
|
||||
R_LARCH_PCREL20_S2 = 103,
|
||||
R_LARCH_CFA = 104,
|
||||
R_LARCH_ADD6 = 105,
|
||||
R_LARCH_SUB6 = 106,
|
||||
R_LARCH_ADD_ULEB128 = 107,
|
||||
R_LARCH_SUB_ULEB128 = 108,
|
||||
R_LARCH_64_PCREL = 109,
|
||||
};
|
||||
|
||||
//
|
||||
@@ -1598,6 +1642,16 @@ struct ElfRel<E> {
|
||||
std::conditional_t<E::is_64, U32<E>, u8> r_type;
|
||||
};
|
||||
|
||||
// Returns true if a given relocation is of type used for direct
|
||||
// function call.
|
||||
template <typename E>
|
||||
inline bool is_func_call_rel(const ElfRel<E> &r) {
|
||||
for (u32 r_type : E::R_FUNCALL)
|
||||
if (r.r_type == r_type)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct ElfDyn {
|
||||
Word<E> d_tag;
|
||||
@@ -1665,6 +1719,35 @@ struct ElfNhdr {
|
||||
// Target-specific ELF data types
|
||||
//
|
||||
|
||||
template <>
|
||||
struct ElfSym<ARM64> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 5;
|
||||
u8 arm64_variant_pcs : 1; // ARM64-specific
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 arm64_variant_pcs : 1;
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
#endif
|
||||
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfSym<PPC64V2> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
@@ -1738,107 +1821,42 @@ struct ElfRel<SPARC64> {
|
||||
ib64 r_addend;
|
||||
};
|
||||
|
||||
static inline u8 get_mips_r_type2(u8 r_type) {
|
||||
if (r_type == R_MIPS_REL32 || r_type == R_MIPS_GLOB_DAT)
|
||||
return R_MIPS_64;
|
||||
return R_NONE;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct ElfRel<MIPS64LE> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
: r_offset(offset), r_sym(sym), r_type(type), r_type2(get_mips_r_type2(type)),
|
||||
r_type3(0), r_padding(0), r_addend(addend) {}
|
||||
|
||||
ul64 r_offset;
|
||||
ul32 r_sym;
|
||||
u8 r_padding;
|
||||
u8 r_type3;
|
||||
u8 r_type2;
|
||||
u8 r_type;
|
||||
il64 r_addend;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfRel<MIPS64BE> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
: r_offset(offset), r_sym(sym), r_padding(0), r_type3(0),
|
||||
r_type2(get_mips_r_type2(type)), r_type(type), r_addend(addend) {}
|
||||
|
||||
ub64 r_offset;
|
||||
ub32 r_sym;
|
||||
u8 r_padding;
|
||||
u8 r_type3;
|
||||
u8 r_type2;
|
||||
u8 r_type;
|
||||
ib64 r_addend;
|
||||
};
|
||||
|
||||
// .MIPS.options section
|
||||
template <typename E>
|
||||
struct MipsOptions {
|
||||
u8 kind;
|
||||
u8 size;
|
||||
U16<E> section;
|
||||
U32<E> info;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct MipsRegInfo {
|
||||
U32<E> ri_gprmask;
|
||||
U32<E> ri_pad;
|
||||
U32<E> ri_cprmask[4];
|
||||
U64<E> ri_gp_value;
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
ODK_REGINFO = 1,
|
||||
};
|
||||
|
||||
//
|
||||
// Machine descriptions
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
static constexpr bool supports_ifunc = requires { E::R_IRELATIVE; };
|
||||
template <typename E> concept supports_ifunc = requires { E::R_IRELATIVE; };
|
||||
template <typename E> concept supports_tlsdesc = requires { E::R_TLSDESC; };
|
||||
template <typename E> concept needs_thunk = requires { E::thunk_size; };
|
||||
|
||||
template <typename E>
|
||||
static constexpr bool supports_tlsdesc = requires { E::R_TLSDESC; };
|
||||
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
|
||||
template <typename E> concept is_i386 = std::same_as<E, I386>;
|
||||
template <typename E> concept is_arm64 = std::same_as<E, ARM64>;
|
||||
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
|
||||
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
|
||||
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
|
||||
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
|
||||
template <typename E> concept is_rv32be = std::same_as<E, RV32BE>;
|
||||
template <typename E> concept is_ppc32 = std::same_as<E, PPC32>;
|
||||
template <typename E> concept is_ppc64v1 = std::same_as<E, PPC64V1>;
|
||||
template <typename E> concept is_ppc64v2 = std::same_as<E, PPC64V2>;
|
||||
template <typename E> concept is_s390x = std::same_as<E, S390X>;
|
||||
template <typename E> concept is_sparc64 = std::same_as<E, SPARC64>;
|
||||
template <typename E> concept is_m68k = std::same_as<E, M68K>;
|
||||
template <typename E> concept is_sh4 = std::same_as<E, SH4>;
|
||||
template <typename E> concept is_alpha = std::same_as<E, ALPHA>;
|
||||
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
|
||||
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
|
||||
|
||||
template <typename E>
|
||||
static constexpr bool needs_thunk = requires { E::thunk_size; };
|
||||
|
||||
template <typename E> static constexpr bool is_x86_64 = std::is_same_v<E, X86_64>;
|
||||
template <typename E> static constexpr bool is_i386 = std::is_same_v<E, I386>;
|
||||
template <typename E> static constexpr bool is_arm64 = std::is_same_v<E, ARM64>;
|
||||
template <typename E> static constexpr bool is_arm32 = std::is_same_v<E, ARM32>;
|
||||
template <typename E> static constexpr bool is_rv64le = std::is_same_v<E, RV64LE>;
|
||||
template <typename E> static constexpr bool is_rv64be = std::is_same_v<E, RV64BE>;
|
||||
template <typename E> static constexpr bool is_rv32le = std::is_same_v<E, RV32LE>;
|
||||
template <typename E> static constexpr bool is_rv32be = std::is_same_v<E, RV32BE>;
|
||||
template <typename E> static constexpr bool is_ppc32 = std::is_same_v<E, PPC32>;
|
||||
template <typename E> static constexpr bool is_ppc64v1 = std::is_same_v<E, PPC64V1>;
|
||||
template <typename E> static constexpr bool is_ppc64v2 = std::is_same_v<E, PPC64V2>;
|
||||
template <typename E> static constexpr bool is_s390x = std::is_same_v<E, S390X>;
|
||||
template <typename E> static constexpr bool is_sparc64 = std::is_same_v<E, SPARC64>;
|
||||
template <typename E> static constexpr bool is_m68k = std::is_same_v<E, M68K>;
|
||||
template <typename E> static constexpr bool is_sh4 = std::is_same_v<E, SH4>;
|
||||
template <typename E> static constexpr bool is_alpha = std::is_same_v<E, ALPHA>;
|
||||
template <typename E> static constexpr bool is_mips64le = std::is_same_v<E, MIPS64LE>;
|
||||
template <typename E> static constexpr bool is_mips64be = std::is_same_v<E, MIPS64BE>;
|
||||
|
||||
template <typename E> static constexpr bool is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> static constexpr bool is_arm = is_arm64<E> || is_arm32<E>;
|
||||
template <typename E> static constexpr bool is_rv64 = is_rv64le<E> || is_rv64be<E>;
|
||||
template <typename E> static constexpr bool is_rv32 = is_rv32le<E> || is_rv32be<E>;
|
||||
template <typename E> static constexpr bool is_riscv = is_rv64<E> || is_rv32<E>;
|
||||
template <typename E> static constexpr bool is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
|
||||
template <typename E> static constexpr bool is_ppc = is_ppc64<E> || is_ppc32<E>;
|
||||
template <typename E> static constexpr bool is_sparc = is_sparc64<E>;
|
||||
template <typename E> static constexpr bool is_mips64 = is_mips64le<E> || is_mips64be<E>;
|
||||
template <typename E> static constexpr bool is_mips = is_mips64<E>;
|
||||
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
|
||||
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
|
||||
template <typename E> concept is_rv32 = is_rv32le<E> || is_rv32be<E>;
|
||||
template <typename E> concept is_riscv = is_rv64<E> || is_rv32<E>;
|
||||
template <typename E> concept is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
|
||||
template <typename E> concept is_ppc = is_ppc64<E> || is_ppc32<E>;
|
||||
template <typename E> concept is_sparc = is_sparc64<E>;
|
||||
template <typename E> concept is_loongarch = is_loongarch64<E> || is_loongarch32<E>;
|
||||
|
||||
struct X86_64 {
|
||||
static constexpr std::string_view target_name = "x86_64";
|
||||
@@ -1849,7 +1867,8 @@ struct X86_64 {
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
@@ -1861,6 +1880,7 @@ struct X86_64 {
|
||||
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
|
||||
};
|
||||
|
||||
struct I386 {
|
||||
@@ -1873,6 +1893,7 @@ struct I386 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
@@ -1884,6 +1905,7 @@ struct I386 {
|
||||
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_386_PLT32 };
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
@@ -1897,7 +1919,8 @@ struct ARM64 {
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 12;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 };
|
||||
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
|
||||
@@ -1909,6 +1932,7 @@ struct ARM64 {
|
||||
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
|
||||
};
|
||||
|
||||
struct ARM32 {
|
||||
@@ -1921,8 +1945,9 @@ struct ARM32 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 12;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
static constexpr u32 thunk_hdr_size = 16;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0xff, 0xde };
|
||||
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
|
||||
@@ -1934,94 +1959,76 @@ struct ARM32 {
|
||||
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 R_FUNCALL[] = {
|
||||
R_ARM_JUMP24, R_ARM_THM_JUMP24, R_ARM_CALL, R_ARM_THM_CALL, R_ARM_PLT32,
|
||||
};
|
||||
};
|
||||
|
||||
struct RV64LE {
|
||||
struct RV64 {
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
|
||||
static constexpr 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 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV64LE : RV64 {
|
||||
static constexpr std::string_view target_name = "riscv64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct RV64BE {
|
||||
struct RV64BE : RV64 {
|
||||
static constexpr std::string_view target_name = "riscv64be";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct RV32 {
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_64;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
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 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV32LE {
|
||||
struct RV32LE : RV32 {
|
||||
static constexpr std::string_view target_name = "riscv32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
};
|
||||
|
||||
struct RV32BE {
|
||||
struct RV32BE : RV32 {
|
||||
static constexpr std::string_view target_name = "riscv32be";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
};
|
||||
|
||||
struct PPC32 {
|
||||
@@ -2036,6 +2043,7 @@ struct PPC32 {
|
||||
static constexpr u32 pltgot_size = 36;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 36;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
|
||||
@@ -2046,53 +2054,49 @@ struct PPC32 {
|
||||
static constexpr u32 R_DTPOFF = R_PPC_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_PPC_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_PPC_DTPMOD32;
|
||||
|
||||
static constexpr u32 R_FUNCALL[] = {
|
||||
R_PPC_REL24, R_PPC_PLTREL24, R_PPC_LOCAL24PC,
|
||||
};
|
||||
};
|
||||
|
||||
struct PPC64V1 {
|
||||
static constexpr std::string_view target_name = "ppc64v1";
|
||||
struct PPC64 {
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_PPC64;
|
||||
static constexpr u32 plt_hdr_size = 52;
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_PPC64_REL24, R_PPC64_REL24_NOTOC };
|
||||
};
|
||||
|
||||
struct PPC64V1 : PPC64 {
|
||||
static constexpr std::string_view target_name = "ppc64v1";
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr u32 plt_hdr_size = 44;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 28;
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
};
|
||||
|
||||
struct PPC64V2 {
|
||||
struct PPC64V2 : PPC64 {
|
||||
static constexpr std::string_view target_name = "ppc64v2";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_PPC64;
|
||||
static constexpr u32 plt_hdr_size = 60;
|
||||
static constexpr u32 plt_hdr_size = 52;
|
||||
static constexpr u32 plt_size = 4;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
static constexpr u32 thunk_size = 24;
|
||||
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f };
|
||||
};
|
||||
|
||||
struct S390X {
|
||||
@@ -2102,9 +2106,10 @@ struct S390X {
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_S390X;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 32;
|
||||
static constexpr u32 plt_hdr_size = 48;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x07, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_390_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
|
||||
@@ -2115,6 +2120,7 @@ struct S390X {
|
||||
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
|
||||
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
|
||||
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
|
||||
};
|
||||
|
||||
struct SPARC64 {
|
||||
@@ -2127,6 +2133,7 @@ struct SPARC64 {
|
||||
static constexpr u32 plt_hdr_size = 128;
|
||||
static constexpr u32 plt_size = 32;
|
||||
static constexpr u32 pltgot_size = 32;
|
||||
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 };
|
||||
|
||||
static constexpr u32 R_COPY = R_SPARC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
|
||||
@@ -2137,6 +2144,7 @@ struct SPARC64 {
|
||||
static constexpr u32 R_DTPOFF = R_SPARC_TLS_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_SPARC_TLS_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_SPARC_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SPARC_WPLT30, R_SPARC_WDISP30 };
|
||||
};
|
||||
|
||||
struct M68K {
|
||||
@@ -2149,6 +2157,7 @@ struct M68K {
|
||||
static constexpr u32 plt_hdr_size = 18;
|
||||
static constexpr u32 plt_size = 14;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0x4a, 0xfc };
|
||||
|
||||
static constexpr u32 R_COPY = R_68K_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
|
||||
@@ -2158,6 +2167,7 @@ struct M68K {
|
||||
static constexpr u32 R_DTPOFF = R_68K_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_68K_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_68K_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_68K_PLT32 };
|
||||
};
|
||||
|
||||
struct SH4 {
|
||||
@@ -2170,6 +2180,7 @@ struct SH4 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 12;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_SH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
|
||||
@@ -2179,6 +2190,7 @@ struct SH4 {
|
||||
static constexpr u32 R_DTPOFF = R_SH_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_SH_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_SH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SH_PLT32 };
|
||||
};
|
||||
|
||||
struct ALPHA {
|
||||
@@ -2191,6 +2203,7 @@ struct ALPHA {
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 0;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_ALPHA_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
|
||||
@@ -2200,48 +2213,59 @@ struct ALPHA {
|
||||
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = {};
|
||||
};
|
||||
|
||||
struct MIPS64LE {
|
||||
static constexpr std::string_view target_name = "mips64le";
|
||||
struct LOONGARCH64 {
|
||||
static constexpr std::string_view target_name = "loongarch64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_MIPS;
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 e_machine = EM_LOONGARCH;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_MIPS_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_MIPS_64;
|
||||
static constexpr u32 R_RELATIVE = R_MIPS_REL32;
|
||||
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_64;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
struct MIPS64BE {
|
||||
static constexpr std::string_view target_name = "mips64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
struct LOONGARCH32 {
|
||||
static constexpr std::string_view target_name = "loongarch32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_MIPS;
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 e_machine = EM_LOONGARCH;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_MIPS_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_MIPS_64;
|
||||
static constexpr u32 R_RELATIVE = R_MIPS_REL32;
|
||||
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_32;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,793 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, often abbreviated as compunit or
|
||||
// CU, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using the
|
||||
// `gdb-add-index` post-processing tool that comes with gdb.
|
||||
//
|
||||
// Post-relocated debug section contents are needed to create a
|
||||
// .gdb_index. Therefore, we create it after relocating all the other
|
||||
// sections. The size of the section is also hard to estimate before
|
||||
// applying relocations to debug info sections, so a .gdb_index is
|
||||
// placed at the very end of the output file, even after the section
|
||||
// header.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name, while there should be no two functions
|
||||
// that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contain attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -ffunction-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4 and .debug_rnglists/.debug_addr
|
||||
// in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
enum DwarfKind { DWARF2_32, DWARF5_32, DWARF2_64, DWARF5_64 };
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U32<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U64<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> debug_info_offset;
|
||||
U32<E> debug_info_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> debug_info_offset;
|
||||
U64<E> debug_info_size;
|
||||
};
|
||||
|
||||
struct SectionHeader {
|
||||
ul32 version = 7;
|
||||
ul32 cu_list_offset = 0;
|
||||
ul32 cu_types_offset = 0;
|
||||
ul32 ranges_offset = 0;
|
||||
ul32 symtab_offset = 0;
|
||||
ul32 const_pool_offset = 0;
|
||||
};
|
||||
|
||||
struct NameType {
|
||||
bool operator==(const NameType &) const = default;
|
||||
|
||||
bool operator<(const NameType &other) const {
|
||||
return std::tuple(hash, type, name) <
|
||||
std::tuple(other.hash, other.type, other.name);
|
||||
}
|
||||
|
||||
std::string_view name;
|
||||
u32 hash;
|
||||
u8 type;
|
||||
};
|
||||
|
||||
struct MapValue {
|
||||
u32 hash = 0;
|
||||
Atomic<u32> count = 0;
|
||||
u32 name_offset = 0;
|
||||
u32 type_offset = 0;
|
||||
};
|
||||
|
||||
struct Compunit {
|
||||
DwarfKind kind;
|
||||
i64 offset;
|
||||
i64 size;
|
||||
std::vector<std::pair<u64, u64>> ranges;
|
||||
std::vector<NameType> nametypes;
|
||||
std::vector<MapValue *> entries;
|
||||
};
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static DwarfKind get_dwarf_kind(Context<E> &ctx, u8 *p) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff) {
|
||||
CuHdrDwarf2_64<E> &hdr = *(CuHdrDwarf2_64<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_64 : DWARF2_64;
|
||||
}
|
||||
|
||||
CuHdrDwarf2_32<E> &hdr = *(CuHdrDwarf2_32<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_32 : DWARF2_32;
|
||||
}
|
||||
|
||||
template <typename E, typename CuHdr>
|
||||
u8 *find_cu_abbrev(Context<E> &ctx, u8 **p, const CuHdr &hdr) {
|
||||
if (hdr.address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << "--gdb-index: unsupported address size " << hdr.address_size;
|
||||
|
||||
if constexpr (requires { hdr.unit_type; }) {
|
||||
switch (hdr.unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
*p += 8;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unknown unit type: 0x"
|
||||
<< std::hex << hdr.unit_type;
|
||||
}
|
||||
}
|
||||
|
||||
i64 abbrev_code = read_uleb(p);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = &ctx.debug_abbrev[0] + hdr.abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(&abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << "--gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(&abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << "--gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(&abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(&abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return abbrev;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields.
|
||||
// This function reads one scalar value from a given location.
|
||||
template <typename E, typename Offset>
|
||||
u64 read_scalar(Context<E> &ctx, u8 **p, u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *(*p)++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)*p;
|
||||
*p += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)*p;
|
||||
*p += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)*p;
|
||||
*p += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)*p;
|
||||
*p += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp: {
|
||||
u64 val = *(Offset *)*p;
|
||||
*p += sizeof(Offset);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)*p;
|
||||
*p += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(p);
|
||||
case DW_FORM_string:
|
||||
*p += strlen((char *)*p) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_debug_range(Word<E> *range, u64 base) {
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0)
|
||||
base = range[i + 1];
|
||||
else
|
||||
vec.emplace_back(range[i] + base, range[i + 1] + base);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static void
|
||||
read_rnglist_range(std::vector<std::pair<u64, u64>> &vec, u8 *p,
|
||||
Word<E> *addrx, u64 base) {
|
||||
for (;;) {
|
||||
switch (*p++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(&p)];
|
||||
break;
|
||||
case DW_RLE_startx_endx: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val2]);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_startx_length: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val1] + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_offset_pair: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
|
||||
// If the base is 0, this address range is for an eliminated
|
||||
// section. We only emit it if it's alive.
|
||||
if (base)
|
||||
vec.emplace_back(base + val1, base + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end: {
|
||||
u64 val1 = ((Word<E> *)p)[0];
|
||||
u64 val2 = ((Word<E> *)p)[1];
|
||||
p += sizeof(Word<E>) * 2;
|
||||
vec.emplace_back(val1, val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_start_length: {
|
||||
u64 val1 = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(val1, val1 + val2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E, typename CuHdr>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_address_ranges(Context<E> &ctx, const Compunit &cu) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *p = &ctx.debug_info[0] + cu.offset;
|
||||
CuHdr &hdr = *(CuHdr *)p;
|
||||
p += sizeof(hdr);
|
||||
|
||||
u8 *abbrev = find_cu_abbrev(ctx, &p, hdr);
|
||||
|
||||
// Now, read debug info records.
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
u64 rnglists_base = -1;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = read_scalar<E, Offset>(ctx, &p, form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(&ctx.debug_addr[0] + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (hdr.version <= 4) {
|
||||
Word<E> *p = (Word<E> *)(&ctx.debug_ranges[0] + ranges.value);
|
||||
return read_debug_range<E>(p, low_pc.value);
|
||||
}
|
||||
|
||||
assert(hdr.version == 5);
|
||||
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
u8 *buf = &ctx.debug_rnglists[0];
|
||||
|
||||
if (ranges.form == DW_FORM_sec_offset) {
|
||||
read_rnglist_range<E>(vec, buf + ranges.value, addrx, low_pc.value);
|
||||
} else {
|
||||
if (rnglists_base == -1)
|
||||
Fatal(ctx) << "--gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + rnglists_base;
|
||||
i64 num_offsets = *(U32<E> *)(base - 4);
|
||||
Offset *offsets = (Offset *)base;
|
||||
|
||||
for (i64 i = 0; i < num_offsets; i++)
|
||||
read_rnglist_range<E>(vec, base + offsets[i], addrx, low_pc.value);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {{lo, high_pc.value}};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {{lo, addrx[high_pc.value]}};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {{lo, lo + high_pc.value}};
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E, typename PubnamesHdr>
|
||||
static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
|
||||
std::vector<Compunit> &cus, ObjectFile<E> &file) {
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
auto get_cu = [&](i64 offset) {
|
||||
for (i64 i = 0; i < cus.size(); i++)
|
||||
if (cus[i].offset == offset)
|
||||
return &cus[i];
|
||||
Fatal(ctx) << file << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
Compunit *cu = get_cu(file.debug_info->offset + hdr.debug_info_offset);
|
||||
i64 size = hdr.size + offsetof(PubnamesHdr, size) + sizeof(hdr.size);
|
||||
u8 *p = (u8 *)&hdr + sizeof(hdr);
|
||||
u8 *end = (u8 *)&hdr + size;
|
||||
|
||||
while (p < end) {
|
||||
if (*(Offset *)p == 0)
|
||||
break;
|
||||
p += sizeof(Offset);
|
||||
|
||||
u8 type = *p++;
|
||||
std::string_view name = (char *)p;
|
||||
p += name.size() + 1;
|
||||
cu->nametypes.push_back({name, gdb_hash(name), type});
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
static void read_pubnames(Context<E> &ctx, std::vector<Compunit> &cus,
|
||||
ObjectFile<E> &file) {
|
||||
for (InputSection<E> *isec : { file.debug_pubnames, file.debug_pubtypes }) {
|
||||
if (!isec)
|
||||
continue;
|
||||
|
||||
isec->uncompress(ctx);
|
||||
if (isec->contents.empty())
|
||||
continue;
|
||||
|
||||
u8 *p = (u8*)&isec->contents[0];
|
||||
u8 *end = p + isec->contents.size();
|
||||
|
||||
while (p < end) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff)
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr64<E> *)p, cus, file);
|
||||
else
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr32<E> *)p, cus, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Compunit> read_compunits(Context<E> &ctx) {
|
||||
std::vector<Compunit> cus;
|
||||
|
||||
// Read compunits from the output .debug_info section.
|
||||
u8 *begin = &ctx.debug_info[0];
|
||||
u8 *end = begin + ctx.debug_info.size();
|
||||
|
||||
for (u8 *p = begin; p < end;) {
|
||||
DwarfKind kind = get_dwarf_kind(ctx, p);
|
||||
i64 size;
|
||||
if (kind == DWARF2_32 || kind == DWARF5_32)
|
||||
size = ((CuHdrDwarf2_32<E> *)p)->size + 4;
|
||||
else
|
||||
size = ((CuHdrDwarf2_64<E> *)p)->size + 12;
|
||||
|
||||
cus.push_back(Compunit{kind, p - begin, size});
|
||||
p += size;
|
||||
}
|
||||
|
||||
// Read address ranges for each compunit.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
switch (cu.kind) {
|
||||
case DWARF2_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF2_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_64<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_64<E>>(ctx, cu);
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove empty ranges
|
||||
std::erase_if(cu.ranges, [](std::pair<u64, u64> p) {
|
||||
return p.first == 0 || p.first == p.second;
|
||||
});
|
||||
});
|
||||
|
||||
// Read symbols from .debug_gnu_pubnames and .debug_gnu_pubtypes.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
read_pubnames(ctx, cus, *file);
|
||||
});
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
sort(cu.nametypes);
|
||||
remove_duplicates(cu.nametypes);
|
||||
});
|
||||
|
||||
return cus;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (chunk->is_compressed)
|
||||
return chunk->uncompressed_data;
|
||||
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_gdb_index(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_gdb_index");
|
||||
|
||||
// Find debug info sections
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
std::string_view name = chunk->name;
|
||||
if (name == ".debug_info")
|
||||
ctx.debug_info = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_abbrev")
|
||||
ctx.debug_abbrev = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_ranges")
|
||||
ctx.debug_ranges = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_addr")
|
||||
ctx.debug_addr = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_rnglists")
|
||||
ctx.debug_rnglists = get_buffer(ctx, chunk);
|
||||
}
|
||||
|
||||
if (ctx.debug_info.empty())
|
||||
return;
|
||||
|
||||
// Read debug info
|
||||
std::vector<Compunit> cus = read_compunits(ctx);
|
||||
|
||||
// Uniquify symbols
|
||||
HyperLogLog estimator;
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
HyperLogLog e;
|
||||
for (NameType &nt : cu.nametypes)
|
||||
e.insert(nt.hash);
|
||||
estimator.merge(e);
|
||||
});
|
||||
|
||||
ConcurrentMap<MapValue> map(estimator.get_cardinality() * 3 / 2);
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
cu.entries.reserve(cu.nametypes.size());
|
||||
for (NameType &nt : cu.nametypes) {
|
||||
MapValue *ent;
|
||||
bool inserted;
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash, MapValue{nt.hash});
|
||||
ent->count++;
|
||||
cu.entries.push_back(ent);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort symbols for build reproducibility
|
||||
using Entry = typename decltype(map)::Entry;
|
||||
std::vector<Entry *> entries = map.get_sorted_entries_all();
|
||||
|
||||
// Compute sizes of each components
|
||||
SectionHeader hdr;
|
||||
hdr.cu_list_offset = sizeof(hdr);
|
||||
hdr.cu_types_offset = hdr.cu_list_offset + cus.size() * 16;
|
||||
hdr.ranges_offset = hdr.cu_types_offset;
|
||||
|
||||
hdr.symtab_offset = hdr.ranges_offset;
|
||||
for (Compunit &cu : cus)
|
||||
hdr.symtab_offset += cu.ranges.size() * 20;
|
||||
|
||||
i64 ht_size = bit_ceil(entries.size() * 5 / 4 + 1);
|
||||
hdr.const_pool_offset = hdr.symtab_offset + ht_size * 8;
|
||||
|
||||
i64 offset = 0;
|
||||
for (Entry *ent : entries) {
|
||||
ent->value.type_offset = offset;
|
||||
offset += ent->value.count * 4 + 4;
|
||||
}
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
ent->value.name_offset = offset;
|
||||
offset += ent->keylen + 1;
|
||||
}
|
||||
|
||||
i64 bufsize = hdr.const_pool_offset + offset;
|
||||
|
||||
// Allocate an output buffer
|
||||
ctx.output_file->buf2.resize(bufsize);
|
||||
u8 *buf = ctx.output_file->buf2.data();
|
||||
|
||||
// Write a section header
|
||||
memcpy(buf, &hdr, sizeof(hdr));
|
||||
|
||||
// Write a CU list
|
||||
u8 *p = buf + sizeof(hdr);
|
||||
|
||||
for (Compunit &cu : cus) {
|
||||
*(ul64 *)p = cu.offset;
|
||||
*(ul64 *)(p + 8) = cu.size;
|
||||
p += 16;
|
||||
}
|
||||
|
||||
// Write address areas
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
for (std::pair<u64, u64> range : cus[i].ranges) {
|
||||
*(ul64 *)p = range.first;
|
||||
*(ul64 *)(p + 8) = range.second;
|
||||
*(ul32 *)(p + 16) = i;
|
||||
p += 20;
|
||||
}
|
||||
}
|
||||
|
||||
// Write a symbol table
|
||||
u32 mask = ht_size - 1;
|
||||
ul32 *ht = (ul32 *)(buf + hdr.symtab_offset);
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
u32 hash = ent->value.hash;
|
||||
u32 step = ((hash * 17) & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
|
||||
while (ht[j * 2] || ht[j * 2 + 1])
|
||||
j = (j + step) & mask;
|
||||
|
||||
ht[j * 2] = ent->value.name_offset;
|
||||
ht[j * 2 + 1] = ent->value.type_offset;
|
||||
}
|
||||
|
||||
// Write types
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
Compunit &cu = cus[i];
|
||||
u8 *base = buf + hdr.const_pool_offset;
|
||||
|
||||
for (i64 j = 0; j < cu.nametypes.size(); j++) {
|
||||
ul32 *p = (ul32 *)(base + cu.entries[j]->type_offset);
|
||||
i64 idx = ++p[0];
|
||||
p[idx] = (cu.nametypes[j].type << 24) | i;
|
||||
}
|
||||
}
|
||||
|
||||
// Write names
|
||||
tbb::parallel_for_each(entries, [&](Entry *ent) {
|
||||
memcpy(buf + hdr.const_pool_offset + ent->value.name_offset,
|
||||
ent->key, ent->keylen);
|
||||
});
|
||||
|
||||
// Update the section size and rewrite the section header
|
||||
if (ctx.shdr) {
|
||||
ctx.gdb_index->shdr.sh_size = bufsize;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void write_gdb_index(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+30
-28
@@ -65,7 +65,7 @@
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../common/sha.h"
|
||||
#include "blake3.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
@@ -81,7 +81,7 @@ static constexpr int64_t HASH_SIZE = 16;
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
|
||||
namespace std {
|
||||
template<> struct hash<Digest> {
|
||||
template <> struct hash<Digest> {
|
||||
size_t operator()(const Digest &k) const {
|
||||
return *(int64_t *)&k[0];
|
||||
}
|
||||
@@ -115,26 +115,26 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
|
||||
ctx.arg.ignore_data_address_equality;
|
||||
bool is_relro = (name == ".data.rel.ro" ||
|
||||
name.starts_with(".data.rel.ro."));
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
|
||||
bool is_bss = (shdr.sh_type == SHT_NOBITS);
|
||||
bool is_empty = (shdr.sh_size == 0);
|
||||
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
|
||||
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
|
||||
bool is_enumerable = is_c_identifier(name);
|
||||
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
|
||||
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
|
||||
return false;
|
||||
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
return (ctx.arg.icf_all || !isec.address_taken) &&
|
||||
name != ".init" && name != ".fini";
|
||||
} else {
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.output_section && isec.output_section->is_relro;
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
}
|
||||
|
||||
static Digest digest_final(SHA256Hash &sha) {
|
||||
u8 buf[SHA256_SIZE];
|
||||
sha.finish(buf);
|
||||
static Digest digest_final(blake3_hasher *hasher) {
|
||||
assert(HASH_SIZE <= BLAKE3_OUT_LEN);
|
||||
|
||||
u8 buf[BLAKE3_OUT_LEN];
|
||||
blake3_hasher_finalize(hasher, buf, BLAKE3_OUT_LEN);
|
||||
|
||||
Digest digest;
|
||||
memcpy(digest.data(), buf, HASH_SIZE);
|
||||
@@ -234,15 +234,16 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
SHA256Hash sha;
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
sha.update((u8 *)&val, sizeof(val));
|
||||
blake3_hasher_update(&hasher, (u8 *)&val, sizeof(val));
|
||||
};
|
||||
|
||||
auto hash_string = [&](std::string_view str) {
|
||||
hash(str.size());
|
||||
sha.update((u8 *)str.data(), str.size());
|
||||
blake3_hasher_update(&hasher, (u8 *)str.data(), str.size());
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
@@ -298,7 +299,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
|
||||
return digest_final(sha);
|
||||
return digest_final(&hasher);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -411,16 +412,17 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
if (converged.get(i))
|
||||
return;
|
||||
|
||||
SHA256Hash sha;
|
||||
sha.update(digests[2][i].data(), HASH_SIZE);
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
blake3_hasher_update(&hasher, digests[2][i].data(), HASH_SIZE);
|
||||
|
||||
i64 begin = edge_indices[i];
|
||||
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
|
||||
|
||||
for (i64 j : edges.subspan(begin, end - begin))
|
||||
sha.update(digests[slot][j].data(), HASH_SIZE);
|
||||
blake3_hasher_update(&hasher, digests[slot][j].data(), HASH_SIZE);
|
||||
|
||||
digests[!slot][i] = digest_final(sha);
|
||||
digests[!slot][i] = digest_final(&hasher);
|
||||
|
||||
if (digests[slot][i] == digests[!slot][i]) {
|
||||
// This node has converged. Skip further iterations as it will
|
||||
@@ -563,7 +565,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group sections by SHA digest.
|
||||
// Group sections by BLAKE3 digest.
|
||||
{
|
||||
Timer t(ctx, "group");
|
||||
|
||||
|
||||
+122
-129
@@ -104,7 +104,7 @@ static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
|
||||
|
||||
template <typename E>
|
||||
void
|
||||
ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
ObjectFile<E>::parse_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
std::string_view data = this->get_string(ctx, shdr);
|
||||
|
||||
while (!data.empty()) {
|
||||
@@ -140,24 +140,55 @@ ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
}
|
||||
}
|
||||
|
||||
// <format-version>
|
||||
// [ <section-length> "vendor-name" <file-tag> <size> <attribute>*]+ ]*
|
||||
template <typename E>
|
||||
static u64 read_mips_gp0(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::string_view data = isec.contents;
|
||||
static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view data) {
|
||||
if (data.empty())
|
||||
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
|
||||
|
||||
if (u8 format_version = data[0]; format_version != 'A')
|
||||
return;
|
||||
data = data.substr(1);
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < sizeof(MipsOptions<E>))
|
||||
Fatal(ctx) << isec << ": corrupted .MIPS.options section";
|
||||
i64 sz = *(U32<E> *)data.data();
|
||||
if (data.size() < sz)
|
||||
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
|
||||
|
||||
MipsOptions<E> *opt = (MipsOptions<E> *)data.data();
|
||||
if (opt->kind == ODK_REGINFO) {
|
||||
if (data.size() < sizeof(MipsOptions<E>) + sizeof(MipsRegInfo<E>))
|
||||
Fatal(ctx) << isec << ": corrupted .MIPS.options section";
|
||||
MipsRegInfo<E> *info = (MipsRegInfo<E> *)(opt + 1);
|
||||
return info->ri_gp_value;
|
||||
std::string_view p(data.data() + 4, sz - 4);
|
||||
data = data.substr(sz);
|
||||
|
||||
if (!p.starts_with("riscv\0"sv))
|
||||
continue;
|
||||
p = p.substr(6);
|
||||
|
||||
if (!p.starts_with(ELF_TAG_FILE))
|
||||
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
|
||||
p = p.substr(5); // skip the tag and the sub-sub-section size
|
||||
|
||||
while (!p.empty()) {
|
||||
i64 tag = read_uleb(&p);
|
||||
|
||||
switch (tag) {
|
||||
case ELF_TAG_RISCV_STACK_ALIGN:
|
||||
file.extra.stack_align = read_uleb(&p);
|
||||
break;
|
||||
case ELF_TAG_RISCV_ARCH: {
|
||||
i64 pos = p.find_first_of('\0');
|
||||
file.extra.arch = p.substr(0, pos);
|
||||
p = p.substr(pos + 1);
|
||||
break;
|
||||
}
|
||||
case ELF_TAG_RISCV_UNALIGNED_ACCESS:
|
||||
file.extra.unaligned_access = read_uleb(&p);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data = data.substr(opt->size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -170,6 +201,17 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
shdr.sh_type != SHT_LLVM_ADDRSIG && !ctx.arg.relocatable)
|
||||
continue;
|
||||
|
||||
if constexpr (is_arm<E>)
|
||||
if (shdr.sh_type == SHT_ARM_ATTRIBUTES)
|
||||
continue;
|
||||
|
||||
if constexpr (is_riscv<E>) {
|
||||
if (shdr.sh_type == SHT_RISCV_ATTRIBUTES) {
|
||||
read_riscv_attributes(ctx, *this, this->get_string(ctx, shdr));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (shdr.sh_type) {
|
||||
case SHT_GROUP: {
|
||||
// Get the signature of this section group.
|
||||
@@ -206,13 +248,12 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
comdat_groups.push_back({group, (u32)i, entries.subspan(1)});
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
case SHT_SYMTAB:
|
||||
case SHT_SYMTAB_SHNDX:
|
||||
case SHT_STRTAB:
|
||||
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;
|
||||
@@ -221,11 +262,8 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// 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.
|
||||
//
|
||||
// MIPS object files don't contain .note.GNU-stack for some reason,
|
||||
// so ignore this error on MIPS.
|
||||
if (name == ".note.GNU-stack" && !ctx.arg.relocatable) {
|
||||
if ((shdr.sh_flags & SHF_EXECINSTR) && !is_mips<E>) {
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
if (!ctx.arg.z_execstack && !ctx.arg.z_execstack_if_needed)
|
||||
Warn(ctx) << *this << ": this file may cause a segmentation"
|
||||
" fault because it requires an executable stack. See"
|
||||
@@ -237,7 +275,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
if (name == ".note.gnu.property") {
|
||||
read_note_gnu_property(ctx, shdr);
|
||||
parse_note_gnu_property(ctx, shdr);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -261,12 +299,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
is_debug_section(shdr, name))
|
||||
continue;
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
llvm_addrsig = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If an output file doesn't have a section header (i.e.
|
||||
// --oformat=binary is given), we discard all non-memory-allocated
|
||||
// sections. This is because without a section header, we can't find
|
||||
@@ -274,15 +306,20 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.oformat_binary && !(shdr.sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
llvm_addrsig = std::move(this->sections[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name == ".eh_frame")
|
||||
eh_frame_section = this->sections[i].get();
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
ppc32_got2 = this->sections[i].get();
|
||||
|
||||
if constexpr (is_mips<E>)
|
||||
if (name == ".MIPS.options")
|
||||
mips_gp0 = read_mips_gp0(ctx, *this->sections[i]);
|
||||
extra.got2 = this->sections[i].get();
|
||||
|
||||
// Save debug sections for --gdb-index.
|
||||
if (ctx.arg.gdb_index) {
|
||||
@@ -290,10 +327,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = isec;
|
||||
if (name == ".debug_ranges")
|
||||
debug_ranges = isec;
|
||||
if (name == ".debug_rnglists")
|
||||
debug_rnglists = isec;
|
||||
|
||||
// If --gdb-index is given, contents of .debug_gnu_pubnames and
|
||||
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
|
||||
@@ -343,16 +376,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_ehframe_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->name() == ".eh_frame") {
|
||||
read_ehframe(ctx, *isec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .eh_frame contains data records explaining how to handle exceptions.
|
||||
// When an exception is thrown, the runtime searches a record from
|
||||
// .eh_frame with the current program counter as a key. A record that
|
||||
@@ -383,7 +406,11 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
|
||||
//
|
||||
// This function parses an input .eh_frame section.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
|
||||
if (!eh_frame_section)
|
||||
return;
|
||||
|
||||
InputSection<E> &isec = *eh_frame_section;
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
@@ -555,7 +582,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
// We expect them to be sorted, so sort them if necessary.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
|
||||
if constexpr (is_riscv<E>) {
|
||||
if constexpr (is_riscv<E> || is_loongarch<E>) {
|
||||
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
|
||||
return a.r_offset < b.r_offset;
|
||||
};
|
||||
@@ -610,9 +637,16 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
if (!(shdr.sh_flags & SHF_MERGE))
|
||||
return nullptr;
|
||||
|
||||
i64 entsize = shdr.sh_entsize;
|
||||
if (entsize == 0)
|
||||
entsize = (shdr.sh_flags & SHF_STRINGS) ? 1 : (int)shdr.sh_addralign;
|
||||
|
||||
if (entsize == 0)
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<MergeableSection<E>> rec(new MergeableSection<E>);
|
||||
rec->parent = MergedSection<E>::get_instance(ctx, sec.name(), shdr.sh_type,
|
||||
shdr.sh_flags);
|
||||
shdr.sh_flags, entsize);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
if (sec.sh_size == 0)
|
||||
@@ -623,18 +657,10 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
const char *begin = data.data();
|
||||
u64 entsize = shdr.sh_entsize;
|
||||
HyperLogLog estimator;
|
||||
|
||||
// Split sections
|
||||
if (shdr.sh_flags & SHF_STRINGS) {
|
||||
if (entsize == 0) {
|
||||
// GHC (Glasgow Haskell Compiler) sometimes creates a mergeable
|
||||
// string section with entsize of 0 instead of 1, though such
|
||||
// entsize is technically wrong. This is a workaround for the issue.
|
||||
entsize = 1;
|
||||
}
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t end = find_null(data, entsize);
|
||||
if (end == data.npos)
|
||||
@@ -651,11 +677,6 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
estimator.insert(hash);
|
||||
}
|
||||
} else {
|
||||
// OCaml compiler seems to create a mergeable non-string section with
|
||||
// entisze of 0. Such section is malformed. We do not split such section.
|
||||
if (entsize == 0)
|
||||
return nullptr;
|
||||
|
||||
if (data.size() % entsize)
|
||||
Fatal(ctx) << sec << ": section size is not multiple of sh_entsize";
|
||||
|
||||
@@ -687,13 +708,14 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
// atomic unit of mergeable section "section pieces".
|
||||
//
|
||||
// This feature is typically used for string literals. String literals
|
||||
// are usually put into a mergeable section by a compiler. If the same
|
||||
// string literal happen to occur in two different translation units,
|
||||
// a linker merges them into a single instance of a string, so that
|
||||
// a linker's output doesn't contain duplicate string literals.
|
||||
// are usually put into a mergeable section by the compiler. If the same
|
||||
// string literal happens to occur in two different translation units,
|
||||
// the linker merges them into a single instance of a string, so that
|
||||
// the linker's output doesn't contain duplicate string literals.
|
||||
//
|
||||
// Handling symbols in mergeable sections is a bit tricky. Assume that
|
||||
// we have a mergeable section with the following contents and symbols:
|
||||
// Handling symbols in the mergeable sections is a bit tricky. Assume
|
||||
// that we have a mergeable section with the following contents and
|
||||
// symbols:
|
||||
//
|
||||
// Hello world\0foo bar\0
|
||||
// ^ ^
|
||||
@@ -702,23 +724,23 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
//
|
||||
// '\0' represents a NUL byte. This mergeable section contains two
|
||||
// section pieces, "Hello world" and "foo bar". The first string is
|
||||
// referred by two symbols, .rodata and .L.str0, and the second by
|
||||
// referred to by two symbols, .rodata and .L.str0, and the second by
|
||||
// .L.str1. .rodata is a section symbol and therefore a local symbol
|
||||
// and refers the begining of the section.
|
||||
// and refers to the beginning of the section.
|
||||
//
|
||||
// In this example, there are actually two different ways to point to
|
||||
// string "foo bar", because .rodata+12 and .L.str1+0 refer the same
|
||||
// string "foo bar", because .rodata+12 and .L.str1+0 refer to the same
|
||||
// place in the section. This kind of "out-of-bound" reference occurs
|
||||
// only when a symbol is a section symbol. In other words, compiler
|
||||
// may use an offset from the beginning of a section to refer any
|
||||
// only when a symbol is a section symbol. In other words, the compiler
|
||||
// may use an offset from the beginning of a section to refer to any
|
||||
// section piece in a section, but it doesn't do for any other types
|
||||
// of symbols.
|
||||
//
|
||||
// In mold, we attach section pieces symbols. If a relocation refers a
|
||||
// section symbol whose section is a mergeable section, we create a
|
||||
// new dummy symbol with a section piece and redirect the relocation
|
||||
// to the symbol. If a non-section symbol refers a section piece, the
|
||||
// section piece is attached to the symbol.
|
||||
// In mold, we attach symbols to section pieces. If a relocation refers
|
||||
// to a section symbol, and that symbol's section is a mergeable one,
|
||||
// we create a new dummy symbol for a section piece and redirect the
|
||||
// relocation to this new symbol. If a non-section symbol refers to a
|
||||
// section piece, the section piece is attached to the symbol.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_mergeable_sections(Context<E> &ctx) {
|
||||
mergeable_sections.resize(sections.size());
|
||||
@@ -826,33 +848,6 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
this->symbols.push_back(&sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::mark_addrsig(Context<E> &ctx) {
|
||||
// Parse a .llvm_addrsig section.
|
||||
if (llvm_addrsig) {
|
||||
u8 *cur = (u8 *)llvm_addrsig->contents.data();
|
||||
u8 *end = cur + llvm_addrsig->contents.size();
|
||||
|
||||
while (cur != end) {
|
||||
Symbol<E> &sym = *this->symbols[read_uleb(cur)];
|
||||
if (sym.file == this)
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
isec->address_significant = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We treat a symbol's address as significant if
|
||||
//
|
||||
// 1. we have no address significance information for the symbol, or
|
||||
// 2. the symbol can be referenced from the outside in an address-
|
||||
// significant manner.
|
||||
for (Symbol<E> *sym : this->symbols)
|
||||
if (sym->file == this)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (!llvm_addrsig || sym->is_exported)
|
||||
isec->address_significant = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
sections.resize(this->elf_sections.size());
|
||||
@@ -872,7 +867,7 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
initialize_sections(ctx);
|
||||
initialize_symbols(ctx);
|
||||
sort_relocations(ctx);
|
||||
initialize_ehframe_sections(ctx);
|
||||
parse_ehframe(ctx);
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
@@ -1028,6 +1023,11 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (ElfRel<E> &rel : cie.get_rels()) {
|
||||
Symbol<E> &sym = *this->symbols[rel.r_sym];
|
||||
|
||||
if (ctx.arg.pic && rel.r_type == E::R_ABS)
|
||||
Error(ctx) << *this << ": relocation " << rel << " in .eh_frame can"
|
||||
<< " not be used when making a position-independent output;"
|
||||
<< " recompile with -fPIE or -fPIC";
|
||||
|
||||
if (sym.is_imported) {
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
Fatal(ctx) << *this << ": " << sym
|
||||
@@ -1079,13 +1079,9 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
|
||||
std::string_view name;
|
||||
|
||||
if (sym.get_type() == STT_TLS) {
|
||||
name = ".tls_common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_TLS;
|
||||
} else {
|
||||
name = ".common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
}
|
||||
|
||||
@@ -1095,7 +1091,7 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, name, idx);
|
||||
std::make_unique<InputSection<E>>(ctx, *this, idx);
|
||||
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec.get());
|
||||
@@ -1139,9 +1135,6 @@ void ObjectFile<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
this->output_sym_indices.resize(this->elf_syms.size(), -1);
|
||||
|
||||
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())
|
||||
@@ -1187,30 +1180,29 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
i64 strtab_off = this->strtab_offset;
|
||||
|
||||
auto write_sym = [&](Symbol<E> &sym, i64 &symtab_idx) {
|
||||
auto write_sym = [&](Symbol<E> &sym, i64 idx) {
|
||||
U32<E> *xindex = nullptr;
|
||||
if (ctx.symtab_shndx)
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + symtab_idx;
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + idx;
|
||||
|
||||
symtab_base[symtab_idx++] = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
symtab_base[idx] = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab_base + strtab_off, sym.name());
|
||||
};
|
||||
|
||||
i64 local_symtab_idx = this->local_symtab_idx;
|
||||
i64 global_symtab_idx = this->global_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, local_symtab_idx);
|
||||
}
|
||||
|
||||
for (i64 i = 1; i < this->first_global; i++)
|
||||
if (Symbol<E> &sym = *this->symbols[i]; sym.write_to_symtab)
|
||||
write_sym(sym, local_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) {
|
||||
if (sym.is_local(ctx))
|
||||
write_sym(sym, local_symtab_idx);
|
||||
write_sym(sym, local_symtab_idx++);
|
||||
else
|
||||
write_sym(sym, global_symtab_idx);
|
||||
write_sym(sym, global_symtab_idx++);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1241,7 +1233,6 @@ SharedFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
template <typename E>
|
||||
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
: InputFile<E>(ctx, mf) {
|
||||
this->is_needed = ctx.as_needed;
|
||||
this->is_alive = !ctx.as_needed;
|
||||
}
|
||||
|
||||
@@ -1396,7 +1387,8 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
if (sym.is_traced)
|
||||
print_trace_symbol(ctx, *this, esym, sym);
|
||||
|
||||
if (esym.is_undef() && sym.file && !sym.file->is_alive.test_and_set()) {
|
||||
if (esym.is_undef() && !esym.is_weak() && sym.file && !sym.file->is_dso &&
|
||||
!sym.file->is_alive.test_and_set()) {
|
||||
feeder(sym.file);
|
||||
|
||||
if (sym.is_traced)
|
||||
@@ -1452,7 +1444,8 @@ bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
|
||||
u64 val = sym->esym().st_value;
|
||||
|
||||
for (ElfPhdr<E> &phdr : this->get_phdrs())
|
||||
if (phdr.p_type == PT_LOAD && !(phdr.p_flags & PF_W) &&
|
||||
if ((phdr.p_type == PT_LOAD || phdr.p_type == PT_GNU_RELRO) &&
|
||||
!(phdr.p_flags & PF_W) &&
|
||||
phdr.p_vaddr <= val && val < phdr.p_vaddr + phdr.p_memsz)
|
||||
return true;
|
||||
return false;
|
||||
|
||||
+108
-50
@@ -7,7 +7,8 @@
|
||||
namespace mold::elf {
|
||||
|
||||
typedef enum {
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL, BASEREL, IFUNC,
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL,
|
||||
BASEREL, IFUNC_DYNREL,
|
||||
} Action;
|
||||
|
||||
template <typename E>
|
||||
@@ -37,8 +38,7 @@ static i64 to_p2align(u64 alignment) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
@@ -107,30 +107,6 @@ void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_rel_action(Context<E> &ctx, Symbol<E> &sym,
|
||||
const Action table[3][4]) {
|
||||
auto get_output_type = [&] {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto get_sym_type = [&] {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
};
|
||||
|
||||
return table[get_output_type()][get_sym_type()];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, Action action) {
|
||||
@@ -174,40 +150,67 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR:
|
||||
// Print out the "recompile with -fPIC" error message.
|
||||
error();
|
||||
break;
|
||||
case COPYREL:
|
||||
// Create a copy relocation.
|
||||
if (!ctx.arg.z_copyreloc)
|
||||
error();
|
||||
copyrel();
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
// Same as COPYREL but try to avoid creating a copy relocation by
|
||||
// creating a dynamic relocation instead if the relocation is in
|
||||
// a writable section.
|
||||
//
|
||||
// GHC (Glasgow Haskell Compiler) places a small amount of data in
|
||||
// .text before each function and access that data with a fixed
|
||||
// offset. The function breaks if we copy-relocate the data. For such
|
||||
// programs, we should avoid copy relocations if possible.
|
||||
//
|
||||
// Besides GHC, copy relocation is a hacky solution, so if we can
|
||||
// represent a relocation either with copyrel or dynrel, we prefer
|
||||
// dynamic relocation.
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
dynrel();
|
||||
else
|
||||
copyrel();
|
||||
break;
|
||||
case PLT:
|
||||
// Create a PLT entry.
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case CPLT:
|
||||
// Create a canonical PLT entry.
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
break;
|
||||
case DYN_CPLT:
|
||||
// Same as CPLT but try to avoid creating a canonical PLT creating by
|
||||
// creating a dynamic relocation instead if the relocation is in a
|
||||
// writable section. The motivation behind it is hte same as DYN_COPYREL.
|
||||
if (writable)
|
||||
dynrel();
|
||||
else
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
break;
|
||||
case DYNREL:
|
||||
// Create a dynamic relocation.
|
||||
dynrel();
|
||||
break;
|
||||
case BASEREL:
|
||||
// Create a base relocation.
|
||||
check_textrel();
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
isec.file.num_dynrel++;
|
||||
break;
|
||||
case IFUNC:
|
||||
case IFUNC_DYNREL:
|
||||
// Create an IRELATIVE relocation for a GNU ifunc symbol.
|
||||
//
|
||||
// We usually create an IRELATIVE relocation in .got for each ifunc.
|
||||
// However, if a statically-initialized pointer is initialized to an
|
||||
// ifunc's address, we have no choice other than emitting an IRELATIVE
|
||||
// relocation for each such pointer.
|
||||
dynrel();
|
||||
ctx.num_ifunc_dynrels++;
|
||||
break;
|
||||
@@ -216,73 +219,93 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static inline i64 get_output_type(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static inline i64 get_sym_type(Symbol<E> &sym) {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
|
||||
// We cannot promote them to dynamic relocations because the dynamic
|
||||
// linker generally does not support PC-relative relocations.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, PLT }, // Shared object
|
||||
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is a decision table for absolute relocations that is smaller
|
||||
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// than the pointer size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// generally does not support dynamic relocations smaller than the
|
||||
// word size, we need to report an error if a relocation cannot be
|
||||
// pointer size, we need to report an error if a relocation cannot be
|
||||
// resolved at link-time.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Shared object
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
return ctx.arg.pic ? IFUNC_DYNREL : NONE;
|
||||
|
||||
// This is a decision table for absolute relocations for the word
|
||||
// This is a decision table for absolute relocations for the pointer
|
||||
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
|
||||
// a dynamic relocation if we cannot resolve an address at link-time.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYN_COPYREL, DYN_CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
return IFUNC_DYNREL;
|
||||
|
||||
// As a special case, we do not create copy relocations nor canonical
|
||||
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
|
||||
// GOT-like section, and no user-generated code directly uses values
|
||||
// in it.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -309,6 +332,29 @@ void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
|
||||
// Relax TLSDESC to Local Exec. In this case, we directly materialize
|
||||
// a TP-relative offset, so no dynamic relocation is needed.
|
||||
//
|
||||
// TLSDESC relocs must always be relaxed for statically-linked
|
||||
// executables even if -no-relax is given. It is because a
|
||||
// statically-linked executable doesn't contain a trampoline
|
||||
// function needed for TLSDESC.
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
// In this condition, TP-relative offset of a thread-local variable
|
||||
// is known at process startup time, so we can relax TLSDESC to the
|
||||
// code that reads the TP-relative offset from GOT and add TP to it.
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
// If no relaxation is doable, we simply create a TLSDESC dynamic
|
||||
// relocation.
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::check_tlsle(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
@@ -325,7 +371,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
Action action) {
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto apply_dynrel = [&] {
|
||||
auto emit_abs_dynrel = [&] {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = A;
|
||||
@@ -348,20 +394,20 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
apply_dynrel();
|
||||
emit_abs_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYN_CPLT:
|
||||
if (writable)
|
||||
apply_dynrel();
|
||||
emit_abs_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYNREL:
|
||||
apply_dynrel();
|
||||
emit_abs_dynrel();
|
||||
break;
|
||||
case IFUNC:
|
||||
case IFUNC_DYNREL:
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
@@ -380,8 +426,8 @@ template <typename E>
|
||||
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> *&dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
|
||||
ElfRel<E> **dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
|
||||
get_dyn_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
@@ -389,8 +435,8 @@ template <typename E>
|
||||
void InputSection<E>::apply_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> *&dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
|
||||
ElfRel<E> **dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
|
||||
get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
@@ -411,6 +457,18 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
|
||||
if constexpr (is_x86_64<E>) {
|
||||
u8 endbr[] = {0xf3, 0x0f, 0x1e, 0xfa};
|
||||
u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
|
||||
|
||||
// Rewrite the leading endbr instruction with a nop if the section
|
||||
// is not address-taken.
|
||||
if (ctx.arg.z_rewrite_endbr && (shdr().sh_flags & SHF_EXECINSTR) &&
|
||||
!address_taken && sh_size >= 4 && memcmp(buf, endbr, 4) == 0) {
|
||||
memcpy(buf, nop, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-5
@@ -1,3 +1,14 @@
|
||||
// Many build systems attempt to invoke as many linker processes as there
|
||||
// are cores, based on the assumption that the linker is single-threaded.
|
||||
// However, since mold is multi-threaded, such build systems' behavior is
|
||||
// not beneficial and just increases the overall peak memory usage.
|
||||
// On machines with limited memory, this could lead to an out-of-memory
|
||||
// error.
|
||||
//
|
||||
// This file implements a feature that limits the number of concurrent
|
||||
// mold processes to just 1 for each user. It is intended to be used as
|
||||
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -14,14 +25,15 @@ template <typename E>
|
||||
void acquire_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || std::string(jobs) != "1")
|
||||
if (!jobs || jobs != "1"s)
|
||||
return;
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (!home)
|
||||
home = getpwuid(getuid())->pw_dir;
|
||||
std::string path;
|
||||
if (char *dir = getenv("XDG_RUNTIME_DIR"))
|
||||
path = dir + "/mold-lock"s;
|
||||
else
|
||||
path = "/tmp/mold-lock-"s + getpwuid(getuid())->pw_name;
|
||||
|
||||
std::string path = std::string(home) + "/.mold-lock";
|
||||
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
|
||||
if (fd == -1)
|
||||
return;
|
||||
|
||||
@@ -279,9 +279,8 @@ static bool read_label(std::span<std::string_view> &tok,
|
||||
template <typename E>
|
||||
static void
|
||||
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
std::string_view ver_str, u16 ver_idx, bool is_cpp) {
|
||||
bool is_global = true;
|
||||
|
||||
std::string_view ver_str, u16 ver_idx,
|
||||
bool is_global, bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (read_label(tok, "global")) {
|
||||
is_global = true;
|
||||
@@ -299,11 +298,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands( ctx, tok, ver_str, ver_idx, false);
|
||||
read_version_script_commands( ctx, tok, ver_str, ver_idx, is_global, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, is_global, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
@@ -349,7 +348,7 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, false);
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, true, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
if (!tok.empty() && tok[0] != ";")
|
||||
tok = tok.subspan(1);
|
||||
|
||||
@@ -709,6 +709,9 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
get_symbol(ctx, y)->referenced_by_regular_obj = true;
|
||||
}
|
||||
|
||||
// Handle --entry
|
||||
get_symbol(ctx, ctx.arg.entry)->referenced_by_regular_obj = true;
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -55,9 +55,7 @@ enum PluginApiVersion {
|
||||
|
||||
struct PluginTagValue {
|
||||
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
|
||||
|
||||
template <typename T>
|
||||
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
PluginTagValue(PluginTag tag, auto *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
|
||||
PluginTag tag;
|
||||
union {
|
||||
|
||||
+16
-90
@@ -70,10 +70,8 @@ std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return SH4::target_name;
|
||||
case EM_ALPHA:
|
||||
return ALPHA::target_name;
|
||||
case EM_MIPS:
|
||||
if (is_64)
|
||||
return is_le ? MIPS64LE::target_name : MIPS64BE::target_name;
|
||||
return "";
|
||||
case EM_LOONGARCH:
|
||||
return is_64 ? LOONGARCH64::target_name : LOONGARCH32::target_name;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -193,7 +191,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
if (!ctx.whole_archive)
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
@@ -340,48 +339,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
template <typename E>
|
||||
static int redo_main(int argc, char **argv, std::string_view target) {
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == MIPS64LE::target_name)
|
||||
return elf_main<MIPS64LE>(argc, argv);
|
||||
if (target == MIPS64BE::target_name)
|
||||
return elf_main<MIPS64BE>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
@@ -405,7 +362,7 @@ int elf_main(int argc, char **argv) {
|
||||
// Redo if -m is not x86-64.
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.emulation != X86_64::target_name)
|
||||
return redo_main<E>(argc, argv, ctx.arg.emulation);
|
||||
return redo_main(ctx, argc, argv);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
@@ -452,7 +409,6 @@ int elf_main(int argc, char **argv) {
|
||||
});
|
||||
}
|
||||
|
||||
Timer t_total(ctx, "total");
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
@@ -500,9 +456,9 @@ int elf_main(int argc, char **argv) {
|
||||
// Set is_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(ctx);
|
||||
// Set "address-taken" bits for input sections.
|
||||
if (ctx.arg.icf || ctx.arg.z_rewrite_endbr)
|
||||
compute_address_significance(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
@@ -641,10 +597,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
@@ -690,12 +642,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
@@ -703,6 +649,12 @@ int elf_main(int argc, char **argv) {
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
// entire output file. Now that we wrote everything except build-id,
|
||||
// we can compute it.
|
||||
@@ -722,7 +674,6 @@ int elf_main(int argc, char **argv) {
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
@@ -751,39 +702,14 @@ int elf_main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
template MappedFile<Context<E>> *open_library(Context<E> &, std::string);
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
|
||||
extern template int elf_main<I386>(int, char **);
|
||||
extern template int elf_main<ARM32>(int, char **);
|
||||
extern template int elf_main<ARM64>(int, char **);
|
||||
extern template int elf_main<RV32BE>(int, char **);
|
||||
extern template int elf_main<RV32LE>(int, char **);
|
||||
extern template int elf_main<RV64LE>(int, char **);
|
||||
extern template int elf_main<RV64BE>(int, char **);
|
||||
extern template int elf_main<PPC32>(int, char **);
|
||||
extern template int elf_main<PPC64V1>(int, char **);
|
||||
extern template int elf_main<PPC64V2>(int, char **);
|
||||
extern template int elf_main<S390X>(int, char **);
|
||||
extern template int elf_main<SPARC64>(int, char **);
|
||||
extern template int elf_main<M68K>(int, char **);
|
||||
extern template int elf_main<SH4>(int, char **);
|
||||
extern template int elf_main<ALPHA>(int, char **);
|
||||
extern template int elf_main<MIPS64LE>(int, char **);
|
||||
extern template int elf_main<MIPS64BE>(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
+198
-229
@@ -36,8 +36,6 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static constexpr i32 SHA256_SIZE = 32;
|
||||
|
||||
template <typename E> class InputFile;
|
||||
template <typename E> class InputSection;
|
||||
template <typename E> class MergedSection;
|
||||
@@ -99,11 +97,11 @@ struct SymbolAux<PPC64V1> : SymbolAux<X86_64> {
|
||||
template <typename E>
|
||||
class RangeExtensionThunk {};
|
||||
|
||||
template <typename E> requires needs_thunk<E>
|
||||
template <needs_thunk E>
|
||||
class RangeExtensionThunk<E> {
|
||||
public:
|
||||
RangeExtensionThunk(OutputSection<E> &osec, i64 thunk_idx, i64 offset)
|
||||
: output_section(osec), thunk_idx(thunk_idx), offset(offset) {}
|
||||
RangeExtensionThunk(OutputSection<E> &osec, i64 offset)
|
||||
: output_section(osec), offset(offset) {}
|
||||
|
||||
i64 size() const { return E::thunk_hdr_size + symbols.size() * E::thunk_size; }
|
||||
void copy_buf(Context<E> &ctx);
|
||||
@@ -113,10 +111,9 @@ public:
|
||||
idx * E::thunk_size;
|
||||
}
|
||||
|
||||
static constexpr i64 alignment = 4;
|
||||
static constexpr i64 alignment = 16;
|
||||
|
||||
OutputSection<E> &output_section;
|
||||
i64 thunk_idx;
|
||||
i64 offset;
|
||||
std::mutex mu;
|
||||
std::vector<Symbol<E> *> symbols;
|
||||
@@ -127,9 +124,6 @@ struct RangeExtensionRef {
|
||||
i16 sym_idx = -1;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec);
|
||||
|
||||
//
|
||||
// input-sections.cc
|
||||
//
|
||||
@@ -216,12 +210,12 @@ struct FdeRecord {
|
||||
template <typename E>
|
||||
struct InputSectionExtras {};
|
||||
|
||||
template <typename E> requires needs_thunk<E>
|
||||
template <needs_thunk E>
|
||||
struct InputSectionExtras<E> {
|
||||
std::vector<RangeExtensionRef> range_extn;
|
||||
};
|
||||
|
||||
template <typename E> requires is_riscv<E>
|
||||
template <is_riscv E>
|
||||
struct InputSectionExtras<E> {
|
||||
std::vector<i32> r_deltas;
|
||||
};
|
||||
@@ -230,8 +224,7 @@ struct InputSectionExtras<E> {
|
||||
template <typename E>
|
||||
class InputSection {
|
||||
public:
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, std::string_view name,
|
||||
i64 shndx);
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx);
|
||||
|
||||
void uncompress(Context<E> &ctx);
|
||||
void uncompress_to(Context<E> &ctx, u8 *buf);
|
||||
@@ -264,17 +257,19 @@ public:
|
||||
i32 fde_begin = -1;
|
||||
i32 fde_end = -1;
|
||||
|
||||
u32 offset = -1;
|
||||
u64 offset = -1;
|
||||
u32 shndx = -1;
|
||||
u32 relsec_idx = -1;
|
||||
u32 reldyn_offset = 0;
|
||||
|
||||
bool uncompressed = false;
|
||||
|
||||
// For COMDAT de-duplication and garbage collection
|
||||
std::atomic_bool is_alive = true;
|
||||
u8 p2align = 0;
|
||||
|
||||
bool address_significant : 1 = false;
|
||||
bool uncompressed : 1 = false;
|
||||
// For ICF
|
||||
Atomic<bool> address_taken = false;
|
||||
|
||||
// For garbage collection
|
||||
Atomic<bool> is_visited = false;
|
||||
@@ -296,14 +291,14 @@ private:
|
||||
void scan_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
|
||||
void scan_dyn_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
|
||||
void scan_toc_rel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
|
||||
|
||||
void scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym);
|
||||
void check_tlsle(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
|
||||
|
||||
void apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel,
|
||||
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> *&dynrel);
|
||||
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> **dynrel);
|
||||
|
||||
void apply_toc_rel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel,
|
||||
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> *&dynrel);
|
||||
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> **dynrel);
|
||||
|
||||
void copy_contents_riscv(Context<E> &ctx, u8 *buf);
|
||||
|
||||
@@ -319,9 +314,9 @@ private:
|
||||
// tls.cc
|
||||
//
|
||||
|
||||
template<typename E> u64 get_tls_begin(Context<E> &);
|
||||
template<typename E> u64 get_tp_addr(Context<E> &);
|
||||
template<typename E> u64 get_dtp_addr(Context<E> &);
|
||||
template <typename E> u64 get_tls_begin(Context<E> &);
|
||||
template <typename E> u64 get_tp_addr(Context<E> &);
|
||||
template <typename E> u64 get_dtp_addr(Context<E> &);
|
||||
|
||||
//
|
||||
// output-chunks.cc
|
||||
@@ -333,9 +328,6 @@ u64 get_eflags(Context<E> &ctx);
|
||||
template <typename E>
|
||||
i64 to_phdr_flags(Context<E> &ctx, Chunk<E> *chunk);
|
||||
|
||||
template <typename E>
|
||||
bool is_relro(Context<E> &ctx, Chunk<E> *chunk);
|
||||
|
||||
template <typename E>
|
||||
std::string_view get_output_name(Context<E> &ctx, std::string_view name, u64 flags);
|
||||
|
||||
@@ -358,16 +350,19 @@ public:
|
||||
virtual ChunkKind kind() { return SYNTHETIC; }
|
||||
virtual OutputSection<E> *to_osec() { return nullptr; }
|
||||
virtual i64 get_reldyn_size(Context<E> &ctx) const { return 0; }
|
||||
virtual void construct_relr(Context<E> &ctx) {}
|
||||
virtual void copy_buf(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
|
||||
// For --gdb-index
|
||||
virtual u8 *get_uncompressed_data() { return nullptr; }
|
||||
|
||||
std::string_view name;
|
||||
ElfShdr<E> shdr = { .sh_addralign = 1 };
|
||||
i64 shndx = 0;
|
||||
bool is_relro = false;
|
||||
|
||||
// For --gdb-index
|
||||
bool is_compressed = false;
|
||||
std::vector<u8> uncompressed_data;
|
||||
|
||||
// Some synethetic sections add local symbols to the output.
|
||||
// For example, range extension thunks adds function_name@thunk
|
||||
@@ -386,6 +381,9 @@ public:
|
||||
|
||||
// For --section-order
|
||||
i64 sect_order = 0;
|
||||
|
||||
// For --pack-dyn-relocs=relr
|
||||
std::vector<u64> relr;
|
||||
};
|
||||
|
||||
// ELF header
|
||||
@@ -451,25 +449,19 @@ public:
|
||||
template <typename E>
|
||||
class OutputSection : public Chunk<E> {
|
||||
public:
|
||||
OutputSection(std::string_view name, u32 type, u64 flags) {
|
||||
this->name = name;
|
||||
this->shdr.sh_type = type;
|
||||
this->shdr.sh_flags = flags;
|
||||
}
|
||||
|
||||
OutputSection(Context<E> &ctx, std::string_view name, u32 type, u64 flags);
|
||||
ChunkKind kind() override { return OUTPUT_SECTION; }
|
||||
OutputSection<E> *to_osec() override { return this; }
|
||||
void construct_relr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx, u8 *buf) override;
|
||||
|
||||
void compute_symtab_size(Context<E> &ctx) override;
|
||||
void populate_symtab(Context<E> &ctx) override;
|
||||
|
||||
void create_range_extension_thunks(Context<E> &ctx);
|
||||
|
||||
std::vector<InputSection<E> *> members;
|
||||
|
||||
void construct_relr(Context<E> &ctx);
|
||||
std::vector<u64> relr;
|
||||
|
||||
std::vector<std::unique_ptr<RangeExtensionThunk<E>>> thunks;
|
||||
std::unique_ptr<RelocSection<E>> reloc_sec;
|
||||
};
|
||||
@@ -479,15 +471,15 @@ class GotSection : public Chunk<E> {
|
||||
public:
|
||||
GotSection() {
|
||||
this->name = ".got";
|
||||
this->is_relro = true;
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
|
||||
// We always create a .got so that _GLOBAL_OFFSET_TABLE_ has
|
||||
// something to point to. s390x/MIPS psABIs define GOT[1] as a
|
||||
// something to point to. s390x psABI define GOT[1] as a
|
||||
// reserved slot, so we allocate one more for them.
|
||||
i64 n = (is_s390x<E> || is_mips<E>) ? 2 : 1;
|
||||
this->shdr.sh_size = n * sizeof(Word<E>);
|
||||
this->shdr.sh_size = (is_s390x<E> ? 2 : 1) * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void add_got_symbol(Context<E> &ctx, Symbol<E> *sym);
|
||||
@@ -501,6 +493,7 @@ public:
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
void construct_relr(Context<E> &ctx) override;
|
||||
void compute_symtab_size(Context<E> &ctx) override;
|
||||
void populate_symtab(Context<E> &ctx) override;
|
||||
|
||||
@@ -509,16 +502,14 @@ public:
|
||||
std::vector<Symbol<E> *> tlsdesc_syms;
|
||||
std::vector<Symbol<E> *> gottp_syms;
|
||||
u32 tlsld_idx = -1;
|
||||
|
||||
void construct_relr(Context<E> &ctx);
|
||||
std::vector<u64> relr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class GotPltSection : public Chunk<E> {
|
||||
public:
|
||||
GotPltSection() {
|
||||
GotPltSection(Context<E> &ctx) {
|
||||
this->name = ".got.plt";
|
||||
this->is_relro = ctx.arg.z_now;
|
||||
this->shdr.sh_type = is_ppc64<E> ? SHT_NOBITS : SHT_PROGBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
@@ -631,6 +622,12 @@ public:
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
// Offsets in .strtab for ARM32 mapping symbols
|
||||
static constexpr i64 ARM = 1;
|
||||
static constexpr i64 THUMB = 4;
|
||||
static constexpr i64 DATA = 7;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -654,7 +651,6 @@ public:
|
||||
this->shdr.sh_flags = SHF_ALLOC;
|
||||
}
|
||||
|
||||
void keep() { this->shdr.sh_size = 1; }
|
||||
i64 add_string(std::string_view str);
|
||||
i64 find_string(std::string_view str);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
@@ -670,6 +666,7 @@ class DynamicSection : public Chunk<E> {
|
||||
public:
|
||||
DynamicSection() {
|
||||
this->name = ".dynamic";
|
||||
this->is_relro = true;
|
||||
this->shdr.sh_type = SHT_DYNAMIC;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
@@ -680,7 +677,7 @@ public:
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template<typename E>
|
||||
template <typename E>
|
||||
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
U32<E> *shndx);
|
||||
|
||||
@@ -720,13 +717,13 @@ public:
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void keep() { this->symbols.resize(1); }
|
||||
void add_symbol(Context<E> &ctx, Symbol<E> *sym);
|
||||
void finalize(Context<E> &ctx);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> symbols;
|
||||
bool finalized = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -770,7 +767,8 @@ template <typename E>
|
||||
class MergedSection : public Chunk<E> {
|
||||
public:
|
||||
static MergedSection<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view name, u64 type, u64 flags);
|
||||
get_instance(Context<E> &ctx, std::string_view name, i64 type, i64 flags,
|
||||
i64 entsize);
|
||||
|
||||
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
|
||||
u64 hash, i64 p2align);
|
||||
@@ -783,7 +781,7 @@ public:
|
||||
HyperLogLog estimator;
|
||||
|
||||
private:
|
||||
MergedSection(std::string_view name, u64 flags, u32 type);
|
||||
MergedSection(std::string_view name, i64 flags, i64 type, i64 entsize);
|
||||
|
||||
ConcurrentMap<SectionFragment<E>> map;
|
||||
std::vector<i64> shard_offsets;
|
||||
@@ -801,7 +799,7 @@ public:
|
||||
}
|
||||
|
||||
void construct(Context<E> &ctx);
|
||||
void apply_reloc(Context<E> &ctx, const ElfRel<E> &rel, u64 offset, u64 val);
|
||||
void apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel, u64 offset, u64 val);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
@@ -842,8 +840,9 @@ public:
|
||||
template <typename E>
|
||||
class CopyrelSection : public Chunk<E> {
|
||||
public:
|
||||
CopyrelSection(bool is_relro) : is_relro(is_relro) {
|
||||
CopyrelSection(bool is_relro) {
|
||||
this->name = is_relro ? ".copyrel.rel.ro" : ".copyrel";
|
||||
this->is_relro = is_relro;
|
||||
this->shdr.sh_type = SHT_NOBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
}
|
||||
@@ -853,7 +852,6 @@ public:
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override { return symbols.size(); }
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
bool is_relro;
|
||||
std::vector<Symbol<E> *> symbols;
|
||||
};
|
||||
|
||||
@@ -959,13 +957,6 @@ private:
|
||||
std::map<u32, u32> properties;
|
||||
};
|
||||
|
||||
struct GdbIndexName {
|
||||
std::string_view name;
|
||||
u32 hash = 0;
|
||||
u32 attr = 0;
|
||||
u32 entry_idx = 0;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class GdbIndexSection : public Chunk<E> {
|
||||
public:
|
||||
@@ -974,38 +965,6 @@ public:
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_addralign = 4;
|
||||
}
|
||||
|
||||
void construct(Context<E> &ctx);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
void write_address_areas(Context<E> &ctx);
|
||||
|
||||
private:
|
||||
struct SectionHeader {
|
||||
ul32 version = 7;
|
||||
ul32 cu_list_offset = 0;
|
||||
ul32 cu_types_offset = 0;
|
||||
ul32 areas_offset = 0;
|
||||
ul32 symtab_offset = 0;
|
||||
ul32 const_pool_offset = 0;
|
||||
};
|
||||
|
||||
struct MapEntry {
|
||||
MapEntry(ObjectFile<E> *owner, u32 hash) : owner(owner), hash(hash) {}
|
||||
|
||||
MapEntry(const MapEntry &other)
|
||||
: owner(other.owner.load()), num_attrs(other.num_attrs.load()),
|
||||
hash(other.hash), name_offset(other.name_offset),
|
||||
attr_offset(other.attr_offset) {}
|
||||
|
||||
std::atomic<ObjectFile<E> *> owner;
|
||||
std::atomic_uint32_t num_attrs = 0;
|
||||
u32 hash = 0;
|
||||
u32 name_offset = -1;
|
||||
u32 attr_offset = -1;
|
||||
};
|
||||
|
||||
SectionHeader header;
|
||||
ConcurrentMap<MapEntry> map;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -1013,12 +972,10 @@ class CompressedSection : public Chunk<E> {
|
||||
public:
|
||||
CompressedSection(Context<E> &ctx, Chunk<E> &chunk);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
u8 *get_uncompressed_data() override { return uncompressed.get(); }
|
||||
|
||||
private:
|
||||
ElfChdr<E> chdr = {};
|
||||
std::unique_ptr<Compressor> compressed;
|
||||
std::unique_ptr<u8[]> uncompressed;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -1041,6 +998,7 @@ class RelroPaddingSection : public Chunk<E> {
|
||||
public:
|
||||
RelroPaddingSection() {
|
||||
this->name = ".relro_padding";
|
||||
this->is_relro = true;
|
||||
this->shdr.sh_type = SHT_NOBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
this->shdr.sh_addralign = 1;
|
||||
@@ -1069,22 +1027,10 @@ private:
|
||||
};
|
||||
|
||||
//
|
||||
// dwarf.cc
|
||||
// gdb-index.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset);
|
||||
template <typename E> void write_gdb_index(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// input-files.cc
|
||||
@@ -1137,11 +1083,11 @@ public:
|
||||
|
||||
virtual ~InputFile() = default;
|
||||
|
||||
template<typename T> std::span<T>
|
||||
get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
template <typename T>
|
||||
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
|
||||
template<typename T> std::span<T>
|
||||
get_data(Context<E> &ctx, i64 idx);
|
||||
template <typename T>
|
||||
std::span<T> get_data(Context<E> &ctx, i64 idx);
|
||||
|
||||
std::string_view get_string(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
std::string_view get_string(Context<E> &ctx, i64 idx);
|
||||
@@ -1190,6 +1136,20 @@ protected:
|
||||
std::vector<Symbol<E>> frag_syms;
|
||||
};
|
||||
|
||||
template <typename E> struct ObjectFileExtras {};
|
||||
|
||||
template <is_riscv E>
|
||||
struct ObjectFileExtras<E> {
|
||||
std::optional<i64> stack_align;
|
||||
std::optional<std::string_view> arch;
|
||||
bool unaligned_access = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ObjectFileExtras<PPC32> {
|
||||
InputSection<PPC32> *got2 = nullptr;
|
||||
};
|
||||
|
||||
// ObjectFile represents an input .o file.
|
||||
template <typename E>
|
||||
class ObjectFile : public InputFile<E> {
|
||||
@@ -1206,7 +1166,6 @@ public:
|
||||
void mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(InputFile<E> *)> feeder) override;
|
||||
void convert_undefined_weak_symbols(Context<E> &ctx);
|
||||
void mark_addrsig(Context<E> &ctx);
|
||||
void scan_relocations(Context<E> &ctx);
|
||||
void convert_common_symbols(Context<E> &ctx);
|
||||
void compute_symtab_size(Context<E> &ctx);
|
||||
@@ -1224,6 +1183,7 @@ public:
|
||||
std::vector<FdeRecord<E>> fdes;
|
||||
BitVector has_symver;
|
||||
std::vector<ComdatGroupRef<E>> comdat_groups;
|
||||
InputSection<E> *eh_frame_section = nullptr;
|
||||
bool exclude_libs = false;
|
||||
std::map<u32, u32> gnu_properties;
|
||||
bool is_lto_obj = false;
|
||||
@@ -1241,28 +1201,14 @@ public:
|
||||
|
||||
// For .gdb_index
|
||||
InputSection<E> *debug_info = nullptr;
|
||||
InputSection<E> *debug_ranges = nullptr;
|
||||
InputSection<E> *debug_rnglists = nullptr;
|
||||
InputSection<E> *debug_pubnames = nullptr;
|
||||
InputSection<E> *debug_pubtypes = nullptr;
|
||||
std::vector<std::string_view> compunits;
|
||||
std::vector<GdbIndexName> gdb_names;
|
||||
i64 compunits_idx = 0;
|
||||
i64 attrs_size = 0;
|
||||
i64 attrs_offset = 0;
|
||||
i64 names_size = 0;
|
||||
i64 names_offset = 0;
|
||||
i64 num_areas = 0;
|
||||
i64 area_offset = 0;
|
||||
|
||||
// For LTO
|
||||
std::vector<std::string_view> lto_symbol_versions;
|
||||
|
||||
// For PPC32
|
||||
InputSection<E> *ppc32_got2 = nullptr;
|
||||
|
||||
// For MIPS
|
||||
u64 mips_gp0 = 0;
|
||||
// Target-specific member
|
||||
[[no_unique_address]] ObjectFileExtras<E> extra;
|
||||
|
||||
private:
|
||||
ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
@@ -1272,8 +1218,8 @@ private:
|
||||
void initialize_symbols(Context<E> &ctx);
|
||||
void sort_relocations(Context<E> &ctx);
|
||||
void initialize_ehframe_sections(Context<E> &ctx);
|
||||
void read_note_gnu_property(Context <E> &ctx, const ElfShdr <E> &shdr);
|
||||
void read_ehframe(Context<E> &ctx, InputSection<E> &isec);
|
||||
void parse_note_gnu_property(Context <E> &ctx, const ElfShdr <E> &shdr);
|
||||
void parse_ehframe(Context<E> &ctx);
|
||||
void override_symbol(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfSym<E> &esym, i64 symidx);
|
||||
void merge_visibility(Context<E> &ctx, Symbol<E> &sym, u8 visibility);
|
||||
@@ -1302,7 +1248,6 @@ public:
|
||||
void compute_symtab_size(Context<E> &ctx);
|
||||
void populate_symtab(Context<E> &ctx);
|
||||
|
||||
bool is_needed = false;
|
||||
std::string soname;
|
||||
std::vector<std::string_view> version_strings;
|
||||
std::vector<ElfSym<E>> elf_syms2;
|
||||
@@ -1398,7 +1343,7 @@ template <typename E> void acquire_global_lock(Context<E> &ctx);
|
||||
template <typename E> void release_global_lock(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// commandline.cc
|
||||
// cmdline.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
@@ -1408,6 +1353,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
|
||||
// passes.cc
|
||||
//
|
||||
|
||||
template <typename E> int redo_main(Context<E> &, int argc, char **argv);
|
||||
template <typename E> void create_internal_file(Context<E> &);
|
||||
template <typename E> void apply_exclude_libs(Context<E> &);
|
||||
template <typename E> void create_synthetic_sections(Context<E> &);
|
||||
@@ -1439,7 +1385,7 @@ template <typename E> void copy_chunks(Context<E> &);
|
||||
template <typename E> void apply_version_script(Context<E> &);
|
||||
template <typename E> void parse_symbol_version(Context<E> &);
|
||||
template <typename E> void compute_import_export(Context<E> &);
|
||||
template <typename E> void mark_addrsig(Context<E> &);
|
||||
template <typename E> void compute_address_significance(Context<E> &);
|
||||
template <typename E> void clear_padding(Context<E> &);
|
||||
template <typename E> void compute_section_headers(Context<E> &);
|
||||
template <typename E> i64 set_osec_offsets(Context<E> &);
|
||||
@@ -1452,13 +1398,31 @@ template <typename E> void show_stats(Context<E> &);
|
||||
// arch-arm32.cc
|
||||
//
|
||||
|
||||
template <> u64 get_eflags(Context<ARM32> &ctx);
|
||||
void fixup_arm_exidx_section(Context<ARM32> &ctx);
|
||||
|
||||
//
|
||||
// arch-riscv64.cc
|
||||
// arch-riscv.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
template <is_riscv E>
|
||||
class RiscvAttributesSection : public Chunk<E> {
|
||||
public:
|
||||
RiscvAttributesSection() {
|
||||
this->name = ".riscv.attributes";
|
||||
this->shdr.sh_type = SHT_RISCV_ATTRIBUTES;
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
template <is_riscv E>
|
||||
u64 get_eflags(Context<E> &ctx);
|
||||
|
||||
template <is_riscv E>
|
||||
i64 riscv_resize_sections(Context<E> &ctx);
|
||||
|
||||
//
|
||||
@@ -1486,6 +1450,12 @@ public:
|
||||
std::vector<Symbol<PPC64V1> *> symbols;
|
||||
};
|
||||
|
||||
//
|
||||
// arch-ppc64v2.cc
|
||||
//
|
||||
|
||||
template <> u64 get_eflags(Context<PPC64V2> &ctx);
|
||||
|
||||
//
|
||||
// arch-sparc.cc
|
||||
//
|
||||
@@ -1511,6 +1481,7 @@ class AlphaGotSection : public Chunk<ALPHA> {
|
||||
public:
|
||||
AlphaGotSection() {
|
||||
this->name = ".alpha_got";
|
||||
this->is_relro = true;
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
this->shdr.sh_addralign = 8;
|
||||
@@ -1533,57 +1504,6 @@ private:
|
||||
std::mutex mu;
|
||||
};
|
||||
|
||||
//
|
||||
// arch-mips64.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class MipsGotSection : public Chunk<E> {
|
||||
public:
|
||||
MipsGotSection() {
|
||||
this->name = ".mips_got";
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_MIPS_GPREL;
|
||||
this->shdr.sh_addralign = 8;
|
||||
this->shdr.sh_size = 8;
|
||||
}
|
||||
|
||||
u64 get_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
|
||||
u64 get_gotpage_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
|
||||
u64 get_gotpage_page_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
|
||||
u64 get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const;
|
||||
u64 get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const;
|
||||
u64 get_tlsld_addr(Context<E> &ctx) const;
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct GotEntry {
|
||||
u64 val = 0;
|
||||
i64 r_type = R_NONE;
|
||||
Symbol<E> *sym = nullptr;
|
||||
};
|
||||
|
||||
struct SymbolAddend {
|
||||
bool operator==(const SymbolAddend &) const = default;
|
||||
bool operator<(const SymbolAddend &) const;
|
||||
u64 get_addr(Context<E> &ctx, i64 flags = 0) const;
|
||||
|
||||
Symbol<E> *sym;
|
||||
i64 addend;
|
||||
};
|
||||
|
||||
std::vector<GotEntry> get_got_entries(Context<E> &ctx) const;
|
||||
|
||||
std::vector<SymbolAddend> got_syms;
|
||||
std::vector<SymbolAddend> gotpage_syms;
|
||||
std::vector<Symbol<E> *> tlsgd_syms;
|
||||
std::vector<Symbol<E> *> gottp_syms;
|
||||
std::atomic_bool needs_tlsld = false;
|
||||
std::mutex mu;
|
||||
};
|
||||
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
@@ -1637,36 +1557,42 @@ struct SectionOrder {
|
||||
};
|
||||
|
||||
// Target-specific context members
|
||||
template <typename E> struct ContextExtras {};
|
||||
template <typename E>
|
||||
struct ContextExtras {};
|
||||
|
||||
template <> struct ContextExtras<PPC32> {
|
||||
template <is_riscv E>
|
||||
struct ContextExtras<E> {
|
||||
RiscvAttributesSection<E> *riscv_attributes = nullptr;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ContextExtras<PPC32> {
|
||||
Symbol<PPC32> *_SDA_BASE_ = nullptr;
|
||||
};
|
||||
|
||||
template <> struct ContextExtras<PPC64V1> {
|
||||
template <>
|
||||
struct ContextExtras<PPC64V1> {
|
||||
PPC64OpdSection *opd = nullptr;
|
||||
Symbol<PPC64V1> *TOC = nullptr;
|
||||
};
|
||||
|
||||
template <> struct ContextExtras<PPC64V2> {
|
||||
template <>
|
||||
struct ContextExtras<PPC64V2> {
|
||||
Symbol<PPC64V2> *TOC = nullptr;
|
||||
Atomic<bool> is_power10 = false;
|
||||
};
|
||||
|
||||
template <> struct ContextExtras<SPARC64> {
|
||||
template <>
|
||||
struct ContextExtras<SPARC64> {
|
||||
SparcTlsGetAddrSection *tls_get_addr_sec = nullptr;
|
||||
Symbol<SPARC64> *tls_get_addr_sym = nullptr;
|
||||
};
|
||||
|
||||
template <> struct ContextExtras<ALPHA> {
|
||||
template <>
|
||||
struct ContextExtras<ALPHA> {
|
||||
AlphaGotSection *got = nullptr;
|
||||
};
|
||||
|
||||
template <typename E> requires is_mips<E>
|
||||
struct ContextExtras<E> {
|
||||
MipsGotSection<E> *got = nullptr;
|
||||
};
|
||||
|
||||
// Context represents a context object for each invocation of the linker.
|
||||
// It contains command line flags, pointers to singleton objects
|
||||
// (such as linker-synthesized output sections), unique_ptrs for
|
||||
@@ -1709,7 +1635,7 @@ struct Context {
|
||||
bool fork = true;
|
||||
bool gc_sections = false;
|
||||
bool gdb_index = false;
|
||||
bool hash_style_gnu = !is_mips<E>;
|
||||
bool hash_style_gnu = true;
|
||||
bool hash_style_sysv = true;
|
||||
bool icf = false;
|
||||
bool icf_all = false;
|
||||
@@ -1760,22 +1686,24 @@ struct Context {
|
||||
bool z_now = false;
|
||||
bool z_origin = false;
|
||||
bool z_relro = true;
|
||||
bool z_rewrite_endbr = false;
|
||||
bool z_sectionheader = true;
|
||||
bool z_shstk = false;
|
||||
bool z_text = false;
|
||||
i64 filler = -1;
|
||||
i64 spare_dynamic_tags = 5;
|
||||
i64 thread_count = 0;
|
||||
i64 z_stack_size = 0;
|
||||
u64 shuffle_sections_seed;
|
||||
std::string_view emulation;
|
||||
std::optional<Glob> unique;
|
||||
std::optional<u64> physical_image_base;
|
||||
std::optional<u64> shuffle_sections_seed;
|
||||
std::string Map;
|
||||
std::string chroot;
|
||||
std::string dependency_file;
|
||||
std::string directory;
|
||||
std::string dynamic_linker;
|
||||
std::string entry = is_mips<E> ? "__start" : "_start";
|
||||
std::string entry = "_start";
|
||||
std::string fini = "_fini";
|
||||
std::string init = "_init";
|
||||
std::string output = "a.out";
|
||||
@@ -1805,7 +1733,7 @@ struct Context {
|
||||
|
||||
std::vector<VersionPattern> version_patterns;
|
||||
u16 default_version = VER_NDX_GLOBAL;
|
||||
i64 page_size = -1;
|
||||
i64 page_size = E::page_size;
|
||||
std::optional<int> global_lock_fd;
|
||||
|
||||
// true if default_version is set by a wildcard in version script.
|
||||
@@ -1857,9 +1785,9 @@ struct Context {
|
||||
bool overwrite_output_file = true;
|
||||
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
std::atomic_bool needs_tlsld = false;
|
||||
std::atomic_bool has_textrel = false;
|
||||
std::atomic_uint32_t num_ifunc_dynrels = 0;
|
||||
Atomic<bool> needs_tlsld = false;
|
||||
Atomic<bool> has_textrel = false;
|
||||
Atomic<u32> num_ifunc_dynrels = 0;
|
||||
|
||||
tbb::concurrent_hash_map<std::string_view, std::vector<std::string>> undef_errors;
|
||||
|
||||
@@ -1901,11 +1829,11 @@ struct Context {
|
||||
[[no_unique_address]] ContextExtras<E> extra;
|
||||
|
||||
// For --gdb-index
|
||||
Chunk<E> *debug_info = nullptr;
|
||||
Chunk<E> *debug_abbrev = nullptr;
|
||||
Chunk<E> *debug_ranges = nullptr;
|
||||
Chunk<E> *debug_addr = nullptr;
|
||||
Chunk<E> *debug_rnglists = nullptr;
|
||||
std::span<u8> debug_info;
|
||||
std::span<u8> debug_abbrev;
|
||||
std::span<u8> debug_ranges;
|
||||
std::span<u8> debug_addr;
|
||||
std::span<u8> debug_rnglists;
|
||||
|
||||
// For thread-local variables
|
||||
u64 tls_begin = 0;
|
||||
@@ -1936,7 +1864,6 @@ struct Context {
|
||||
Symbol<E> *_edata = nullptr;
|
||||
Symbol<E> *_end = nullptr;
|
||||
Symbol<E> *_etext = nullptr;
|
||||
Symbol<E> *_gp = nullptr;
|
||||
Symbol<E> *edata = nullptr;
|
||||
Symbol<E> *end = nullptr;
|
||||
Symbol<E> *etext = nullptr;
|
||||
@@ -1981,7 +1908,7 @@ enum {
|
||||
template <typename E>
|
||||
struct SymbolExtras {};
|
||||
|
||||
template <typename E> requires needs_thunk<E>
|
||||
template <needs_thunk E>
|
||||
struct SymbolExtras<E> {
|
||||
// For range extension thunks
|
||||
i16 thunk_idx = -1;
|
||||
@@ -2014,6 +1941,7 @@ public:
|
||||
u64 get_tlsdesc_addr(Context<E> &ctx) const;
|
||||
u64 get_plt_addr(Context<E> &ctx) const;
|
||||
u64 get_opd_addr(Context<E> &ctx) const;
|
||||
u64 get_got_pltgot_addr(Context<E> &ctx) const;
|
||||
|
||||
void set_got_idx(Context<E> &ctx, i32 idx);
|
||||
void set_gottp_idx(Context<E> &ctx, i32 idx);
|
||||
@@ -2047,8 +1975,13 @@ public:
|
||||
bool is_relative() const { return !is_absolute(); }
|
||||
bool is_local(Context<E> &ctx) const;
|
||||
bool is_ifunc() const { return get_type() == STT_GNU_IFUNC; }
|
||||
bool is_pde_ifunc(Context<E> &ctx) const;
|
||||
bool is_remaining_undef_weak() const;
|
||||
|
||||
bool is_pcrel_linktime_const(Context<E> &ctx) const;
|
||||
bool is_tprel_linktime_const(Context<E> &ctx) const;
|
||||
bool is_tprel_runtime_const(Context<E> &ctx) const;
|
||||
|
||||
InputSection<E> *get_input_section() const;
|
||||
Chunk<E> *get_output_section() const;
|
||||
SectionFragment<E> *get_frag() const;
|
||||
@@ -2420,7 +2353,7 @@ InputSection<E>::is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) const {
|
||||
(rel.r_offset % sizeof(Word<E>)) == 0;
|
||||
}
|
||||
|
||||
template<typename E>
|
||||
template <typename E>
|
||||
inline bool InputSection<E>::is_killed_by_icf() const {
|
||||
return this->leader && this->leader != this;
|
||||
}
|
||||
@@ -2644,6 +2577,25 @@ inline u64 Symbol<E>::get_opd_addr(Context<E> &ctx) const {
|
||||
get_opd_idx(ctx) * PPC64OpdSection::ENTRY_SIZE;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline u64 Symbol<E>::get_got_pltgot_addr(Context<E> &ctx) const {
|
||||
// An ifunc symbol occupies two consecutive GOT slots in a
|
||||
// position-dependent executable (PDE). The first slot contains the
|
||||
// symbol's PLT address, and the second slot holds the resolved
|
||||
// address. A PDE uses the ifunc symbol's PLT entry as the address
|
||||
// for the symbol, akin to a canonical PLT.
|
||||
//
|
||||
// This function returns the address that the PLT entry should use
|
||||
// to jump to the resolved address.
|
||||
//
|
||||
// Note that we don't use this function for PPC64. In PPC64, symbols
|
||||
// are always accessed through the TOC table regardless of the
|
||||
// -fno-PIE setting. We don't need canonical PLTs on the psABIs too.
|
||||
if (is_pde_ifunc(ctx))
|
||||
return get_got_addr(ctx) + sizeof(Word<E>);
|
||||
return get_got_addr(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline void Symbol<E>::set_got_idx(Context<E> &ctx, i32 idx) {
|
||||
assert(aux_idx != -1);
|
||||
@@ -2772,14 +2724,43 @@ inline bool Symbol<E>::is_local(Context<E> &ctx) const {
|
||||
return !is_imported && !is_exported;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_pde_ifunc(Context<E> &ctx) const {
|
||||
// Returns true if this is an ifunc tha uses two GOT slots
|
||||
return is_ifunc() && !ctx.arg.pic && !is_ppc64<E>;
|
||||
}
|
||||
|
||||
// A remaining weak undefined symbol is promoted to a dynamic symbol
|
||||
// in DSO and resolved to 0 in an executable. This function returns
|
||||
// true if it's latter.
|
||||
template<typename E>
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_remaining_undef_weak() const {
|
||||
return !is_imported && esym().is_undef_weak();
|
||||
}
|
||||
|
||||
// Returns true if the symbol's PC-relative address is known at link-time.
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_pcrel_linktime_const(Context<E> &ctx) const {
|
||||
return !is_imported && !is_ifunc() && (is_relative() || !ctx.arg.pic);
|
||||
}
|
||||
|
||||
// Returns true if the symbol's Thread Pointer-relative address is
|
||||
// known at link-time.
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_tprel_linktime_const(Context<E> &ctx) const {
|
||||
assert(get_type() == STT_TLS);
|
||||
return !ctx.arg.shared && !is_imported;
|
||||
}
|
||||
|
||||
// Returns true if the symbol's Thread Pointer-relative address is
|
||||
// known at load-time.
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_tprel_runtime_const(Context<E> &ctx) const {
|
||||
// Returns true unless we are creating a dlopen'able DSO.
|
||||
assert(get_type() == STT_TLS);
|
||||
return !(ctx.arg.shared && ctx.arg.z_dlopen);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline InputSection<E> *Symbol<E>::get_input_section() const {
|
||||
if ((origin & TAG_MASK) == TAG_ISEC)
|
||||
@@ -2864,9 +2845,8 @@ inline std::string_view Symbol<E>::name() const {
|
||||
template <typename E>
|
||||
inline void Symbol<E>::add_aux(Context<E> &ctx) {
|
||||
if (aux_idx == -1) {
|
||||
i64 sz = ctx.symbol_aux.size();
|
||||
aux_idx = sz;
|
||||
ctx.symbol_aux.resize(sz + 1);
|
||||
aux_idx = ctx.symbol_aux.size();
|
||||
ctx.symbol_aux.resize(aux_idx + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2890,15 +2870,4 @@ inline bool is_c_identifier(std::string_view s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool relax_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// TLSDESC relocs must be always relaxed for statically-linked
|
||||
// executables even if -no-relax is given. It is because a
|
||||
// statically-linked executable doesn't contain a tranpoline
|
||||
// function needed for TLSDESC.
|
||||
if (ctx.arg.is_static)
|
||||
return true;
|
||||
return ctx.arg.relax && !ctx.arg.shared && !sym.is_imported;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+417
-624
File diff suppressed because it is too large.
Load diff
+258
-150
@@ -4,7 +4,6 @@
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <shared_mutex>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
@@ -14,6 +13,50 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
template <typename E>
|
||||
int redo_main(Context<E> &ctx, int argc, char **argv) {
|
||||
std::string_view target = ctx.arg.emulation;
|
||||
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == LOONGARCH32::target_name)
|
||||
return elf_main<LOONGARCH32>(argc, argv);
|
||||
if (target == LOONGARCH64::target_name)
|
||||
return elf_main<LOONGARCH64>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void apply_exclude_libs(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_exclude_libs");
|
||||
@@ -32,9 +75,17 @@ void apply_exclude_libs(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool has_debug_info_section(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->debug_info)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_synthetic_sections(Context<E> &ctx) {
|
||||
auto push = [&]<typename T>(T *x) {
|
||||
auto push = [&](auto *x) {
|
||||
ctx.chunks.push_back(x);
|
||||
ctx.chunk_pool.emplace_back(x);
|
||||
return x;
|
||||
@@ -58,13 +109,14 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
else
|
||||
ctx.phdr = push(new OutputPhdr<E>(0));
|
||||
|
||||
ctx.shdr = push(new OutputShdr<E>);
|
||||
if (ctx.arg.z_sectionheader)
|
||||
ctx.shdr = push(new OutputShdr<E>);
|
||||
}
|
||||
|
||||
ctx.got = push(new GotSection<E>);
|
||||
|
||||
if constexpr (!is_sparc<E>)
|
||||
ctx.gotplt = push(new GotPltSection<E>);
|
||||
ctx.gotplt = push(new GotPltSection<E>(ctx));
|
||||
|
||||
ctx.reldyn = push(new RelDynSection<E>);
|
||||
ctx.relplt = push(new RelPltSection<E>);
|
||||
@@ -82,7 +134,7 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.copyrel = push(new CopyrelSection<E>(false));
|
||||
ctx.copyrel_relro = push(new CopyrelSection<E>(true));
|
||||
|
||||
if (!ctx.arg.oformat_binary)
|
||||
if (ctx.shdr)
|
||||
ctx.shstrtab = push(new ShstrtabSection<E>);
|
||||
|
||||
if (!ctx.arg.dynamic_linker.empty())
|
||||
@@ -91,7 +143,7 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.buildid = push(new BuildIdSection<E>);
|
||||
if (ctx.arg.eh_frame_hdr)
|
||||
ctx.eh_frame_hdr = push(new EhFrameHdrSection<E>);
|
||||
if (ctx.arg.gdb_index)
|
||||
if (ctx.arg.gdb_index && has_debug_info_section(ctx))
|
||||
ctx.gdb_index = push(new GdbIndexSection<E>);
|
||||
if (ctx.arg.z_relro && ctx.arg.section_order.empty() &&
|
||||
ctx.arg.z_separate_code != SEPARATE_LOADABLE_SEGMENTS)
|
||||
@@ -105,14 +157,22 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.emit_relocs)
|
||||
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
|
||||
|
||||
if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie)
|
||||
if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie) {
|
||||
ctx.dynamic = push(new DynamicSection<E>);
|
||||
|
||||
// If .dynamic exists, .dynsym and .dynstr must exist as well
|
||||
// since .dynamic refers to them.
|
||||
ctx.dynstr->shdr.sh_size = 1;
|
||||
ctx.dynsym->symbols.resize(1);
|
||||
}
|
||||
|
||||
ctx.versym = push(new VersymSection<E>);
|
||||
ctx.verneed = push(new VerneedSection<E>);
|
||||
ctx.note_package = push(new NotePackageSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if constexpr (is_riscv<E>)
|
||||
ctx.extra.riscv_attributes = push(new RiscvAttributesSection<E>);
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
ctx.extra.opd = push(new PPC64OpdSection);
|
||||
@@ -125,16 +185,6 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
|
||||
if constexpr (is_alpha<E>)
|
||||
ctx.extra.got = push(new AlphaGotSection);
|
||||
|
||||
if constexpr (is_mips<E>)
|
||||
ctx.extra.got = push(new MipsGotSection<E>);
|
||||
|
||||
// If .dynamic exists, .dynsym and .dynstr must exist as well
|
||||
// since .dynamic refers them.
|
||||
if (ctx.dynamic) {
|
||||
ctx.dynstr->keep();
|
||||
ctx.dynsym->keep();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -297,6 +347,7 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
// .eh_frame sections are parsed and regenerated by the linker for the purpose
|
||||
// of deduplication and garbage collection. As such, the input sections should
|
||||
// not be copied over.
|
||||
//
|
||||
// However, in very rare cases (e.g. GCC CRT compiled with LTO) we might need
|
||||
// to resolve cross-object .eh_frame section references (they only point to
|
||||
// begin or end and don't depend on the actual section contents).
|
||||
@@ -306,15 +357,9 @@ template <typename E>
|
||||
void kill_eh_frame_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "kill_eh_frame_sections");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->sections.size(); i++) {
|
||||
if (std::unique_ptr<InputSection<E>> &isec = file->sections[i]) {
|
||||
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
|
||||
isec->is_alive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->eh_frame_section)
|
||||
file->eh_frame_section->is_alive = false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -352,7 +397,7 @@ template <typename E>
|
||||
void add_comment_string(Context<E> &ctx, std::string str) {
|
||||
MergedSection<E> *sec =
|
||||
MergedSection<E>::get_instance(ctx, ".comment", SHT_PROGBITS,
|
||||
SHF_MERGE | SHF_STRINGS);
|
||||
SHF_MERGE | SHF_STRINGS, 1);
|
||||
|
||||
std::string_view buf = save_string(ctx, str);
|
||||
std::string_view data(buf.data(), buf.size() + 1);
|
||||
@@ -407,13 +452,10 @@ static u64 canonicalize_type(std::string_view name, u64 type) {
|
||||
}
|
||||
|
||||
struct OutputSectionKey {
|
||||
bool operator==(const OutputSectionKey &) const = default;
|
||||
std::string_view name;
|
||||
u64 type;
|
||||
u64 flags;
|
||||
|
||||
bool operator==(const OutputSectionKey &other) const {
|
||||
return name == other.name && type == other.type && flags == other.flags;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -474,10 +516,7 @@ get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
|
||||
u64 type = canonicalize_type<E>(name, shdr.sh_type);
|
||||
u64 flags = shdr.sh_flags & ~(u64)SHF_COMPRESSED;
|
||||
|
||||
if (!ctx.arg.relocatable)
|
||||
flags &= ~(u64)SHF_GROUP & ~(u64)SHF_GNU_RETAIN;
|
||||
u64 flags = shdr.sh_flags & ~(u64)(SHF_COMPRESSED | SHF_GROUP | SHF_GNU_RETAIN);
|
||||
|
||||
// .init_array is usually writable. We don't want to create multiple
|
||||
// .init_array output sections, so make it always writable.
|
||||
@@ -504,6 +543,8 @@ void create_output_sections(Context<E> &ctx) {
|
||||
std::unordered_map<OutputSectionKey, OutputSection<E> *, Hash> map;
|
||||
std::shared_mutex mu;
|
||||
|
||||
i64 size = ctx.osec_pool.size();
|
||||
|
||||
// Instantiate output sections
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
// Make a per-thread cache of the main map to avoid lock contention.
|
||||
@@ -518,6 +559,15 @@ void create_output_sections(Context<E> &ctx) {
|
||||
if (!isec || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
const ElfShdr<E> &shdr = isec->shdr();
|
||||
if (ctx.arg.relocatable && (shdr.sh_flags & SHF_GROUP)) {
|
||||
OutputSection<E> *osec =
|
||||
new OutputSection<E>(ctx, isec->name(), shdr.sh_type, shdr.sh_flags);
|
||||
isec->output_section = osec;
|
||||
ctx.osec_pool.emplace_back(osec);
|
||||
continue;
|
||||
}
|
||||
|
||||
OutputSectionKey key = get_output_section_key(ctx, *isec);
|
||||
|
||||
if (auto it = cache.find(key); it != cache.end()) {
|
||||
@@ -533,12 +583,11 @@ void create_output_sections(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputSection<E>> osec =
|
||||
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
|
||||
std::make_unique<OutputSection<E>>(ctx, key.name, key.type, key.flags);
|
||||
|
||||
std::unique_lock lock(mu);
|
||||
auto [it, inserted] = map.insert({key, osec.get()});
|
||||
OutputSection<E> *ret = it->second;
|
||||
lock.unlock();
|
||||
|
||||
if (inserted)
|
||||
ctx.osec_pool.emplace_back(std::move(osec));
|
||||
@@ -552,29 +601,29 @@ void create_output_sections(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Add input sections to output sections
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
for (i64 i = size; i < ctx.osec_pool.size(); i++)
|
||||
chunks.push_back(ctx.osec_pool[i].get());
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec && isec->is_alive)
|
||||
isec->output_section->members.push_back(isec.get());
|
||||
|
||||
// Add output sections and mergeable sections to ctx.chunks
|
||||
std::vector<Chunk<E> *> vec;
|
||||
for (std::pair<const OutputSectionKey, OutputSection<E> *> &kv : map)
|
||||
vec.push_back(kv.second);
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
|
||||
if (osec->shdr.sh_size)
|
||||
vec.push_back(osec.get());
|
||||
chunks.push_back(osec.get());
|
||||
|
||||
// Sections are added to the section lists in an arbitrary order
|
||||
// because they are created in parallel. Sort them to to make the
|
||||
// output deterministic.
|
||||
tbb::parallel_sort(vec.begin(), vec.end(), [](Chunk<E> *x, Chunk<E> *y) {
|
||||
tbb::parallel_sort(chunks.begin(), chunks.end(), [](Chunk<E> *x, Chunk<E> *y) {
|
||||
return std::tuple(x->name, x->shdr.sh_type, x->shdr.sh_flags) <
|
||||
std::tuple(y->name, y->shdr.sh_type, y->shdr.sh_flags);
|
||||
});
|
||||
|
||||
append(ctx.chunks, vec);
|
||||
append(ctx.chunks, chunks);
|
||||
}
|
||||
|
||||
// Create a dummy object file containing linker-synthesized
|
||||
@@ -681,10 +730,10 @@ template <typename E>
|
||||
void add_synthetic_symbols(Context<E> &ctx) {
|
||||
ObjectFile<E> &obj = *ctx.internal_obj;
|
||||
|
||||
auto add = [&](std::string_view name) {
|
||||
auto add = [&](std::string_view name, u32 type = STT_NOTYPE) {
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_type = type;
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = STV_HIDDEN;
|
||||
@@ -730,7 +779,7 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
ctx.__dso_handle = add("__dso_handle");
|
||||
|
||||
if constexpr (supports_tlsdesc<E>)
|
||||
ctx._TLS_MODULE_BASE_ = add("_TLS_MODULE_BASE_");
|
||||
ctx._TLS_MODULE_BASE_ = add("_TLS_MODULE_BASE_", STT_TLS);
|
||||
|
||||
if constexpr (is_riscv<E>)
|
||||
if (!ctx.arg.shared)
|
||||
@@ -747,9 +796,6 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
if constexpr (is_ppc32<E>)
|
||||
ctx.extra._SDA_BASE_ = add("_SDA_BASE_");
|
||||
|
||||
if constexpr (is_mips<E>)
|
||||
ctx._gp = add("_gp");
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
|
||||
add(save_string(ctx, "__start_" + *name));
|
||||
@@ -975,33 +1021,30 @@ template <typename E>
|
||||
void check_symbol_types(Context<E> &ctx) {
|
||||
Timer t(ctx, "check_symbol_types");
|
||||
|
||||
auto check = [&](InputFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
std::vector<InputFile<E> *> files;
|
||||
append(files, ctx.objs);
|
||||
append(files, ctx.dsos);
|
||||
|
||||
if (!sym.file)
|
||||
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (!sym.file || sym.file == file)
|
||||
continue;
|
||||
|
||||
u32 x = sym.esym().st_type;
|
||||
if (x == STT_GNU_IFUNC)
|
||||
x = STT_FUNC;
|
||||
const ElfSym<E> &esym1 = sym.esym();
|
||||
const ElfSym<E> &esym2 = file->elf_syms[i];
|
||||
|
||||
u32 y = esym.st_type;
|
||||
if (y == STT_GNU_IFUNC)
|
||||
y = STT_FUNC;
|
||||
u32 ty1 = (esym1.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym1.st_type;
|
||||
u32 ty2 = (esym2.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym2.st_type;
|
||||
|
||||
if (x != STT_NOTYPE && y != STT_NOTYPE && x != y)
|
||||
if (ty1 != STT_NOTYPE && ty2 != STT_NOTYPE && ty1 != ty2)
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
<< ">>> defined in " << *sym.file << " as "
|
||||
<< stt_to_string<E>(sym.esym().st_type) << '\n'
|
||||
<< stt_to_string<E>(esym1.st_type) << '\n'
|
||||
<< ">>> defined in " << *file << " as "
|
||||
<< stt_to_string<E>(esym.st_type);
|
||||
<< stt_to_string<E>(esym2.st_type);
|
||||
}
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, check);
|
||||
tbb::parallel_for_each(ctx.dsos, check);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1109,37 +1152,35 @@ template <typename E>
|
||||
void shuffle_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "shuffle_sections");
|
||||
|
||||
auto is_eligible = [](OutputSection<E> &osec) {
|
||||
return osec.name != ".init" && osec.name != ".fini" &&
|
||||
osec.name != ".ctors" && osec.name != ".dtors" &&
|
||||
osec.name != ".init_array" && osec.name != ".preinit_array" &&
|
||||
osec.name != ".fini_array";
|
||||
auto is_eligible = [](OutputSection<E> *osec) {
|
||||
if (osec) {
|
||||
std::string_view name = osec->name;
|
||||
return name != ".init" && name != ".fini" &&
|
||||
name != ".ctors" && name != ".dtors" &&
|
||||
name != ".init_array" && name != ".preinit_array" &&
|
||||
name != ".fini_array";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (ctx.arg.shuffle_sections) {
|
||||
case SHUFFLE_SECTIONS_NONE:
|
||||
unreachable();
|
||||
case SHUFFLE_SECTIONS_SHUFFLE: {
|
||||
u64 seed;
|
||||
if (ctx.arg.shuffle_sections_seed)
|
||||
seed = *ctx.arg.shuffle_sections_seed;
|
||||
else
|
||||
seed = ((u64)std::random_device()() << 32) | std::random_device()();
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (is_eligible(*osec))
|
||||
shuffle(osec->members, seed + hash_string(osec->name));
|
||||
if (OutputSection<E> *osec = chunk->to_osec(); is_eligible(osec)) {
|
||||
u64 seed = ctx.arg.shuffle_sections_seed + hash_string(osec->name);
|
||||
shuffle(osec->members, seed);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case SHUFFLE_SECTIONS_REVERSE:
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (is_eligible(*osec))
|
||||
std::reverse(osec->members.begin(), osec->members.end());
|
||||
if (OutputSection<E> *osec = chunk->to_osec(); is_eligible(osec))
|
||||
std::reverse(osec->members.begin(), osec->members.end());
|
||||
});
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1180,19 +1221,16 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
}
|
||||
});
|
||||
|
||||
i64 offset = 0;
|
||||
i64 p2align = 0;
|
||||
ElfShdr<E> &shdr = osec->shdr;
|
||||
shdr.sh_size = 0;
|
||||
|
||||
for (i64 i = 0; i < groups.size(); i++) {
|
||||
offset = align_to(offset, 1 << groups[i].p2align);
|
||||
groups[i].offset = offset;
|
||||
offset += groups[i].size;
|
||||
p2align = std::max(p2align, groups[i].p2align);
|
||||
shdr.sh_size = align_to(shdr.sh_size, 1 << groups[i].p2align);
|
||||
groups[i].offset = shdr.sh_size;
|
||||
shdr.sh_size += groups[i].size;
|
||||
shdr.sh_addralign = std::max<u32>(shdr.sh_addralign, 1 << groups[i].p2align);
|
||||
}
|
||||
|
||||
osec->shdr.sh_size = offset;
|
||||
osec->shdr.sh_addralign = 1 << p2align;
|
||||
|
||||
// Assign offsets to input sections.
|
||||
tbb::parallel_for_each(groups, [](Group &group) {
|
||||
i64 offset = group.offset;
|
||||
@@ -1213,22 +1251,14 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
// create_range_extension_thunks is parallelized internally, but the
|
||||
// function itself is not thread-safe.
|
||||
if constexpr (needs_thunk<E>) {
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
OutputSection<E> *osec = chunk->to_osec();
|
||||
if (osec && (osec->shdr.sh_flags & SHF_EXECINSTR) && !ctx.arg.relocatable) {
|
||||
create_range_extension_thunks(ctx, *osec);
|
||||
Timer t2(ctx, "create_range_extension_thunks");
|
||||
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
osec->shdr.sh_addralign =
|
||||
std::max<u32>(osec->shdr.sh_addralign, 1 << isec->p2align);
|
||||
}
|
||||
}
|
||||
if (!ctx.arg.relocatable)
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
osec->create_range_extension_thunks(ctx);
|
||||
}
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (u32 align = ctx.arg.section_align[osec->name])
|
||||
osec->shdr.sh_addralign = std::max<u32>(osec->shdr.sh_addralign, align);
|
||||
}
|
||||
|
||||
// Find all unresolved symbols and attach them to the most appropriate files.
|
||||
@@ -1352,6 +1382,9 @@ void scan_relocations(Context<E> &ctx) {
|
||||
std::vector<Symbol<E> *> syms = flatten(vec);
|
||||
ctx.symbol_aux.reserve(syms.size());
|
||||
|
||||
if (ctx.needs_tlsld)
|
||||
ctx.got->add_tlsld(ctx);
|
||||
|
||||
// Assign offsets in additional tables for each dynamic symbol.
|
||||
for (Symbol<E> *sym : syms) {
|
||||
sym->add_aux(ctx);
|
||||
@@ -1402,9 +1435,6 @@ void scan_relocations(Context<E> &ctx) {
|
||||
sym->flags = 0;
|
||||
}
|
||||
|
||||
if (ctx.needs_tlsld)
|
||||
ctx.got->add_tlsld(ctx);
|
||||
|
||||
if constexpr (is_alpha<E>)
|
||||
ctx.extra.got->finalize();
|
||||
|
||||
@@ -1497,20 +1527,19 @@ void construct_relr(Context<E> &ctx) {
|
||||
Timer t(ctx, "construct_relr");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
osec->construct_relr(ctx);
|
||||
chunk->construct_relr(ctx);
|
||||
});
|
||||
|
||||
ctx.got->construct_relr(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_output_symtab(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_symtab_size");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_symtab_size(ctx);
|
||||
});
|
||||
if (!ctx.arg.strip_all && !ctx.arg.retain_symbols_file) {
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_symtab_size(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->compute_symtab_size(ctx);
|
||||
@@ -1700,12 +1729,85 @@ void compute_import_export(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
// Compute the "address-taken" bit for each input section.
|
||||
//
|
||||
// As a space-saving optimization, we want to merge two read-only objects
|
||||
// into a single object if their contents are equivalent. That
|
||||
// optimization is called the Identical Code Folding or ICF.
|
||||
//
|
||||
// A catch is that comparing object contents is not enough to determine if
|
||||
// two objects can be merged safely; we need to take care of pointer
|
||||
// equivalence.
|
||||
//
|
||||
// In C/C++, two pointers are equivalent if and only if they are taken for
|
||||
// the same object. Merging two objects into a single object can break
|
||||
// this assumption because two distinctive pointers would become
|
||||
// equivalent as a result of merging. We can still merge one object with
|
||||
// another if no pointer to the object was taken in code, because without
|
||||
// a pointer, comparing its address becomes moot.
|
||||
//
|
||||
// In mold, each input section has an "address-taken" bit. If there is a
|
||||
// pointer-taking reference to the object, it's set to true. At the ICF
|
||||
// stage, we merge only objects whose addresses were not taken.
|
||||
//
|
||||
// For functions, address-taking relocations are separated from
|
||||
// non-address-taking ones. For example, x86-64 uses R_X86_64_PLT32 for
|
||||
// direct function calls (e.g., "call foo" to call the function foo) while
|
||||
// R_X86_64_PC32 or R_X86_64_GOT32 are used for pointer-taking operations.
|
||||
//
|
||||
// Unfortunately, for data, we can't distinguish between address-taking
|
||||
// relocations and non-address-taking ones. LLVM generates an "address
|
||||
// significance" table in the ".llvm_addrsig" section to mark symbols
|
||||
// whose addresses are taken in code. If that table is available, we use
|
||||
// that information in this function. Otherwise, we conservatively assume
|
||||
// that all data items are address-taken.
|
||||
template <typename E>
|
||||
void mark_addrsig(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_addrsig");
|
||||
void compute_address_significance(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_address_significance");
|
||||
|
||||
// Flip address-taken bit for executable sections first.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->mark_addrsig(ctx);
|
||||
for (std::unique_ptr<InputSection<E>> &src : file->sections)
|
||||
if (src && src->is_alive && (src->shdr().sh_flags & SHF_ALLOC))
|
||||
for (const ElfRel<E> &r : src->get_rels(ctx))
|
||||
if (!is_func_call_rel(r))
|
||||
if (InputSection<E> *dst = file->symbols[r.r_sym]->get_input_section())
|
||||
if (dst->shdr().sh_flags & SHF_EXECINSTR)
|
||||
dst->address_taken = true;
|
||||
});
|
||||
|
||||
auto mark = [](Symbol<E> *sym) {
|
||||
if (sym)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
isec->address_taken = true;
|
||||
};
|
||||
|
||||
// Some symbols' pointer values are leaked to the dynamic section.
|
||||
mark(get_symbol(ctx, ctx.arg.entry));
|
||||
mark(get_symbol(ctx, ctx.arg.init));
|
||||
mark(get_symbol(ctx, ctx.arg.fini));
|
||||
|
||||
// Exported symbols are conservatively considered address-taken.
|
||||
if (ctx.dynsym)
|
||||
for (Symbol<E> *sym : ctx.dynsym->symbols)
|
||||
if (sym->is_exported)
|
||||
mark(sym);
|
||||
|
||||
// Handle data objects.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (InputSection<E> *sec = file->llvm_addrsig.get()) {
|
||||
u8 *p = (u8 *)sec->contents.data();
|
||||
u8 *end = p + sec->contents.size();
|
||||
while (p != end) {
|
||||
Symbol<E> *sym = file->symbols[read_uleb(&p)];
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
isec->address_taken = true;
|
||||
}
|
||||
} else {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec && !(isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
isec->address_taken = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1751,13 +1853,13 @@ void clear_padding(Context<E> &ctx) {
|
||||
// .got
|
||||
// .toc
|
||||
// .alpha_got
|
||||
// .mips_got
|
||||
// <writable RELRO bss>
|
||||
// .relro_padding
|
||||
// <writable non-RELRO data>
|
||||
// <writable non-RELRO bss>
|
||||
// <non-memory-allocated sections>
|
||||
// <section header>
|
||||
// .gdb_index
|
||||
//
|
||||
// .interp and some other linker-synthesized sections are placed at the
|
||||
// beginning of a file because they are needed by loader. Especially on
|
||||
@@ -1769,6 +1871,10 @@ void clear_padding(Context<E> &ctx) {
|
||||
// particular, if .note.gnu.build-id is in a truncated core file, you
|
||||
// can at least identify which executable has crashed.
|
||||
//
|
||||
// .gdb_index cannot be constructed before applying relocations to
|
||||
// other debug sections, so we create it after completing other part
|
||||
// of the output file and append it to the very end of the file.
|
||||
//
|
||||
// A PT_NOTE segment will contain multiple .note sections if exists,
|
||||
// but there's no way to represent a gap between .note sections.
|
||||
// Therefore, we sort .note sections by decreasing alignment
|
||||
@@ -1814,45 +1920,48 @@ void sort_output_sections_regular(Context<E> &ctx) {
|
||||
if (chunk == ctx.relplt)
|
||||
return 11;
|
||||
if (chunk == ctx.shdr)
|
||||
return INT32_MAX - 1;
|
||||
if (chunk == ctx.gdb_index)
|
||||
return INT32_MAX;
|
||||
|
||||
bool alloc = (flags & SHF_ALLOC);
|
||||
bool writable = (flags & SHF_WRITE);
|
||||
bool exec = (flags & SHF_EXECINSTR);
|
||||
bool tls = (flags & SHF_TLS);
|
||||
bool relro = is_relro(ctx, chunk);
|
||||
bool relro = chunk->is_relro;
|
||||
bool is_bss = (type == SHT_NOBITS);
|
||||
|
||||
return (1 << 10) | (!alloc << 9) | (writable << 8) | (exec << 7) |
|
||||
(!tls << 6) | (!relro << 5) | (is_bss << 4);
|
||||
};
|
||||
|
||||
// Ties are broken by additional rules
|
||||
auto get_rank2 = [&](Chunk<E> *chunk) -> i64 {
|
||||
if (chunk->shdr.sh_type == SHT_NOTE)
|
||||
return -chunk->shdr.sh_addralign;
|
||||
ElfShdr<E> &shdr = chunk->shdr;
|
||||
if (shdr.sh_type == SHT_NOTE)
|
||||
return -shdr.sh_addralign;
|
||||
|
||||
if (chunk == ctx.got)
|
||||
return 1;
|
||||
if (chunk->name == ".toc")
|
||||
return 2;
|
||||
if (chunk->name == ".alpha_got")
|
||||
if (chunk->name == ".toc")
|
||||
return 3;
|
||||
if (chunk->name == ".mips_got")
|
||||
if (chunk->name == ".alpha_got")
|
||||
return 4;
|
||||
|
||||
if (shdr.sh_flags & SHF_MERGE) {
|
||||
if (shdr.sh_flags & SHF_STRINGS)
|
||||
return (5LL << 32) | shdr.sh_entsize;
|
||||
return (6LL << 32) | shdr.sh_entsize;
|
||||
}
|
||||
|
||||
if (chunk == ctx.relro_padding)
|
||||
return INT_MAX;
|
||||
return INT64_MAX;
|
||||
return 0;
|
||||
};
|
||||
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
// Sort sections by segments
|
||||
i64 x = get_rank1(a);
|
||||
i64 y = get_rank1(b);
|
||||
if (x != y)
|
||||
return x < y;
|
||||
|
||||
// Ties are broken by additional rules
|
||||
return get_rank2(a) < get_rank2(b);
|
||||
return std::tuple{get_rank1(a), get_rank2(a), a->name} <
|
||||
std::tuple{get_rank1(b), get_rank2(b), b->name};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1961,7 +2070,7 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
|
||||
|
||||
auto get_flags = [&](Chunk<E> *chunk) {
|
||||
i64 flags = to_phdr_flags(ctx, chunk);
|
||||
if (is_relro(ctx, chunk))
|
||||
if (chunk->is_relro)
|
||||
return flags | RELRO;
|
||||
return flags;
|
||||
};
|
||||
@@ -2224,8 +2333,9 @@ void compute_section_headers(Context<E> &ctx) {
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Remove empty chunks.
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
|
||||
std::erase_if(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk != ctx.gdb_index &&
|
||||
chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
@@ -2418,7 +2528,7 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
// this symbol.
|
||||
if (ctx._TLS_MODULE_BASE_) {
|
||||
ctx._TLS_MODULE_BASE_->set_output_section(sections[0]);
|
||||
ctx._TLS_MODULE_BASE_->value = ctx.tls_begin;
|
||||
ctx._TLS_MODULE_BASE_->value = ctx.dtp_addr;
|
||||
}
|
||||
|
||||
// __GNU_EH_FRAME_HDR
|
||||
@@ -2454,10 +2564,6 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// MIPS' _gp symbol.
|
||||
if (ctx._gp)
|
||||
start(ctx._gp, ctx.got, 0x7ff0);
|
||||
|
||||
// __start_ and __stop_ symbols
|
||||
for (Chunk<E> *chunk : sections) {
|
||||
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
|
||||
@@ -2523,7 +2629,8 @@ i64 compress_debug_sections(Context<E> &ctx) {
|
||||
ctx.chunks[i] = comp;
|
||||
});
|
||||
|
||||
ctx.shstrtab->update_shdr(ctx);
|
||||
if (ctx.shstrtab)
|
||||
ctx.shstrtab->update_shdr(ctx);
|
||||
|
||||
if (ctx.ehdr)
|
||||
ctx.ehdr->update_shdr(ctx);
|
||||
@@ -2631,6 +2738,7 @@ void show_stats(Context<E> &ctx) {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int redo_main(Context<E> &, int, char **);
|
||||
template void create_internal_file(Context<E> &);
|
||||
template void apply_exclude_libs(Context<E> &);
|
||||
template void create_synthetic_sections(Context<E> &);
|
||||
@@ -2661,7 +2769,7 @@ template void create_output_symtab(Context<E> &);
|
||||
template void apply_version_script(Context<E> &);
|
||||
template void parse_symbol_version(Context<E> &);
|
||||
template void compute_import_export(Context<E> &);
|
||||
template void mark_addrsig(Context<E> &);
|
||||
template void compute_address_significance(Context<E> &);
|
||||
template void clear_padding(Context<E> &);
|
||||
template void compute_section_headers(Context<E> &);
|
||||
template i64 set_osec_offsets(Context<E> &);
|
||||
|
||||
+4
-1
@@ -40,7 +40,7 @@ namespace mold::elf {
|
||||
// Create linker-synthesized sections
|
||||
template <typename E>
|
||||
static void r_create_synthetic_sections(Context<E> &ctx) {
|
||||
auto push = [&]<typename T>(T *x) {
|
||||
auto push = [&](auto *x) {
|
||||
ctx.chunks.push_back(x);
|
||||
ctx.chunk_pool.emplace_back(x);
|
||||
return x;
|
||||
@@ -54,6 +54,9 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.symtab = push(new SymtabSection<E>);
|
||||
ctx.shstrtab = push(new ShstrtabSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if constexpr (is_riscv<E>)
|
||||
ctx.extra.riscv_attributes = push(new RiscvAttributesSection<E>);
|
||||
}
|
||||
|
||||
// Create SHT_GROUP (i.e. comdat group) sections. We uniquify comdat
|
||||
|
||||
+56
-69
@@ -20,7 +20,8 @@
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || \
|
||||
MOLD_LOONGARCH64 || MOLD_LOONGARCH32
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -29,15 +30,18 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
// Returns a branch reach in bytes for a given target.
|
||||
template <typename E>
|
||||
static consteval i64 max_distance() {
|
||||
// ARM64's branch has 26 bits immediate. The immediate is padded with
|
||||
// implicit two-bit zeros because all instructions are 4 bytes aligned
|
||||
// and therefore the least two bits are always zero. So the branch
|
||||
// operand is effectively 28 bits long. That means the branch range is
|
||||
// [-2^27, 2^27) or PC ± 128 MiB.
|
||||
if (is_arm64<E>)
|
||||
//
|
||||
// LoongArch's BR instruction also takes a 26 bit immediate.
|
||||
if (is_arm64<E> || is_loongarch<E>)
|
||||
return 1 << 27;
|
||||
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
@@ -57,30 +61,12 @@ static consteval i64 max_distance() {
|
||||
|
||||
// We create thunks for each 12.8/1.6/3.2 MiB code block for
|
||||
// ARM64/ARM32/PPC, respectively.
|
||||
template <typename E>
|
||||
static constexpr i64 batch_size = max_distance<E>() / 10;
|
||||
static constexpr i64 batch_size = max_distance() / 10;
|
||||
|
||||
// We assume that a single thunk group is smaller than 100 KiB.
|
||||
static constexpr i64 max_thunk_size = 102400;
|
||||
|
||||
// Returns true if a given relocation is of type used for function calls.
|
||||
template <typename E>
|
||||
static bool needs_thunk_rel(const ElfRel<E> &r) {
|
||||
u32 ty = r.r_type;
|
||||
|
||||
if constexpr (is_arm64<E>) {
|
||||
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
|
||||
} else if constexpr (is_arm32<E>) {
|
||||
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
|
||||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL ||
|
||||
ty == R_ARM_PLT32;
|
||||
} else if constexpr (is_ppc32<E>) {
|
||||
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
|
||||
} else {
|
||||
static_assert(is_ppc64<E>);
|
||||
return ty == R_PPC64_REL24 || ty == R_PPC64_REL24_NOTOC;
|
||||
}
|
||||
}
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
@@ -130,10 +116,9 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
i64 A = get_addend(isec, rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -max_distance<E>() <= val && val < max_distance<E>();
|
||||
return -max_distance() <= val && val < max_distance();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
@@ -143,16 +128,15 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
}
|
||||
|
||||
// Scan relocations to collect symbols that need thunks.
|
||||
template <typename E>
|
||||
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
RangeExtensionThunk<E> &thunk) {
|
||||
RangeExtensionThunk<E> &thunk, i64 thunk_idx) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
if (!is_func_call_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
@@ -174,7 +158,7 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
|
||||
// Otherwise, add the symbol to the current thunk if it's not
|
||||
// added already.
|
||||
range_extn[i].thunk_idx = thunk.thunk_idx;
|
||||
range_extn[i].thunk_idx = thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
@@ -184,19 +168,19 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> m = osec.members;
|
||||
template <>
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
using Thunk = RangeExtensionThunk<E>;
|
||||
|
||||
std::span<InputSection<E> *> m = members;
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
m[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)m.size(), [&](i64 i) {
|
||||
m[i]->offset = -1;
|
||||
tbb::parallel_for_each(m, [](InputSection<E> *isec) {
|
||||
isec->offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
@@ -206,7 +190,7 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// Input sections between B and C are in the current batch.
|
||||
//
|
||||
// A is the input section with the smallest address than can reach
|
||||
// anywhere from the current batch.
|
||||
// from the current batch.
|
||||
//
|
||||
// D is the input section with the largest address such that the thunk
|
||||
// is reachable from the current batch if it's inserted right before D.
|
||||
@@ -214,22 +198,28 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// ................................ <input sections> ............
|
||||
// A B C D
|
||||
// ^ We insert a thunk for the current batch just before D
|
||||
// <---> The current batch, which is smaller than batch_size
|
||||
// <--------> Smaller than max_distance
|
||||
// <--------> Smaller than max_distance
|
||||
// <---> The current batch, which is smaller than BATCH_SIZE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <-------------> Reachable from the current batch
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
i64 thunk_idx = 0;
|
||||
|
||||
// The smallest thunk index that is reachable from the current batch.
|
||||
i64 t = 0;
|
||||
|
||||
while (b < m.size()) {
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk at D.
|
||||
// Move D foward as far as we can jump from B to a thunk at D.
|
||||
auto d_thunk_end = [&] {
|
||||
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
|
||||
return align_to(d_end, Thunk::alignment) + max_thunk_size;
|
||||
};
|
||||
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance<E>()) {
|
||||
(b == d || d_thunk_end() <= m[b]->offset + max_distance())) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
@@ -240,34 +230,33 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// to make sure that there's at least one section between B and C
|
||||
// to ensure progress.
|
||||
c = b + 1;
|
||||
while (c < m.size() &&
|
||||
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size<E>)
|
||||
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < m.size() && m[a]->offset + max_distance<E>() < c_offset)
|
||||
while (a < b && m[a]->offset + max_distance() < c_offset)
|
||||
a++;
|
||||
|
||||
// Erase references to out-of-range thunks.
|
||||
while (thunk_idx < osec.thunks.size() &&
|
||||
osec.thunks[thunk_idx]->offset < m[a]->offset)
|
||||
reset_thunk(*osec.thunks[thunk_idx++]);
|
||||
while (t < thunks.size() && thunks[t]->offset < m[a]->offset)
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
|
||||
RangeExtensionThunk<E> *thunk =
|
||||
new RangeExtensionThunk<E>(osec, osec.thunks.size(), offset);
|
||||
osec.thunks.emplace_back(thunk);
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, Thunk::alignment);
|
||||
i64 thunk_idx = thunks.size();
|
||||
Thunk *thunk = new Thunk(*this, offset);
|
||||
thunks.emplace_back(thunk);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
// entries in the new thunk.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, *thunk);
|
||||
scan_rels(ctx, *isec, *thunk, thunk_idx);
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
// the thunk's size.
|
||||
assert(thunk->size() < max_thunk_size);
|
||||
offset += thunk->size();
|
||||
|
||||
@@ -280,7 +269,7 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; i < thunk->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *thunk->symbols[i];
|
||||
sym.extra.thunk_idx = thunk->thunk_idx;
|
||||
sym.extra.thunk_idx = thunk_idx;
|
||||
sym.extra.thunk_sym_idx = i;
|
||||
}
|
||||
|
||||
@@ -292,7 +281,7 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk->thunk_idx)
|
||||
if (range_extn[i].thunk_idx == thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
@@ -300,18 +289,16 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (thunk_idx < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[thunk_idx++]);
|
||||
while (t < thunks.size())
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
this->shdr.sh_size = offset;
|
||||
|
||||
for (InputSection<E> *isec : members)
|
||||
this->shdr.sh_addralign =
|
||||
std::max<u32>(this->shdr.sh_addralign, 1 << isec->p2align);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
|
||||
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+29
-28
@@ -19,10 +19,10 @@
|
||||
// image" in the PT_TLS segment. For each newly created thread including the
|
||||
// initial one, the runtime allocates a contiguous memory for an executable
|
||||
// and its depending shared libraries and copies template images there. That
|
||||
// per-thread memory is called the "TLS block". After allocating and
|
||||
// initializing a TLS block, the runtime sets a register to refer to the TLS
|
||||
// block, so that the thread-local variables are accessible relative to the
|
||||
// register.
|
||||
// per-thread memory is called the "initial TLS block". After allocating and
|
||||
// initializing the initial TLS block, the runtime sets a register to refer
|
||||
// to the initial TLS block, so that the thread-local variables are
|
||||
// accessible relative to the register.
|
||||
//
|
||||
// The register referring to the per-thread storage is called the Thread
|
||||
// Pointer (TP). TP is part of the thread's context. When the kernel
|
||||
@@ -37,8 +37,8 @@
|
||||
// follows:
|
||||
//
|
||||
// 1. If we are creating an executable, we know the exact size of the TLS
|
||||
// template image we are creating, and we know where the TP will be
|
||||
// set to after the template is copied to the TLS block. Therefore,
|
||||
// template image we are creating, and we know where the TP will be set
|
||||
// to after the template is copied to the initial TLS block. Therefore,
|
||||
// the TP-relative address of a TLV in the main executable is known at
|
||||
// link-time. That means, computing a TLV's address can be as easy as
|
||||
// `add %dst, %tp, <link-time constant>`.
|
||||
@@ -50,9 +50,9 @@
|
||||
// runtime knows the exact TP-relative address.
|
||||
//
|
||||
// We can solve the problem with an indirection. Specifically, for
|
||||
// each TLV whose TP-relative address is only known at process startup
|
||||
// each TLV whose TP-relative address is known only at process startup
|
||||
// time, we create a GOT entry to store its TP-relative address. We
|
||||
// also emit a dynamic relocation to let the runtime to fill the GOT
|
||||
// then emit a dynamic relocation to let the runtime to fill the GOT
|
||||
// entry with a TP-relative address.
|
||||
//
|
||||
// Computing a TLV address in this scheme needs at least two machine
|
||||
@@ -60,10 +60,10 @@
|
||||
// the GOT entry, and the second one adds the loaded value to TP.
|
||||
//
|
||||
// 3. Now, think about libraries that are dynamically loaded with dlopen.
|
||||
// The TLS block for such library has to be allocated separately from
|
||||
// the initial TLS block, so we now have two or more discontiguous
|
||||
// TLS blocks. There's no easy formula to compute an address of a TLV
|
||||
// in a separate TLS block.
|
||||
// The TLS block for such library may not be allocated next to the
|
||||
// initial TLS block, so we can have two or more discontiguous TLS
|
||||
// blocks. There's no easy formula to compute an address of a TLV in a
|
||||
// separate TLS block.
|
||||
//
|
||||
// The address of a TLV in a separate TLS block can be obtained by
|
||||
// calling the libc-provided function, __tls_get_addr(). The function
|
||||
@@ -160,19 +160,20 @@ u64 get_tp_addr(Context<E> &ctx) {
|
||||
// of TLV template image when copying TLVs to the TLS block, so we need
|
||||
// to offset it.
|
||||
return align_down(phdr->p_vaddr - sizeof(Word<E>) * 2, phdr->p_align);
|
||||
} else if constexpr (is_ppc<E> || is_m68k<E> || is_mips<E>) {
|
||||
// On PPC and m68k, TP is 0x7000 (28 KiB) past the beginning of the TLV
|
||||
// block to maximize the addressable range for load/store instructions
|
||||
// with 16-bits signed immediates. It's not exactly 0x8000 (32 KiB) off
|
||||
// because there's a small implementation-defined piece of data before
|
||||
// the TLV block, and the runtime wants to access them efficiently too.
|
||||
} else if constexpr (is_ppc<E> || is_m68k<E>) {
|
||||
// On PowerPC and m68k, TP is 0x7000 (28 KiB) past the beginning
|
||||
// of the TLV block to maximize the addressable range of load/store
|
||||
// instructions with 16-bits signed immediates. It's not exactly 0x8000
|
||||
// (32 KiB) off because there's a small implementation-defined piece of
|
||||
// data before the initial TLV block, and the runtime wants to access
|
||||
// them efficiently too.
|
||||
return phdr->p_vaddr + 0x7000;
|
||||
} else {
|
||||
// RISC-V just uses the beginning of the main executable's TLV block as
|
||||
// TP. RISC-V load/store instructions usually take 12-bits signed
|
||||
// immediates, so the beginning of the TLS block ± 2 KiB is accessible
|
||||
// with a single load/store instruction.
|
||||
static_assert(is_riscv<E>);
|
||||
// RISC-V and LoongArch just uses the beginning of the main executable's
|
||||
// TLV block as TP. Their load/store instructions usually take 12-bits
|
||||
// signed immediates, so the beginning of the TLS block ± 2 KiB is
|
||||
// accessible with a single load/store instruction.
|
||||
static_assert(is_riscv<E> || is_loongarch<E>);
|
||||
return phdr->p_vaddr;
|
||||
}
|
||||
}
|
||||
@@ -185,12 +186,12 @@ u64 get_dtp_addr(Context<E> &ctx) {
|
||||
if (!phdr)
|
||||
return 0;
|
||||
|
||||
if constexpr (is_ppc<E> || is_m68k<E> || is_mips<E>) {
|
||||
// On PPC64 and m68k, R_DTPOFF is resolved to the address 0x8000
|
||||
if constexpr (is_ppc<E> || is_m68k<E>) {
|
||||
// On PowerPC and m68k, R_DTPOFF is resolved to the address 0x8000
|
||||
// (32 KiB) past the start of the TLS block. The bias maximizes the
|
||||
// accessible range for load/store instructions with 16-bits signed
|
||||
// immediates. That is, if the offset were right at the beginning of
|
||||
// the start of the TLS block, the half of addressible space (negative
|
||||
// accessible range of load/store instructions with 16-bits signed
|
||||
// immediates. That is, if the offset were right at the beginning of the
|
||||
// start of the TLS block, the half of addressible space (negative
|
||||
// immediates) would have been wasted.
|
||||
return phdr->p_vaddr + 0x8000;
|
||||
} else if constexpr (is_riscv<E>) {
|
||||
|
||||
+13
-9
@@ -12,39 +12,43 @@ set -x
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
|
||||
apt-get install -y cmake gcc g++ g++-10
|
||||
apt-get install -y file
|
||||
;;
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
|
||||
apt-get install -y cmake gcc g++
|
||||
apt-get install -y file
|
||||
;;
|
||||
fedora-*)
|
||||
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
|
||||
dnf install -y gcc-g++ cmake
|
||||
dnf install -y glibc-static file libstdc++-static diffutils util-linux
|
||||
;;
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
|
||||
zypper install -y make cmake gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
opensuse-tumbleweed-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
|
||||
zypper install -y make cmake gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo-*)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake sys-libs/zlib
|
||||
emerge dev-util/cmake
|
||||
;;
|
||||
arch-*)
|
||||
arch-* | archarm-* | artix-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
|
||||
pacman -S --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
void-*)
|
||||
xbps-install -Sy xbps
|
||||
xbps-install -Sy bash make cmake openssl-devel zlib-devel gcc
|
||||
xbps-install -Sy bash make cmake gcc
|
||||
xbps-install -Sy tar diffutils util-linux
|
||||
;;
|
||||
alpine-*)
|
||||
apk update
|
||||
apk add bash make linux-headers cmake gcc g++
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
|
||||
# to enable cross-target tests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
+58
-40
@@ -1,73 +1,91 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
|
||||
|
||||
function(add_target TRIPLE)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "ppc64")
|
||||
set(HOST powerpc64)
|
||||
endif()
|
||||
# CMAKE_HOST_SYSTEM_PROCESSOR returns "aarch64" on ARM32 userland
|
||||
# with ARM64 kernel. Here, we run `cc -dumpmachine` to determine the
|
||||
# compiler's default target.
|
||||
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
||||
RESULT_VARIABLE EXIT_CODE
|
||||
OUTPUT_VARIABLE ARCH
|
||||
ERROR_QUIET)
|
||||
if(NOT EXIT_CODE AND ARCH MATCHES "([^-]+).*")
|
||||
set(HOST ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "${HOST}-.*")
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "i386")
|
||||
set(HOST i686)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "powerpc64")
|
||||
set(HOST ppc64)
|
||||
elseif(${HOST} STREQUAL "powerpc64le")
|
||||
set(HOST ppc64le)
|
||||
endif()
|
||||
|
||||
function(add_target ARCH TRIPLE)
|
||||
if(${ARCH} STREQUAL ${HOST})
|
||||
set(IS_NATIVE 1)
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "([^-]+)-.")
|
||||
set(MACHINE ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
|
||||
if(IS_NATIVE OR MOLD_ENABLE_QEMU_TESTS)
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "*.sh")
|
||||
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"${MACHINE}_*.sh")
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "${ARCH}_*.sh")
|
||||
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${MACHINE}-${TEST}")
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}-${TEST}")
|
||||
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT "HOST=${HOST}")
|
||||
|
||||
if(IS_NATIVE)
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
SKIP_REGULAR_EXPRESSION "skipped")
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY SKIP_REGULAR_EXPRESSION "skipped")
|
||||
else()
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
ENVIRONMENT "TRIPLE=${TRIPLE}")
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT "TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_target(x86_64-linux-gnu)
|
||||
add_target(i686-linux-gnu)
|
||||
add_target(aarch64-linux-gnu)
|
||||
add_target(arm-linux-gnueabihf)
|
||||
add_target(riscv64-linux-gnu)
|
||||
add_target(powerpc-linux-gnu)
|
||||
add_target(powerpc64-linux-gnu)
|
||||
add_target(powerpc64le-linux-gnu)
|
||||
add_target(sparc64-linux-gnu)
|
||||
add_target(s390x-linux-gnu)
|
||||
add_target(m68k-linux-gnu)
|
||||
add_target(sh4-linux-gnu)
|
||||
add_target(alpha-linux-gnu)
|
||||
add_target(x86_64 x86_64-linux-gnu)
|
||||
add_target(i686 i686-linux-gnu)
|
||||
add_target(aarch64 aarch64-linux-gnu)
|
||||
add_target(arm arm-linux-gnueabihf)
|
||||
add_target(riscv64 riscv64-linux-gnu)
|
||||
add_target(ppc powerpc-linux-gnu)
|
||||
add_target(ppc64 powerpc64-linux-gnu)
|
||||
add_target(ppc64le powerpc64le-linux-gnu)
|
||||
add_target(sparc64 sparc64-linux-gnu)
|
||||
add_target(s390x s390x-linux-gnu)
|
||||
add_target(sh4 sh4-linux-gnu)
|
||||
add_target(alpha alpha-linux-gnu)
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_target(riscv32-linux-gnu)
|
||||
add_target(riscv32 riscv32-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
|
||||
add_target(powerpc64le_power10-linux-gnu)
|
||||
add_target(ppc64le powerpc64le_power10-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_M68K "Enable tests for m68k" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_M68K)
|
||||
add_target(m68k m68k-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_LOONGARCH "Enable tests for LoongArch" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH OR ${HOST} STREQUAL loongarch64)
|
||||
add_target(loongarch64 loongarch64-linux-gnu)
|
||||
endif()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
|
||||
.global foo
|
||||
.type foo, %function
|
||||
.variant_pcs foo
|
||||
foo:
|
||||
ret
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
readelf -W --dyn-syms $t/b.so | grep foo | grep -q '[VARIANT_PCS]'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so
|
||||
readelf -W --dynamic $t/exe | grep -q AARCH64_VARIANT_PCS
|
||||
@@ -6,8 +6,7 @@
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = s390x ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[ $MACHINE = mips64el ] && skip
|
||||
[ $MACHINE = mips64 ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
|
||||
.globl foo
|
||||
|
||||
@@ -22,6 +22,5 @@ $CC -B. -o $t/exe $t/a.o \
|
||||
|
||||
$OBJDUMP -dr $t/exe | grep -F -A7 '<fn1$thunk>:' > $t/log
|
||||
|
||||
grep -Eq 'mov\s+ip, pc' $t/log
|
||||
grep -Eq 'bx\s+ip' $t/log
|
||||
grep -Eq 'add\s+ip, ip, pc' $t/log
|
||||
grep -Eq 'bx\s+pc' $t/log
|
||||
grep -Eq 'add\s+pc, ip, pc' $t/log
|
||||
@@ -2,6 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] || skip
|
||||
is_musl && skip
|
||||
|
||||
echo 'int main() {}' | $GCC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|
||||
|| skip
|
||||
|
||||
@@ -15,10 +15,7 @@ int fn2();
|
||||
int main() { fn2(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -L$t -Wl,--as-needed -lbar -Wl,--allow-shlib-undefined
|
||||
readelf -W --dynamic $t/exe1 > $t/log1
|
||||
! grep -q libfoo $t/log1 || false
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
|
||||
readelf -W --dynamic $t/exe2 > $t/log2
|
||||
grep -q libfoo $t/log2
|
||||
$CC -B. -o $t/exe $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
|
||||
readelf -W --dynamic $t/exe > $t/log2
|
||||
grep -q libbar $t/log2
|
||||
! grep -q libfoo $t/log2 || false
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
int foo() {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
__attribute__((weak)) int foo();
|
||||
|
||||
int bar() {
|
||||
if (foo) return foo();
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -xc -c -o $t/c.o -
|
||||
int bar();
|
||||
|
||||
int main() {
|
||||
return bar();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/libfoo.so $t/a.o
|
||||
$CC -B. -shared -o $t/libbar.so $t/b.o
|
||||
$CC -B. -o $t/exe $t/c.o -L$t -Wl,--as-needed -lfoo -lbar
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
! grep libfoo.so $t/log || false
|
||||
grep -q libbar.so $t/log
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/libfoo.so -Wl,--soname,libfoo.so -xc -
|
||||
int foo() { return 3; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/libbar.so -Wl,--soname,libbar.so -xc -
|
||||
int bar() { return 3; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
|
||||
int foo();
|
||||
int baz() { return foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/libbaz.so -Wl,--soname,libbaz.so -L$t $t/a.o -lfoo
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
#include <stdio.h>
|
||||
int baz();
|
||||
int main() {
|
||||
printf("%d\n", baz());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/b.o -L$t -Wl,--as-needed -lbaz -lbar -lfoo
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
grep -q libbaz $t/log || false
|
||||
! grep -q libbar $t/log || false
|
||||
grep -q libfoo $t/log || false
|
||||
+28
-2
@@ -14,10 +14,12 @@ canonical_name() {
|
||||
esac
|
||||
}
|
||||
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
if [ -z "$HOST" ]; then
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
fi
|
||||
|
||||
# Set tool names
|
||||
if [ "$TRIPLE" = "" ]; then
|
||||
if [ -z "$TRIPLE" ]; then
|
||||
MACHINE=$HOST
|
||||
TESTDIR=out/test/elf/$HOST
|
||||
CC="${TEST_CC:-cc}"
|
||||
@@ -53,6 +55,19 @@ else
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
fi
|
||||
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i386 -o $MACHINE = arm ]; then
|
||||
tlsdesc_opt=-mtls-dialect=gnu2
|
||||
elif [ $MACHINE = aarch64 ]; then
|
||||
tlsdesc_opt=-mtls-dialect=desc
|
||||
fi
|
||||
|
||||
if [ $MACHINE = ppc64le -a -e /proc/cpuinfo ] && grep -q POWER10 /proc/cpuinfo; then
|
||||
CC="$CC -mcpu=power10"
|
||||
CXX="$CXX -mcpu=power10"
|
||||
GCC="$GCC -mcpu=power10"
|
||||
GXX="$GXX -mcpu=power10"
|
||||
fi
|
||||
|
||||
# Common functions
|
||||
test_cflags() {
|
||||
echo 'int main() {}' | $CC "$@" -o /dev/null -xc - >& /dev/null
|
||||
@@ -63,6 +78,17 @@ supports_ifunc() {
|
||||
$CC -c -o /dev/null -xc - >& /dev/null
|
||||
}
|
||||
|
||||
is_musl() {
|
||||
ldd --help 2>&1 | grep -q musl
|
||||
}
|
||||
|
||||
supports_tlsdesc() {
|
||||
# musl's tlsdesc on arm32 seems to be broken
|
||||
[ $MACHINE = arm ] && is_musl && return 1
|
||||
|
||||
[ -n "$tlsdesc_opt" ]
|
||||
}
|
||||
|
||||
skip() {
|
||||
echo skipped
|
||||
trap - EXIT
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/a.so -xc -
|
||||
__attribute__((aligned(32))) int foo = 5;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
extern int foo;
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern 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() {
|
||||
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
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
|
||||
__attribute__((section (".data.rel.ro"))) char readonly[100] = "abc";
|
||||
char readwrite[100] = "abc";
|
||||
EOF
|
||||
|
||||
$CC -B. $t/a.o $t/b.so -o $t/exe -no-pie
|
||||
$QEMU $t/exe | grep -q '^sigsegv 0 1$'
|
||||
@@ -6,8 +6,8 @@ void foo() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so'
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o \
|
||||
-Wl,--soname=bar -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar'
|
||||
+2
-1
@@ -2,10 +2,11 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
# musl libc does not support init/fini on ARM
|
||||
# https://github.com/rui314/mold/issues/951
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && ldd --help 2>&1 | grep -q musl && skip
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
rm -f $t/b.a
|
||||
ar rcs $t/b.a $t/a.o
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.a $t/b.a
|
||||
|
||||
! $CC -B. -o $t/exe $t/c.o -Wl,--push-state,--whole-archive \
|
||||
$t/b.a $t/b.a -Wl,--pop-state 2> $t/log || false
|
||||
|
||||
grep -q 'duplicate symbol:.* foo' $t/log
|
||||
@@ -10,8 +10,8 @@ EOF
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; bar; };
|
||||
@@ -20,17 +20,17 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
grep -q ' bar$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
grep -q ' bar' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
grep -q ' bar$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
grep -q ' bar' $t/log2
|
||||
|
||||
$CC -B. -o $t/exe3 $t/a.o -Wl,--export-dynamic-symbol=foo,--export-dynamic-symbol=bar
|
||||
|
||||
readelf --dyn-syms $t/exe3 > $t/log3
|
||||
grep -q ' foo$' $t/log3
|
||||
grep -q ' bar$' $t/log3
|
||||
grep -q ' foo' $t/log3
|
||||
grep -q ' bar' $t/log3
|
||||
@@ -14,8 +14,8 @@ EOF
|
||||
$CXX -B. -o $t/exe $t/a.o $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; extern "C++" { "baz(int)"; }; };
|
||||
@@ -24,13 +24,13 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
! grep -q ' bar$' $t/log1 || false
|
||||
grep -q ' _Z3bazi$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
! grep -q ' bar' $t/log1 || false
|
||||
grep -q ' _Z3bazi' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
! grep -q ' bar$' $t/log2 || false
|
||||
grep -q ' _Z3bazi$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
! grep -q ' bar' $t/log2 || false
|
||||
grep -q ' _Z3bazi' $t/log2
|
||||
+12
-12
@@ -21,19 +21,19 @@ EOF
|
||||
$CC -B. -Wl,--dynamic-list=$t/dyn -o $t/exe1 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' xyz$' $t/log1
|
||||
! grep -q ' foobarzx$' $t/log1 || false
|
||||
grep -q ' foobarcx$' $t/log1
|
||||
grep -q ' foo123bar456bx$' $t/log1
|
||||
! grep -q ' foo123bar456c$' $t/log1 || false
|
||||
! grep -q ' foo123bar456x$' $t/log1 || false
|
||||
grep -q ' xyz' $t/log1
|
||||
! grep -q ' foobarzx' $t/log1 || false
|
||||
grep -q ' foobarcx' $t/log1
|
||||
grep -q ' foo123bar456bx' $t/log1
|
||||
! grep -q ' foo123bar456c' $t/log1 || false
|
||||
! grep -q ' foo123bar456x' $t/log1 || false
|
||||
|
||||
$CC -B. -Wl,--export-dynamic-symbol-list=$t/dyn -o $t/exe2 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' xyz$' $t/log2
|
||||
! grep -q ' foobarzx$' $t/log2 || false
|
||||
grep -q ' foobarcx$' $t/log2
|
||||
grep -q ' foo123bar456bx$' $t/log2
|
||||
! grep -q ' foo123bar456c$' $t/log2 || false
|
||||
! grep -q ' foo123bar456x$' $t/log2 || false
|
||||
grep -q ' xyz' $t/log2
|
||||
! grep -q ' foobarzx' $t/log2 || false
|
||||
grep -q ' foobarcx' $t/log2
|
||||
grep -q ' foo123bar456bx' $t/log2
|
||||
! grep -q ' foo123bar456c' $t/log2 || false
|
||||
! grep -q ' foo123bar456x' $t/log2 || false
|
||||
@@ -11,5 +11,5 @@ EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar1$'
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar2$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar1'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar2'
|
||||
@@ -26,7 +26,7 @@ $CXX -c -o $t/c.o $t/a.cc -fno-PIC
|
||||
$CXX -B. -o $t/exe1 $t/b.o $static
|
||||
$QEMU $t/exe1
|
||||
|
||||
$CXX -B. -o $t/exe2 $t/c.o $static
|
||||
$CXX -B. -o $t/exe2 $t/c.o -no-pie $static
|
||||
$QEMU $t/exe2
|
||||
|
||||
$CXX -B. -o $t/exe3 $t/b.o -pie
|
||||
@@ -38,7 +38,7 @@ $QEMU $t/exe4
|
||||
$CXX -B. -o $t/exe5 $t/b.o -pie -Wl,--gc-sections
|
||||
$QEMU $t/exe5
|
||||
|
||||
$CXX -B. -o $t/exe6 $t/c.o $static -Wl,--gc-sections
|
||||
$CXX -B. -o $t/exe6 $t/c.o -no-pie $static -Wl,--gc-sections
|
||||
$QEMU $t/exe6
|
||||
|
||||
if [ $MACHINE = x86_64 ]; then
|
||||
@@ -54,7 +54,7 @@ fi
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = aarch64 ]; then
|
||||
$CXX -c -o $t/e.o $t/a.cc -mcmodel=large -fno-PIC
|
||||
|
||||
$CXX -B. -o $t/exe9 $t/e.o $static
|
||||
$CXX -B. -o $t/exe9 $t/e.o -no-pie $static
|
||||
$QEMU $t/exe9
|
||||
|
||||
$CXX -B. -o $t/exe10 $t/e.o -no-pie
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
test_cflags -gdwarf-5 -g -gdwarf64 || skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
void fn3();
|
||||
|
||||
static void fn2() {
|
||||
fn3();
|
||||
}
|
||||
|
||||
void fn1() {
|
||||
fn2();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/b.c
|
||||
void fn5();
|
||||
|
||||
static void fn4() {
|
||||
fn5();
|
||||
}
|
||||
|
||||
void fn3() {
|
||||
fn4();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/c.c
|
||||
void fn7();
|
||||
|
||||
static void fn6() {
|
||||
fn7();
|
||||
}
|
||||
|
||||
void fn5() {
|
||||
fn6();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/d.c
|
||||
#include <stdio.h>
|
||||
void trap() {}
|
||||
|
||||
static void fn8() {
|
||||
printf("Hello world\n");
|
||||
trap();
|
||||
}
|
||||
|
||||
void fn7() {
|
||||
fn8();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -c -o $t/a.o $t/a.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/b.o $t/b.c -fPIC -g -ggnu-pubnames -gdwarf-4 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/c.o $t/c.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64
|
||||
$CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
|
||||
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o
|
||||
|
||||
$CC -B. -shared -o $t/f.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
|
||||
readelf -WS $t/f.so 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
cat <<EOF | $CC -c -o $t/g.o -fPIC -g -ggnu-pubnames -gdwarf-5 -xc - -gz
|
||||
void fn1();
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
# Older versions of gdb are buggy that they complain DWARF64 debug sections
|
||||
# even without .gdb_index. Skip if such version.
|
||||
$CC -B. -o $t/exe1 $t/e.so $t/g.o
|
||||
DEBUGINFOD_URLS= gdb $t/exe1 -nx -batch -ex 'b main' -ex r -ex quit >& $t/log1
|
||||
grep -q 'DW_FORM_line_strp pointing outside of .debug_line_str' $t/log1 && skip
|
||||
|
||||
# We are using a recent version of gdb.
|
||||
$CC -B. -o $t/exe2 $t/f.so $t/g.o -Wl,--gdb-index
|
||||
readelf -WS $t/exe2 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
|
||||
DEBUGINFOD_URLS= gdb $t/exe2 -nx -batch -ex 'b main' -ex r -ex 'b trap' \
|
||||
-ex c -ex bt -ex quit >& $t/log2
|
||||
|
||||
grep -q 'fn8 () at .*/d.c:6' $t/log2
|
||||
grep -q 'fn7 () at .*/d.c:10' $t/log2
|
||||
grep -q 'fn6 () at .*/c.c:4' $t/log2
|
||||
grep -q 'fn5 () at .*/c.c:8' $t/log2
|
||||
grep -q 'fn4 () at .*/b.c:4' $t/log2
|
||||
grep -q 'fn3 () at .*/b.c:8' $t/log2
|
||||
grep -q 'fn2 () at .*/a.c:4' $t/log2
|
||||
grep -q 'fn1 () at .*/a.c:8' $t/log2
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# MIPS ABIs are not compatible with .gnu.hash
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# GCC 11 or older uses R_390_PLT32DBL to take an address of a function
|
||||
# instead of R_390_PC32DBL
|
||||
if [ $MACHINE = s390x ]; then
|
||||
echo 'void *foo() { return foo; }' | $CC -c -o $t/a.o -xc -
|
||||
readelf -r $t/a.o | grep -q R_390_PLT32DBL && skip
|
||||
fi
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -ffunction-sections -fdata-sections -xc -
|
||||
int bar() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
int foo1(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
|
||||
int foo2(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
|
||||
int foo3(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -ffunction-sections -fdata-sections -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
int foo1();
|
||||
int foo2();
|
||||
int foo3();
|
||||
|
||||
int main() {
|
||||
printf("%d %d\n", foo1 == foo2, foo2 == foo3);
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 -Wl,-icf=safe $t/a.o $t/b.o
|
||||
$QEMU $t/exe1 | grep -q '^0 0$'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -ffunction-sections -fdata-sections -xc -
|
||||
int foo1();
|
||||
int foo2();
|
||||
int foo3();
|
||||
|
||||
int main() {
|
||||
foo1();
|
||||
foo2();
|
||||
foo3();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe2 -Wl,-icf=safe $t/a.o $t/c.o
|
||||
$QEMU $t/exe2 > $t/log2
|
||||
! grep foo2 $t/log2 || false
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
typedef void Func();
|
||||
void foo();
|
||||
Func *get_foo() { return foo; }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
__attribute__((ifunc("resolve_foo"))) void foo(void);
|
||||
void real_foo(void) { printf("foo "); }
|
||||
Func *resolve_foo() { return real_foo; }
|
||||
|
||||
Func *get_foo();
|
||||
|
||||
int main() {
|
||||
printf("%p %p\n", foo, get_foo());
|
||||
foo();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so -no-pie
|
||||
$QEMU $t/exe1 | grep -Eq '^(\S+) \1'
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
__attribute__((ifunc("resolve_foo"))) void foo(void);
|
||||
void real_foo(void) { printf("foo "); }
|
||||
Func *resolve_foo() { return real_foo; }
|
||||
|
||||
__attribute__((ifunc("resolve_bar"))) void bar(void);
|
||||
void real_bar(void) { printf("bar "); }
|
||||
Func *resolve_bar() { return real_bar; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
typedef void Func();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
|
||||
Func *get_foo() { return foo; }
|
||||
Func *get_bar() { return bar; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
Func *get_foo();
|
||||
Func *get_bar();
|
||||
|
||||
int main() {
|
||||
printf("%p %p %p %p\n", foo, get_foo(), bar, get_bar());
|
||||
foo();
|
||||
bar();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -no-pie
|
||||
$QEMU $t/exe1 | grep -Eq '^(\S+) \1 (\S+) \2'
|
||||
|
||||
readelf --dynamic $t/exe1 > $t/log1
|
||||
! grep -q TEXTREL $t/log1 || false
|
||||
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# musl does not support GNU-style init/fini priorities
|
||||
ldd --help 2>&1 | grep -q musl && skip
|
||||
is_musl && skip
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -mcmodel=extreme
|
||||
#include <stdio.h>
|
||||
char msg[] = "Hello world\n";
|
||||
int main() { printf(msg); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -flto -c -o $t/a.o -xc -
|
||||
void _start() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe -flto $t/a.o -nostdlib
|
||||
readelf -Ws $t/exe | grep -Eq ' _start'
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
$CC -o $t/c.o -c -xc /dev/null -fPIC
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so -pie
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
|
||||
$CC -o $t/c.o -c -xc /dev/null -fno-PIC
|
||||
$CC -B. -o $t/exe2 $t/c.o $t/b.so -no-pie
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
@@ -4,9 +4,16 @@
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.globl x\0\nx\0: .word 0\n/g' | \
|
||||
$CC -c -xassembler -o $t/a.o -
|
||||
echo 'foo = 0x1000' > $t/a.s
|
||||
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.globl x\0\nx\0: .word 0\n/g' >> $t/a.s
|
||||
$CC -c -xassembler -o $t/a.o $t/a.s
|
||||
|
||||
./mold --relocatable -o $t/b.o $t/a.o
|
||||
readelf -WS $t/b.o | grep -Fq .data.100000
|
||||
readelf -Ws $t/b.o | grep -Fq 'GLOBAL DEFAULT 100000'
|
||||
|
||||
readelf -WS $t/b.o > $t/log1
|
||||
grep -Fq .data.100000 $t/log1
|
||||
|
||||
readelf -Ws $t/b.o > $t/log2
|
||||
grep -Fq 'GLOBAL DEFAULT 100000' $t/log2
|
||||
grep -Fq 'ABS foo' $t/log2
|
||||
! grep -Fq 'ABS x68966' $t/log2 || false
|
||||
@@ -8,6 +8,7 @@
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -shared -o $t/a.so -xc -
|
||||
int foo = 3;
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
# BusyBox's grep can't handle capture groups (e.g. \1, \2 ...)
|
||||
grep --version 2>&1 | grep BusyBox && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
|
||||
#include <stdio.h>
|
||||
|
||||
void f0();
|
||||
void f1();
|
||||
void f2();
|
||||
void f3();
|
||||
void f4();
|
||||
void f5();
|
||||
void f6();
|
||||
void f7();
|
||||
void f8();
|
||||
void f9();
|
||||
void f10();
|
||||
void f11();
|
||||
void f12();
|
||||
void f13();
|
||||
void f14();
|
||||
void f15();
|
||||
void f16();
|
||||
void f17();
|
||||
void f18();
|
||||
void f19();
|
||||
|
||||
void f0(int x) { printf("0 "); if (!x) f9(); }
|
||||
void space0() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f1(int x) { printf("1 "); f8(x); }
|
||||
void space1() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f2(int x) { printf("2 "); f7(x); }
|
||||
void space2() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f3(int x) { printf("3 "); f6(x); }
|
||||
void space3() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f4(int x) { printf("4 "); f5(x); }
|
||||
void space4() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f5(int x) { printf("5 "); f10(x); }
|
||||
void space5() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f6(int x) { printf("6 "); f4(x); }
|
||||
void space6() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f7(int x) { printf("7 "); f3(x); }
|
||||
void space7() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f8(int x) { printf("8 "); f2(x); }
|
||||
void space8() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f9(int x) { printf("9 "); f1(x); }
|
||||
void space9() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f10(int x) { printf("10 "); f19(x); }
|
||||
void space10() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f11(int x) { printf("11 "); f18(x); }
|
||||
void space11() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f12(int x) { printf("12 "); f17(x); }
|
||||
void space12() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f13(int x) { printf("13 "); f16(x); }
|
||||
void space13() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f14(int x) { printf("14 "); f15(x); }
|
||||
void space14() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f15(int x) { printf("15 "); f0(x + 1); }
|
||||
void space15() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f16(int x) { printf("16 "); f14(x); }
|
||||
void space16() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f17(int x) { printf("17 "); f13(x); }
|
||||
void space17() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f18(int x) { printf("18 "); f12(x); }
|
||||
void space18() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f19(int x) { printf("19 "); f11(x); }
|
||||
void space19() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
int main() {
|
||||
f0(0);
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$QEMU $t/exe | grep -Eq '^0 9 1 8 2 7 3 6 4 5 10 19 11 18 12 17 13 16 14 15 0 $'
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
# Ubuntu 22.04 SH4 GCC is broken
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -xc++ -
|
||||
void hello();
|
||||
void world();
|
||||
|
||||
template <typename T>
|
||||
struct Foo {
|
||||
Foo() { hello(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Bar {
|
||||
Bar() { world(); }
|
||||
};
|
||||
|
||||
void baz() {
|
||||
Foo<int> foo;
|
||||
Bar<int> bar;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/b.o -xc++ -
|
||||
#include <iostream>
|
||||
|
||||
void hello() { std::cout << "Hello "; }
|
||||
void world() { std::cout << "world\n"; }
|
||||
void baz();
|
||||
|
||||
int main() {
|
||||
baz();
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold --relocatable -o $t/c.o $t/a.o
|
||||
./mold --relocatable -o $t/d.o $t/b.o
|
||||
|
||||
$CXX -B. -o $t/exe $t/c.o $t/d.o
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xassembler -
|
||||
.section .rodata.str1.1,"aMS",@progbits,1
|
||||
val1:
|
||||
.ascii "Hello \0"
|
||||
|
||||
.section .rodata.str1.1,"aMS",@progbits,1
|
||||
val5:
|
||||
.ascii "World \0"
|
||||
EOF
|
||||
|
||||
./mold --relocatable -o $t/b.o $t/a.o
|
||||
|
||||
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b0\b.*Hello'
|
||||
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b7\b.*World'
|
||||
@@ -17,4 +17,4 @@ EOF
|
||||
|
||||
echo "'$t/b.o' '$t/c.o'" > $t/rsp
|
||||
|
||||
$CC -o $t/exe $t/a.o -Wl,@$t/rsp
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
echo "'$t/b.o'" > $t/rsp1
|
||||
echo "@$t/rsp1" > $t/rsp2
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp2
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -march=rv64imafd_xtheadba1p5 -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
# The compiler might not create .riscv.attributes
|
||||
readelf --sections $t/a.o | grep -Fq .riscv.attributes || skip
|
||||
|
||||
cat <<EOF | $CC -march=rv64imafd_xtheadba2p0 -o $t/b.o -c -xc -
|
||||
void bar() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc -
|
||||
void _start() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -nostdlib -o $t/exe $t/a.o $t/c.o
|
||||
readelf -A $t/exe | grep -q '_xtheadba1p5"'
|
||||
|
||||
$CC -B. -nostdlib -o $t/exe $t/a.o $t/b.o $t/c.o
|
||||
readelf -A $t/exe | grep -q '_xtheadba2p0"'
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void _start() {}
|
||||
EOF
|
||||
|
||||
$STRIP --remove-section=.riscv.attributes $t/a.o
|
||||
|
||||
$CC -B. -nostdlib -o $t/exe $t/a.o
|
||||
|
||||
readelf -W --segments --sections $t/exe > $t/log
|
||||
! grep -F .riscv.attributes $t/log || false
|
||||
! grep -F RISCV_ATTR $t/log || false
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 ] || skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xassembler -
|
||||
.globl get_foo, get_foo2, get_bar, get_baz
|
||||
get_foo:
|
||||
lui a0, %hi(foo)
|
||||
add a0, a0, %lo(foo)
|
||||
ret
|
||||
get_foo2:
|
||||
lui a0, %hi(foo+0x10000000)
|
||||
add a0, a0, %lo(foo)
|
||||
ret
|
||||
get_bar:
|
||||
lui a0, %hi(bar)
|
||||
add a0, a0, %lo(bar)
|
||||
ret
|
||||
get_baz:
|
||||
lui a0, %hi(baz)
|
||||
add a0, a0, %lo(baz)
|
||||
ret
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xassembler -
|
||||
.globl foo, bar, baz
|
||||
foo = 0xf00
|
||||
bar = 0xba
|
||||
baz = 0x11beef
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
int get_foo();
|
||||
int get_foo2();
|
||||
int get_bar();
|
||||
int get_baz();
|
||||
|
||||
int main() {
|
||||
printf("%x %x %x %x\n", get_foo(), get_foo2(), get_bar(), get_baz());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -Wl,--no-relax
|
||||
$QEMU $t/exe1 | grep -q 'f00 10000f00 ba 11beef'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o $t/c.o
|
||||
$QEMU $t/exe2 | grep -q 'f00 10000f00 ba 11beef'
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
test_cflags -static || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xassembler -
|
||||
.globl foo
|
||||
.weak bar
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# ARM assembler has a differnet grammar than the others.
|
||||
# Concretely speaking, ARM as uses "@" as a start of a comment.
|
||||
[ $MACHINE = arm ] && skip
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/a.o -x assembler -
|
||||
.globl val1, val2, val3, val4, val5
|
||||
|
||||
.section .rodata.str1.1,"aMS",@progbits,1
|
||||
val1:
|
||||
.ascii "Hello \0"
|
||||
|
||||
.section .rodata.str4.4,"aMS",@progbits,4
|
||||
.align 4
|
||||
val2:
|
||||
.ascii "world \0\0\0\0"
|
||||
|
||||
.section .rodata.foo,"aMS",@progbits,1
|
||||
val3:
|
||||
.ascii "foobar\0"
|
||||
|
||||
.section .rodata.cst8,"aM",@progbits,8
|
||||
.align 8
|
||||
val4:
|
||||
.ascii "abcdefgh"
|
||||
|
||||
.section .rodatabar,"aMS",@progbits,1
|
||||
val5:
|
||||
.ascii "bar\0"
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/b.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
extern char val1, val2, val3, val4, val5;
|
||||
|
||||
int main() {
|
||||
printf("%p %p %p %p %p\n", &val1, &val2, &val3, &val4, &val5);
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
|
||||
readelf -p .rodata.str $t/exe | grep -q Hello
|
||||
readelf -p .rodata.str $t/exe | grep -q world
|
||||
readelf -p .rodata.str $t/exe | grep -q foobar
|
||||
readelf -p .rodata.cst $t/exe | grep -q abcdefgh
|
||||
readelf -p .rodatabar $t/exe | grep -q bar
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[ "$CC" = cc ] || skip
|
||||
|
||||
# ASAN doesn't work with LD_PRELOAD
|
||||
nm mold-wrapper.so | grep -q '__[at]san_init' && skip
|
||||
nm mold | grep -q '__[at]san_init' && skip
|
||||
|
||||
cat <<'EOF' | $CC -xc -c -o $t/a.o -
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
# but instead refers "function descriptors" in .opd.
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
# The crt*.o compiled with B26 caused far form GOT.
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
[ $MACHINE = arm ] && flags=-marm
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIC $flags -
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -g
|
||||
static void foo() {}
|
||||
void bar() {}
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,--strip-debug
|
||||
|
||||
readelf -W --sections $t/exe > $t/log
|
||||
! grep -Fq .debug_info $t/log || false
|
||||
|
||||
readelf -W --symbols $t/exe | grep -q ' bar'
|
||||
@@ -16,7 +16,7 @@ EOF
|
||||
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
|
||||
readelf -W --dyn-syms $t/c.so > $t/log
|
||||
|
||||
grep -q ' foo@TEST$' $t/log
|
||||
grep -q ' bar@TEST$' $t/log
|
||||
grep -q ' bar1$' $t/log
|
||||
! grep -q ' foo@@TEST$' $t/log || false
|
||||
grep -q ' foo@TEST' $t/log
|
||||
grep -q ' bar@TEST' $t/log
|
||||
grep -q ' bar1' $t/log
|
||||
! grep -q ' foo@@TEST' $t/log || false
|
||||
@@ -19,7 +19,7 @@ EOF
|
||||
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
|
||||
readelf -W --dyn-syms $t/c.so > $t/log
|
||||
|
||||
grep -q ' foo@@TEST1$' $t/log
|
||||
grep -q ' foo@TEST2$' $t/log
|
||||
grep -q ' foo@TEST3$' $t/log
|
||||
grep -q ' foo@@TEST1' $t/log
|
||||
grep -q ' foo@TEST2' $t/log
|
||||
grep -q ' foo@TEST3' $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
Loaded 100 of 946 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user