mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 13:01:26 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d99824c3d | ||
|
|
344dcff8e8 | ||
|
|
d0dffd5985 | ||
|
|
251041e371 | ||
|
|
4dd872ee8d | ||
|
|
bb1b9dda64 | ||
|
|
78ec306cf4 | ||
|
|
163c62f491 | ||
|
|
026fb54b9d | ||
|
|
cd162ba9f6 | ||
|
|
e08e7f6aa4 | ||
|
|
6be2e676d2 | ||
|
|
b6565025a3 | ||
|
|
118dc5de8e | ||
|
|
f3475d9f09 | ||
|
|
299904e56f | ||
|
|
fded2d8a77 | ||
|
|
f0e726024e | ||
|
|
3d29f1678d | ||
|
|
559f23ff81 | ||
|
|
3274bcbdf0 | ||
|
|
83dd353979 | ||
|
|
8bae43b842 | ||
|
|
4038cfd64d | ||
|
|
41079c64f5 | ||
|
|
02e410133b | ||
|
|
e70236a45a | ||
|
|
285c5bb509 | ||
|
|
975da12333 | ||
|
|
157b16adb5 | ||
|
|
564757a0f5 | ||
|
|
216b4a2325 | ||
|
|
954107e29c | ||
|
|
21e20e01da | ||
|
|
161cb6c99c | ||
|
|
21cfee556a | ||
|
|
06203c4a72 | ||
|
|
232067634f | ||
|
|
3f9aab7066 | ||
|
|
8b88595f26 | ||
|
|
fa09c562c9 | ||
|
|
9457d993c4 | ||
|
|
43810dfecd | ||
|
|
2d6061a233 | ||
|
|
3d6aad65b0 | ||
|
|
f7b1920f84 | ||
|
|
b42f978212 |
No files matched your search
@@ -5,7 +5,7 @@ apt-get install -y wget xz-utils
|
||||
|
||||
# Install a 32-bit RISC-V toolchain
|
||||
mkdir /rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
|
||||
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
@@ -18,7 +18,7 @@ done
|
||||
|
||||
# Install a LoongArch toolchain
|
||||
mkdir /larch
|
||||
wget -O- -q https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
|
||||
|
||||
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
|
||||
ln -sf /larch/target /usr/loongarch64-linux-gnu
|
||||
@@ -27,25 +27,39 @@ for i in gcc g++ objdump objcopy strip; do
|
||||
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
|
||||
done
|
||||
|
||||
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
|
||||
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
|
||||
chmod 755 /usr/local/bin/qemu-loongarch64
|
||||
|
||||
# Install ARM32 big-endian toolchain
|
||||
mkdir /armeb
|
||||
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
|
||||
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
|
||||
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
|
||||
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
|
||||
done
|
||||
|
||||
# Install ARM64 big-endian toolchain
|
||||
mkdir /aarch64be
|
||||
wget -O- -q https://sources.buildroot.net/toolchain-external-arm-aarch64-be/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64_be-none-linux-gnu.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /aarch64be/aarch64_be-none-linux-gnu/libc /usr/aarch64_be-linux-gnu
|
||||
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
|
||||
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
|
||||
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
|
||||
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
|
||||
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
|
||||
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /aarch64be/bin/aarch64_be-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
|
||||
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
|
||||
done
|
||||
|
||||
# Install SH4 big-endian toolchain
|
||||
mkdir /sh4aeb
|
||||
wget -O- -q https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
|
||||
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
|
||||
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
|
||||
@@ -57,5 +71,5 @@ done
|
||||
|
||||
# Install Intel SDE CPU emulator for CET-related tests
|
||||
mkdir /sde
|
||||
wget -O- -q https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
|
||||
wget -O- --progress=dot:mega https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
|
||||
ln -s /sde/sde64 /usr/bin
|
||||
+10
-5
@@ -44,7 +44,7 @@
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.37.1)
|
||||
project(mold VERSION 2.39.1)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
@@ -62,7 +62,11 @@ endif()
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold -Wl,--gdb-index)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold)
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Deb")
|
||||
target_link_options(mold PRIVATE -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
|
||||
@@ -310,10 +314,12 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
|
||||
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64)
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
|
||||
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
|
||||
LOONGARCH32 LOONGARCH64)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
src/arch-arm32.cc
|
||||
src/arch-arm64.cc
|
||||
src/arch-loongarch.cc
|
||||
src/arch-riscv.cc
|
||||
@@ -385,7 +391,6 @@ target_sources(mold PRIVATE
|
||||
lib/perf.cc
|
||||
lib/random.cc
|
||||
lib/tar.cc
|
||||
src/arch-arm32.cc
|
||||
src/arch-i386.cc
|
||||
src/arch-m68k.cc
|
||||
src/arch-ppc32.cc
|
||||
|
||||
@@ -120,7 +120,7 @@ accept an absolute path as an argument for `-fuse-ld`.
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
@@ -131,7 +131,7 @@ example above, we use `clang` as a linker driver since it always accepts the
|
||||
may be able to remove the `linker = "clang"` line.
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
|
||||
@@ -126,10 +126,10 @@ RUN mkdir /build && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build LLVM 19
|
||||
# Build LLVM 20
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-20.1.3.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir b && \
|
||||
cd b && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
|
||||
@@ -195,14 +195,22 @@ dest=mold-$version-$arch-linux
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp="$(git log -1 --format=%ci)"
|
||||
timestamp=$(git log -1 --format=%ct)
|
||||
|
||||
# Build mold in a container.
|
||||
mkdir -p dist
|
||||
|
||||
# If the host is ARM64, `uname -m` in an ARM32 container running on it
|
||||
# reports it not as ARM32 but as ARM64, which confuses BLAKE3's cmake script
|
||||
# and erroneously enables NEON SIMD instructions. `setarch` can be used
|
||||
# to change the output of `uname -m`.
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
|
||||
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image bash -c "
|
||||
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
|
||||
$setarch bash -c "
|
||||
set -e
|
||||
export SOURCE_DATE_EPOCH=$timestamp
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
|
||||
@@ -212,7 +220,7 @@ cmake -DMOLD_USE_MOLD=1 .
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest --output-on-failure -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date='$timestamp'
|
||||
find $dest -print | xargs touch --no-dereference --date=@$timestamp
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
|
||||
cp mold /dist
|
||||
sha256sum /dist/$dest.tar.gz
|
||||
|
||||
+2
-2
@@ -54,14 +54,14 @@ dir=gentoo/$git_hash
|
||||
|
||||
mkdir -p "$dir"/success "$dir"/failure
|
||||
|
||||
$podman nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
|
||||
$podman chrt --idle 0 nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".mold "$dir"/success
|
||||
else
|
||||
mv "$dir"/"$filename".mold "$dir"/failure
|
||||
fi
|
||||
|
||||
$podman nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
|
||||
$podman chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".ld "$dir"/success
|
||||
else
|
||||
|
||||
+168
-103
@@ -31,18 +31,21 @@
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = ARM32;
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
ul32 *arm = (ul32 *)loc;
|
||||
ul16 *thm = (ul16 *)loc;
|
||||
U32<E> *arm = (U32<E> *)loc;
|
||||
U16<E> *thm = (U16<E> *)loc;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
@@ -59,7 +62,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
return (il32)*arm;
|
||||
return (I32<E>)*arm;
|
||||
case R_ARM_THM_JUMP8:
|
||||
return sign_extend(thm[0], 8) << 1;
|
||||
case R_ARM_THM_JUMP11:
|
||||
@@ -125,7 +128,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
static void write_arm_mov(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b21(u8 *loc, u32 val) {
|
||||
@@ -135,7 +138,7 @@ static void write_thm_b21(u8 *loc, u32 val) {
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
@@ -149,7 +152,7 @@ static void write_thm_b25(u8 *loc, u32 val) {
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
@@ -160,7 +163,7 @@ static void write_thm_mov(u8 *loc, u32 val) {
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
@@ -184,13 +187,13 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = val;
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xff00) | bits(val, 8, 1);
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
@@ -200,7 +203,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
@@ -209,7 +212,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
write_arm_mov(loc, val);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
@@ -236,7 +239,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
static constexpr ul32 plt_entry[] = {
|
||||
@@ -249,13 +252,13 @@ static constexpr ul32 plt_entry[] = {
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -267,24 +270,16 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
*(ul32 *)loc = val;
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static Thunk<E> &get_reachable_thunk(OutputSection<E> &osec, u64 addr) {
|
||||
auto it = std::upper_bound(osec.thunks.begin(), osec.thunks.end(), addr,
|
||||
[](u64 addr, std::unique_ptr<Thunk<E>> &thunk) {
|
||||
return addr < thunk->get_addr();
|
||||
});
|
||||
return **it;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -312,7 +307,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
|
||||
|
||||
auto get_tlsdesc_trampoline_addr = [&] {
|
||||
return get_reachable_thunk(*output_section, P).get_addr();
|
||||
auto it = ranges::upper_bound(output_section->thunks, P, {},
|
||||
[](std::unique_ptr<Thunk<E>> &thunk) {
|
||||
return thunk->get_addr();
|
||||
});
|
||||
return (*it)->get_addr();
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -320,13 +319,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_TARGET1:
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -337,61 +336,61 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val2 = align_to(S + A - P, 4);
|
||||
|
||||
if (T && is_int(val1, 25)) {
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // BL
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, val1);
|
||||
} else if (!T && is_int(val2, 25)) {
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // BLX
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
|
||||
write_thm_b25(loc, val2);
|
||||
} else {
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // BL
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
*(U32<E> *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_ARM_GOTOFF32:
|
||||
*(ul32 *)loc = ((S + A) | T) - GOT;
|
||||
*(U32<E> *)loc = ((S + A) | T) - GOT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = GOT + G + A - P;
|
||||
*(U32<E> *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
*(U32<E> *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (is_int(val, 26)) {
|
||||
if (T) {
|
||||
*(ul32 *)loc = 0xfa00'0000; // BLX
|
||||
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = 0xfa00'0000; // BLX
|
||||
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(val, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -404,26 +403,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val = S + A - P;
|
||||
if (T || !is_int(val, 26))
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
check(S + A - P, -(1 << 8), 1 << 8);
|
||||
*(ul16 *)loc &= 0xff00;
|
||||
*(ul16 *)loc |= bits(S + A - P, 8, 1);
|
||||
*(U16<E> *)loc &= 0xff00;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc &= 0xf800;
|
||||
*(ul16 *)loc |= bits(S + A - P, 11, 1);
|
||||
*(U16<E> *)loc &= 0xf800;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
@@ -431,7 +430,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_ARM_THM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -454,8 +453,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc &= 0x8000'0000;
|
||||
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
*(U32<E> *)loc &= 0x8000'0000;
|
||||
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov(loc, (S + A) | T);
|
||||
@@ -473,19 +472,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_thm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
@@ -514,35 +513,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(ul32 *)loc = S - ctx.tp_addr;
|
||||
*(U32<E> *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = 0xeb00'0000; // bl 0
|
||||
*(ul32 *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // bl 0
|
||||
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
*(ul32 *)loc = 0xe320'f000; // nop
|
||||
*(U32<E> *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
|
||||
write_thm_b25(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
|
||||
// we use two instructions instead.
|
||||
*(ul16 *)loc = 0x4478; // add r0, pc
|
||||
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
*(U16<E> *)loc = 0x4478; // add r0, pc
|
||||
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
*(ul32 *)loc = 0x8000'f3af; // nop.w
|
||||
*(U32<E> *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -573,15 +572,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -703,7 +702,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
*(U32<E> *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
|
||||
buf += sizeof(entry);
|
||||
P += sizeof(entry);
|
||||
@@ -712,10 +711,14 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return EF_ARM_EABI_VER5;
|
||||
if constexpr (E::is_le)
|
||||
return EF_ARM_EABI_VER5;
|
||||
else
|
||||
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
|
||||
}
|
||||
|
||||
void create_arm_exidx_section(Context<E> &ctx) {
|
||||
template <>
|
||||
void create_arm_exidx_section<E>(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < ctx.chunks.size(); i++) {
|
||||
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
|
||||
|
||||
@@ -732,28 +735,20 @@ void create_arm_exidx_section(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::compute_section_size(Context<E> &ctx) {
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
output_section.compute_section_size(ctx);
|
||||
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::update_shdr(Context<E> &ctx) {
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (Chunk<E> *chunk = find_chunk(ctx, ".text"))
|
||||
this->shdr.sh_link = chunk->shndx;
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::remove_duplicate_entries(Context<E> &ctx) {
|
||||
this->shdr.sh_size = get_contents(ctx).size();
|
||||
}
|
||||
|
||||
void Arm32ExidxSection::copy_buf(Context<E> &ctx) {
|
||||
std::vector<u8> contents = get_contents(ctx);
|
||||
assert(this->shdr.sh_size == contents.size());
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
// Returns the end of the text segment
|
||||
static u64 get_text_end(Context<E> &ctx) {
|
||||
u64 ret = 0;
|
||||
@@ -772,7 +767,8 @@ static u64 get_text_end(Context<E> &ctx) {
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function returns contents of .ARM.exidx.
|
||||
std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
template <>
|
||||
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
@@ -788,8 +784,8 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
const u32 CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
ul32 addr;
|
||||
ul32 val;
|
||||
U32<E> addr;
|
||||
U32<E> val;
|
||||
};
|
||||
|
||||
// We reserve one extra slot for the sentinel
|
||||
@@ -820,19 +816,13 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
std::sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
|
||||
|
||||
// Remove duplicate adjacent entries. That is, if two adjacent functions
|
||||
// have the same compact unwind info or are both CANTUNWIND, we can
|
||||
// merge them into a single address range.
|
||||
auto it = std::unique(ent, ent + num_entries,
|
||||
[](const Entry &a, const Entry &b) {
|
||||
return a.val == b.val;
|
||||
});
|
||||
|
||||
num_entries = it - ent;
|
||||
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
|
||||
num_entries -= tail.size();
|
||||
buf.resize(num_entries * sizeof(Entry));
|
||||
|
||||
// Make addresses relative to themselves.
|
||||
@@ -846,4 +836,79 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
|
||||
this->shdr.sh_size = get_contents(ctx).size();
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
|
||||
std::vector<u8> contents = get_contents(ctx);
|
||||
assert(this->shdr.sh_size == contents.size());
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
#if MOLD_ARM32BE
|
||||
// Even though using ARM32 in big-endian mode is very rare, the processor
|
||||
// technically supports both little- and big-endian modes. There are two
|
||||
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
|
||||
// data are encoded in big-endian. In BE8, instructions are encoded in
|
||||
// little-endian, and only data is in big-endian. BE8 is the de facto
|
||||
// standard for ARMv6 or later. We support only BE8.
|
||||
//
|
||||
// A tricky thing is that instructions in an object file are always
|
||||
// big-endian if the file is compiled for big-endian mode. It is the
|
||||
// linker's responsibility to rewrite instructions from big-endian to
|
||||
// little-endian for an BE8 output. This function does that.
|
||||
//
|
||||
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
|
||||
// Thumb instructions, and data. We need to distinguish them to swap 4
|
||||
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
|
||||
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
|
||||
// and $d, respectively. We use mapping symbols to determine what to do
|
||||
// with the text section.
|
||||
void arm32be_swap_bytes(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
// Collect mapping symbols
|
||||
std::vector<Symbol<E> *> syms;
|
||||
|
||||
for (Symbol<E> *sym : file->get_local_syms())
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
if (std::string_view x = sym->name();
|
||||
x == "$a" || x.starts_with("$a.") ||
|
||||
x == "$t" || x.starts_with("$t.") ||
|
||||
x == "$d" || x.starts_with("$d."))
|
||||
syms.push_back(sym);
|
||||
|
||||
// Group mapping symbols by input section and sort by address
|
||||
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
|
||||
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
|
||||
});
|
||||
|
||||
// Swap bytes
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
Symbol<E> &sym = *syms[i];
|
||||
if (sym.name().starts_with("$d"))
|
||||
continue;
|
||||
|
||||
InputSection<E> &isec = *sym.get_input_section();
|
||||
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
|
||||
|
||||
u8 *end;
|
||||
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
|
||||
end = base + syms[i + 1]->value;
|
||||
else
|
||||
end = base + isec.sh_size;
|
||||
|
||||
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
|
||||
for (u8 *p = base + sym.value; p < end; p += sz)
|
||||
std::reverse(p, p + sz);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
+5
-12
@@ -484,17 +484,12 @@ static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
|
||||
}
|
||||
|
||||
static ElfRel<E> *
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
|
||||
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
|
||||
[](const ElfRel<E> &r, i64 offset) {
|
||||
return r.r_offset < offset;
|
||||
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
|
||||
return (u64)r.r_offset;
|
||||
});
|
||||
|
||||
if (it == rels.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
if (it == rels.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
@@ -509,9 +504,7 @@ struct OpdSymbol {
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
if (it == syms.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return it->sym;
|
||||
}
|
||||
|
||||
+3
-9
@@ -303,8 +303,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
assert(E::is_64);
|
||||
*(U32<E> *)loc = S + A;
|
||||
if (E::is_64)
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
break;
|
||||
@@ -321,12 +321,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val = S + A - P;
|
||||
i64 rd = get_rd(buf + rel.r_offset + 4);
|
||||
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
if (sym.esym().is_undef_weak())
|
||||
val = 0;
|
||||
|
||||
if (removed_bytes == 4) {
|
||||
// auipc + jalr -> jal
|
||||
*(ul32 *)loc = (rd << 7) | 0b1101111;
|
||||
@@ -473,7 +467,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
case R_RISCV_TLSDESC_HI20:
|
||||
// RISC-V TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
// a TP-relative address in a0.
|
||||
//
|
||||
// .L0:
|
||||
// auipc tX, 0
|
||||
|
||||
+18
-5
@@ -587,6 +587,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
bool warn_shared_textrel = false;
|
||||
bool error_unresolved_symbols = true;
|
||||
std::optional<SeparateCodeKind> z_separate_code;
|
||||
std::optional<bool> allow_shlib_undefined;
|
||||
std::optional<bool> report_undefined;
|
||||
std::optional<bool> z_relro;
|
||||
std::optional<bool> z_dynamic_undefined_weak;
|
||||
@@ -732,7 +733,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (arg == "aarch64elfb" || arg == "aarch64linuxb") {
|
||||
ctx.arg.emulation = ARM64BE::name;
|
||||
} else if (arg == "armelf_linux_eabi") {
|
||||
ctx.arg.emulation = ARM32::name;
|
||||
ctx.arg.emulation = ARM32LE::name;
|
||||
} else if (arg == "armelfb_linux_eabi") {
|
||||
ctx.arg.emulation = ARM32BE::name;
|
||||
} else if (arg == "elf64lriscv") {
|
||||
ctx.arg.emulation = RV64LE::name;
|
||||
} else if (arg == "elf64briscv") {
|
||||
@@ -1291,6 +1294,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
}
|
||||
} else if (read_flag("no-build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::NONE;
|
||||
} else if (read_flag("be8")) {
|
||||
ctx.arg.be8 = true;
|
||||
} else if (read_flag("be32")) {
|
||||
ctx.arg.be8 = false;
|
||||
} else if (read_arg("format") || read_arg("b")) {
|
||||
if (arg == "binary")
|
||||
Fatal(ctx)
|
||||
@@ -1303,9 +1310,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("filter") || read_arg("F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag("allow-shlib-undefined")) {
|
||||
ctx.arg.allow_shlib_undefined = true;
|
||||
allow_shlib_undefined = true;
|
||||
} else if (read_flag("no-allow-shlib-undefined")) {
|
||||
ctx.arg.allow_shlib_undefined = false;
|
||||
allow_shlib_undefined = false;
|
||||
} else if (read_arg("O")) {
|
||||
} else if (read_flag("EB")) {
|
||||
} else if (read_flag("EL")) {
|
||||
@@ -1412,6 +1419,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
if (allow_shlib_undefined)
|
||||
ctx.arg.allow_shlib_undefined = *allow_shlib_undefined;
|
||||
else
|
||||
ctx.arg.allow_shlib_undefined = ctx.arg.shared;
|
||||
|
||||
if (!report_undefined)
|
||||
report_undefined = !ctx.arg.shared;
|
||||
|
||||
@@ -1485,6 +1497,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.image_base % ctx.page_size)
|
||||
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
|
||||
|
||||
if (ctx.arg.emulation == ARM32BE::name && !ctx.arg.be8)
|
||||
Fatal(ctx) << "--be32 is not supported";
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
|
||||
@@ -1495,9 +1510,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::string ver = ctx.arg.soname;
|
||||
if (ver.empty())
|
||||
ver = path_filename(ctx.arg.output);
|
||||
|
||||
ctx.arg.version_definitions.push_back(ver);
|
||||
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
|
||||
}
|
||||
|
||||
for (std::string_view path : version_scripts) {
|
||||
|
||||
+6
-1
@@ -232,7 +232,7 @@ std::string rel_to_string<ARM64BE>(u32 r_type) {
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM32>(u32 r_type) {
|
||||
std::string rel_to_string<ARM32LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
CASE(R_ARM_NONE);
|
||||
CASE(R_ARM_PC24);
|
||||
@@ -373,6 +373,11 @@ std::string rel_to_string<ARM32>(u32 r_type) {
|
||||
return unknown_type(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM32BE>(u32 r_type) {
|
||||
return rel_to_string<ARM32LE>(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<RV64LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
|
||||
@@ -13,7 +13,8 @@ struct X86_64;
|
||||
struct I386;
|
||||
struct ARM64LE;
|
||||
struct ARM64BE;
|
||||
struct ARM32;
|
||||
struct ARM32LE;
|
||||
struct ARM32BE;
|
||||
struct RV64LE;
|
||||
struct RV64BE;
|
||||
struct RV32LE;
|
||||
@@ -357,9 +358,10 @@ enum : u32 {
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
EF_ARM_ABI_FLOAT_SOFT = 0x00000200,
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400,
|
||||
EF_ARM_EABI_VER5 = 0x05000000,
|
||||
EF_ARM_ABI_FLOAT_SOFT = 0x200,
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x400,
|
||||
EF_ARM_BE8 = 0x800000,
|
||||
EF_ARM_EABI_VER5 = 0x5000000,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -1810,7 +1812,8 @@ template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
|
||||
template <typename E> concept is_i386 = std::same_as<E, I386>;
|
||||
template <typename E> concept is_arm64le = std::same_as<E, ARM64LE>;
|
||||
template <typename E> concept is_arm64be = std::same_as<E, ARM64BE>;
|
||||
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
|
||||
template <typename E> concept is_arm32le = std::same_as<E, ARM32LE>;
|
||||
template <typename E> concept is_arm32be = std::same_as<E, ARM32BE>;
|
||||
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
|
||||
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
|
||||
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
|
||||
@@ -1827,6 +1830,7 @@ template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
|
||||
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
|
||||
|
||||
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> concept is_arm32 = is_arm32le<E> || is_arm32be<E>;
|
||||
template <typename E> concept is_arm64 = is_arm64le<E> || is_arm64be<E>;
|
||||
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
|
||||
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
|
||||
@@ -1920,7 +1924,7 @@ struct ARM64BE : ARM64LE {
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct ARM32 {
|
||||
struct ARM32LE {
|
||||
static constexpr std::string_view name = "arm32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
@@ -1950,6 +1954,11 @@ struct ARM32 {
|
||||
};
|
||||
};
|
||||
|
||||
struct ARM32BE : ARM32LE {
|
||||
static constexpr std::string_view name = "arm32be";
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct RV64LE {
|
||||
static constexpr std::string_view name = "riscv64";
|
||||
static constexpr bool is_64 = true;
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ static std::string_view get_elf_type(u8 *buf) {
|
||||
case EM_X86_64:
|
||||
return X86_64::name;
|
||||
case EM_ARM:
|
||||
return ARM32::name;
|
||||
return is_le ? ARM32LE::name : ARM32BE::name;
|
||||
case EM_AARCH64:
|
||||
return is_le ? ARM64LE::name : ARM64BE::name;
|
||||
case EM_RISCV:
|
||||
|
||||
+52
-9
@@ -999,6 +999,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, esym, !this->is_reachable) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec);
|
||||
@@ -1006,6 +1007,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = esym.is_weak();
|
||||
sym.is_versioned_default = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1273,23 +1275,48 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
if (vers.empty() || esyms[i].is_undef())
|
||||
ver = VER_NDX_GLOBAL;
|
||||
else
|
||||
ver = (vers[i] & ~VERSYM_HIDDEN);
|
||||
ver = vers[i] & ~VERSYM_HIDDEN;
|
||||
|
||||
if (ver == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_default = vers.empty() || !(vers[i] & VERSYM_HIDDEN);
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
this->versyms.push_back(ver);
|
||||
|
||||
if (is_default) {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
std::string_view mangled_name = save_string(
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
|
||||
auto get_versioned_sym = [&] {
|
||||
std::string_view key = save_string(
|
||||
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
|
||||
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
|
||||
return get_symbol(ctx, key, name);
|
||||
};
|
||||
|
||||
// Symbol resolution involving symbol versioning is tricky because one
|
||||
// symbol can be resolved with two different identifiers. Among
|
||||
// symbols with the same name but different versions, one of them is
|
||||
// always marked as the "default" one. This symbol is often denoted
|
||||
// with two atsigns as `foo@@VERSION` and can be referred to either
|
||||
// as `foo` or `foo@VERSION`. No other symbols have two names like that.
|
||||
//
|
||||
// On contrary, a versioned non-default symbol can be referred only
|
||||
// with an explicit version suffix, e.g., `foo@VERSION`.
|
||||
//
|
||||
// Here is how we resolve versioned default symbols. We resolve `foo`
|
||||
// and `foo@VERSION` as usual, but with information to forward
|
||||
// references to `foo@VERSION` to `foo`. After name resolution, we
|
||||
// visit all symbol references to redirect `foo@VERSION` to `foo`.
|
||||
if (vers.empty() || ver == VER_NDX_GLOBAL) {
|
||||
// Unversioned symbol
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else if (vers[i] & VERSYM_HIDDEN) {
|
||||
// Versioned non-default symbol
|
||||
this->symbols.push_back(get_versioned_sym());
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else {
|
||||
// Versioned default symbol
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
this->symbols2.push_back(get_versioned_sym());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1396,6 +1423,22 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = versyms[i];
|
||||
sym.is_weak = true;
|
||||
sym.is_versioned_default = false;
|
||||
}
|
||||
|
||||
// A symbol with the default version is a special case because, unlike
|
||||
// other symbols, the symbol can be referred by two names, `foo` and
|
||||
// `foo@VERSION`. Here, we resolve `foo@VERSOIN` as a proxy of `foo`.
|
||||
Symbol<E> *sym2 = this->symbols2[i];
|
||||
if (sym2 && sym2 != &sym) {
|
||||
std::scoped_lock lock2(sym2->mu);
|
||||
|
||||
if (get_rank(this, esym, false) < get_rank(*sym2)) {
|
||||
sym2->file = this;
|
||||
sym2->origin = (uintptr_t)&sym;
|
||||
sym2->sym_idx = i;
|
||||
sym2->is_versioned_default = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -600,10 +600,10 @@ create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
|
||||
<< "because no -plugin option was given. Please make sure you "
|
||||
<< "added -flto not only for creating object files but also for "
|
||||
<< "creating the final executable.";
|
||||
Fatal(ctx) << mf->name << ": unable to handle this LTO object file because "
|
||||
<< "the -plugin option was not provided. Please make sure you "
|
||||
<< "added -flto not only when creating object files but also "
|
||||
<< "when linking the final executable.";
|
||||
|
||||
load_lto_plugin(ctx);
|
||||
|
||||
@@ -670,8 +670,10 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
// section. We handle such symbols differently than comdat symbols in
|
||||
// a regular file because, unlike regular object files, IR files don't
|
||||
// have input sections.
|
||||
if (psym.comdat_key)
|
||||
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, psym.comdat_key);
|
||||
if (psym.comdat_key) {
|
||||
std::string_view key = save_string(ctx, psym.comdat_key);
|
||||
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, key);
|
||||
}
|
||||
}
|
||||
|
||||
obj->symbol_strtab = save_string(ctx, strtab);
|
||||
|
||||
+20
-16
@@ -44,8 +44,8 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
|
||||
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, archive_name);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_reachable =
|
||||
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
@@ -65,8 +65,8 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_reachable =
|
||||
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
if (ctx.arg.trace)
|
||||
Out(ctx) << "trace: " << *file;
|
||||
@@ -81,7 +81,7 @@ new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
SharedFile<E> *file = new SharedFile<E>(ctx, mf);
|
||||
ctx.dso_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_reachable = !rctx.as_needed;
|
||||
file->as_needed = rctx.as_needed;
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
@@ -496,7 +496,7 @@ int mold_main(int argc, char **argv) {
|
||||
// create as few segments as possible.
|
||||
sort_output_sections(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
separate_debug_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
@@ -536,13 +536,6 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// We've created range extension thunks with a pessimistive assumption
|
||||
// that all out-of-section references are out of range. Now that we are
|
||||
// able to assign addresses to all SHF_ALLOC output sections, we can
|
||||
// eliminate excessive thunks.
|
||||
if constexpr (needs_thunk<E>)
|
||||
remove_redundant_thunks(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
if (!ctx.arg.strip_all)
|
||||
create_output_symtab(ctx);
|
||||
@@ -562,6 +555,14 @@ int mold_main(int argc, char **argv) {
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// We've created range extension thunks with a pessimistive assumption
|
||||
// that all out-of-section references are out of range. Now that we know
|
||||
// the addresses of all sections,, we can eliminate excessive thunks.
|
||||
if constexpr (needs_thunk<E>) {
|
||||
remove_redundant_thunks(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
if constexpr (is_arm32<E>) {
|
||||
if (ctx.extra.exidx) {
|
||||
ctx.extra.exidx->remove_duplicate_entries(ctx);
|
||||
@@ -601,6 +602,9 @@ int mold_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
if constexpr (is_arm32be<E>)
|
||||
arm32be_swap_bytes(ctx);
|
||||
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.z_rewrite_endbr)
|
||||
rewrite_endbr(ctx);
|
||||
@@ -612,7 +616,7 @@ int mold_main(int argc, char **argv) {
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gdb_index && !ctx.gnu_debuglink)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
@@ -621,7 +625,7 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.buildid)
|
||||
write_build_id(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
write_gnu_debuglink(ctx);
|
||||
|
||||
t_copy.stop();
|
||||
@@ -642,7 +646,7 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
write_separate_debug_file(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
|
||||
+47
-20
@@ -262,9 +262,9 @@ struct FdeRecord {
|
||||
template <typename E>
|
||||
struct InputSectionExtras {};
|
||||
|
||||
template <>
|
||||
struct InputSectionExtras<ARM32> {
|
||||
InputSection<ARM32> *exidx = nullptr;
|
||||
template <is_arm32 E>
|
||||
struct InputSectionExtras<E> {
|
||||
InputSection<E> *exidx = nullptr;
|
||||
};
|
||||
|
||||
struct RelocDelta {
|
||||
@@ -570,7 +570,7 @@ public:
|
||||
void populate_symtab(Context<E> &ctx) override;
|
||||
|
||||
void scan_abs_relocations(Context<E> &ctx);
|
||||
void create_range_extension_thunks(Context<E> &ctx, bool first_pass);
|
||||
void create_range_extension_thunks(Context<E> &ctx);
|
||||
|
||||
std::vector<InputSection<E> *> members;
|
||||
std::vector<std::unique_ptr<Thunk<E>>> thunks;
|
||||
@@ -1337,6 +1337,7 @@ public:
|
||||
mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(InputFile<E> *)> feeder) = 0;
|
||||
|
||||
std::span<Symbol<E> *> get_local_syms();
|
||||
std::span<Symbol<E> *> get_global_syms();
|
||||
std::string_view get_source_name() const;
|
||||
|
||||
@@ -1353,6 +1354,7 @@ public:
|
||||
std::string_view shstrtab;
|
||||
std::string_view symbol_strtab;
|
||||
|
||||
bool as_needed = false;
|
||||
bool has_init_array = false;
|
||||
bool has_ctors = false;
|
||||
|
||||
@@ -1487,6 +1489,7 @@ public:
|
||||
|
||||
std::string soname;
|
||||
std::vector<std::string_view> version_strings;
|
||||
std::vector<Symbol<E> *> symbols2;
|
||||
std::vector<ElfSym<E>> elf_syms2;
|
||||
|
||||
private:
|
||||
@@ -1717,28 +1720,34 @@ void rewrite_endbr(Context<X86_64> &ctx);
|
||||
// arch-arm32.cc
|
||||
//
|
||||
|
||||
class Arm32ExidxSection : public Chunk<ARM32> {
|
||||
template <is_arm32 E>
|
||||
class Arm32ExidxSection : public Chunk<E> {
|
||||
public:
|
||||
Arm32ExidxSection(OutputSection<ARM32> &osec) : output_section(osec) {
|
||||
Arm32ExidxSection(OutputSection<E> &osec) : output_section(osec) {
|
||||
this->name = ".ARM.exidx";
|
||||
this->shdr.sh_type = SHT_ARM_EXIDX;
|
||||
this->shdr.sh_flags = SHF_ALLOC;
|
||||
this->shdr.sh_addralign = 4;
|
||||
}
|
||||
|
||||
void compute_section_size(Context<ARM32> &ctx) override;
|
||||
void update_shdr(Context<ARM32> &ctx) override;
|
||||
void remove_duplicate_entries(Context<ARM32> &ctx);
|
||||
void copy_buf(Context<ARM32> &ctx) override;
|
||||
void compute_section_size(Context<E> &ctx) override;
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void remove_duplicate_entries(Context<E> &ctx);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
private:
|
||||
std::vector<u8> get_contents(Context<ARM32> &ctx);
|
||||
std::vector<u8> get_contents(Context<E> &ctx);
|
||||
|
||||
OutputSection<ARM32> &output_section;
|
||||
OutputSection<E> &output_section;
|
||||
};
|
||||
|
||||
template <> u64 get_eflags(Context<ARM32> &ctx);
|
||||
void create_arm_exidx_section(Context<ARM32> &ctx);
|
||||
template <is_arm32 E>
|
||||
u64 get_eflags(Context<E> &ctx);
|
||||
|
||||
template <is_arm32 E>
|
||||
void create_arm_exidx_section(Context<E> &ctx);
|
||||
|
||||
void arm32be_swap_bytes(Context<ARM32BE> &ctx);
|
||||
|
||||
//
|
||||
// arch-riscv.cc
|
||||
@@ -1888,9 +1897,9 @@ struct ContextExtras<E> {
|
||||
NotePropertySection<E> *note_property = nullptr;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ContextExtras<ARM32> {
|
||||
Arm32ExidxSection *exidx = nullptr;
|
||||
template <is_arm32 E>
|
||||
struct ContextExtras<E> {
|
||||
Arm32ExidxSection<E> *exidx = nullptr;
|
||||
};
|
||||
|
||||
template <is_riscv E>
|
||||
@@ -1961,6 +1970,7 @@ struct Context {
|
||||
bool allow_multiple_definition = false;
|
||||
bool allow_shlib_undefined = true;
|
||||
bool apply_dynamic_relocs = true;
|
||||
bool be8 = false;
|
||||
bool color_diagnostics = false;
|
||||
bool default_symver = false;
|
||||
bool demangle = true;
|
||||
@@ -2386,6 +2396,9 @@ public:
|
||||
bool is_traced : 1 = false; // for --trace-symbol
|
||||
bool is_wrapped : 1 = false; // for --wrap
|
||||
|
||||
// For symbols with default symbol version, e.g. foo@@VERSION.
|
||||
bool is_versioned_default : 1 = false;
|
||||
|
||||
// If a symbol can be resolved to a symbol in a different ELF file at
|
||||
// runtime, `is_imported` is true. If a symbol is a dynamic symbol and
|
||||
// can be used by other ELF file at runtime, `is_exported` is true.
|
||||
@@ -2728,6 +2741,11 @@ inline std::string_view InputFile<E>::get_string(Context<E> &ctx, i64 idx) {
|
||||
return this->get_string(ctx, elf_sections[idx]);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::span<Symbol<E> *> InputFile<E>::get_local_syms() {
|
||||
return std::span<Symbol<E> *>(this->symbols).subspan(0, this->first_global);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::span<Symbol<E> *> InputFile<E>::get_global_syms() {
|
||||
return std::span<Symbol<E> *>(this->symbols).subspan(this->first_global);
|
||||
@@ -3015,12 +3033,16 @@ template <typename E>
|
||||
u64
|
||||
Symbol<E>::get_thunk_addr(Context<E> &ctx, u64 P) const requires needs_thunk<E> {
|
||||
std::span<u64> vec = ctx.symbol_aux[aux_idx].thunk_addrs;
|
||||
|
||||
u64 lo = (P < branch_distance<E>) ? 0 : P - branch_distance<E>;
|
||||
u64 val = *ranges::lower_bound(vec, lo);
|
||||
i64 disp = val - P;
|
||||
auto it = ranges::lower_bound(vec, lo);
|
||||
if (it == vec.end())
|
||||
Fatal(ctx) << "range extension thunk out of range: " << *this;
|
||||
|
||||
i64 disp = *it - P;
|
||||
if (disp < -branch_distance<E> || branch_distance<E> <= disp)
|
||||
Fatal(ctx) << "range extension thunk out of range: " << *this;
|
||||
return val;
|
||||
return *it;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -3030,6 +3052,11 @@ inline bool Symbol<E>::has_plt(Context<E> &ctx) const {
|
||||
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_absolute() const {
|
||||
// An unresolved weak symbol acts as if it were an absolute address
|
||||
// at address 0
|
||||
if (is_remaining_undef_weak())
|
||||
return true;
|
||||
|
||||
return !is_imported && !get_frag() && !get_input_section() &&
|
||||
!get_output_section();
|
||||
}
|
||||
|
||||
+17
-22
@@ -834,20 +834,6 @@ void DynamicSection<E>::copy_buf(Context<E> &ctx) {
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::vector<std::span<T>> split(std::vector<T> &input, i64 unit) {
|
||||
std::span<T> span(input);
|
||||
std::vector<std::span<T>> vec;
|
||||
|
||||
while (span.size() >= unit) {
|
||||
vec.push_back(span.subspan(0, unit));
|
||||
span = span.subspan(unit);
|
||||
}
|
||||
if (!span.empty())
|
||||
vec.push_back(span);
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Assign offsets to OutputSection members
|
||||
template <typename E>
|
||||
void OutputSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
@@ -1176,6 +1162,9 @@ void OutputSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
u8 *strtab = strtab_base + this->strtab_offset;
|
||||
|
||||
memset(esym, 0, this->num_local_symtab * sizeof(ElfSym<E>));
|
||||
memset(strtab, 0, this->strtab_size);
|
||||
|
||||
auto write_esym = [&](u64 addr, i64 st_name) {
|
||||
memset(esym, 0, sizeof(*esym));
|
||||
esym->st_name = st_name;
|
||||
@@ -1315,18 +1304,16 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
if (sym->is_imported) {
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
add({idx, 0, E::R_GLOB_DAT, sym});
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we know an address at link-time, fill that GOT entry now.
|
||||
// It may need a base relocation, though.
|
||||
if (ctx.arg.pic && sym->is_relative())
|
||||
} else if (ctx.arg.pic && sym->is_relative()) {
|
||||
// We know the symbol's address, but it needs a base relocation.
|
||||
add({idx, sym->get_addr(ctx, NO_PLT), E::R_RELATIVE});
|
||||
else
|
||||
} else {
|
||||
// We know the symbol's exact run-time address at link-time.
|
||||
add({idx, sym->get_addr(ctx, NO_PLT)});
|
||||
}
|
||||
}
|
||||
|
||||
// Create GOT entries for TLVs.
|
||||
@@ -2634,6 +2621,14 @@ void VerdefSection<E>::construct(Context<E> &ctx) {
|
||||
if (ctx.arg.version_definitions.empty())
|
||||
return;
|
||||
|
||||
// Handle --default-symver
|
||||
if (ctx.arg.default_symver)
|
||||
for (Symbol<E> *sym : ctx.dynsym->symbols)
|
||||
if (sym && !sym->file->is_dso)
|
||||
if (u16 ver = sym->ver_idx;
|
||||
ver == VER_NDX_GLOBAL || ver == VER_NDX_UNSPECIFIED)
|
||||
sym->ver_idx = VER_NDX_LAST_RESERVED + 1;
|
||||
|
||||
// Resize .gnu.version and write to it
|
||||
ctx.versym->contents.resize(ctx.dynsym->symbols.size(), VER_NDX_GLOBAL);
|
||||
ctx.versym->contents[0] = VER_NDX_LOCAL;
|
||||
|
||||
+100
-101
@@ -26,8 +26,10 @@ int redo_main(Context<E> &ctx, int argc, char **argv) {
|
||||
return mold_main<ARM64LE>(argc, argv);
|
||||
if (target == ARM64BE::name)
|
||||
return mold_main<ARM64BE>(argc, argv);
|
||||
if (target == ARM32::name)
|
||||
return mold_main<ARM32>(argc, argv);
|
||||
if (target == ARM32LE::name)
|
||||
return mold_main<ARM32LE>(argc, argv);
|
||||
if (target == ARM32BE::name)
|
||||
return mold_main<ARM32BE>(argc, argv);
|
||||
if (target == RV64LE::name)
|
||||
return mold_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::name)
|
||||
@@ -215,14 +217,14 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
std::vector<InputFile<E> *> roots;
|
||||
append(roots, ctx.objs);
|
||||
append(roots, ctx.dsos);
|
||||
|
||||
for (InputFile<E> *file : ctx.objs)
|
||||
if (file->is_reachable)
|
||||
roots.push_back(file);
|
||||
for (InputFile<E> *file : roots)
|
||||
if (!file->as_needed)
|
||||
file->is_reachable = true;
|
||||
|
||||
for (InputFile<E> *file : ctx.dsos)
|
||||
if (file->is_reachable)
|
||||
roots.push_back(file);
|
||||
std::erase_if(roots, [](InputFile<E> *file) { return !file->is_reachable; });
|
||||
|
||||
tbb::parallel_for_each(roots, [&](InputFile<E> *file,
|
||||
tbb::feeder<InputFile<E> *> &feeder) {
|
||||
@@ -247,25 +249,14 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
// `VER1`. If it does, that's the symbol we are looking for.
|
||||
template <typename E>
|
||||
static void resolve_default_symver(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
std::vector<InputFile<E> *> files;
|
||||
append(files, ctx.objs);
|
||||
append(files, ctx.dsos);
|
||||
|
||||
if (!sym.file && esym.is_undef() &&
|
||||
file->has_symver[i - file->first_global]) {
|
||||
std::string_view str = file->symbol_strtab.data() + esym.st_name;
|
||||
i64 pos = str.find('@');
|
||||
assert(pos != str.npos);
|
||||
|
||||
std::string_view name = str.substr(0, pos);
|
||||
std::string_view ver = str.substr(pos + 1);
|
||||
|
||||
Symbol<E> *sym2 = get_symbol(ctx, name);
|
||||
if (sym2->file && sym2->file->is_dso && sym2->get_version() == ver)
|
||||
file->symbols[i] = sym2;
|
||||
}
|
||||
}
|
||||
tbb::parallel_for_each(files, [](InputFile<E> *file) {
|
||||
for (Symbol<E> *&sym : file->get_global_syms())
|
||||
if (sym->is_versioned_default)
|
||||
sym = (Symbol<E> *)sym->origin;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -310,69 +301,68 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
resolve_default_symver(ctx);
|
||||
mark_live_objects(ctx);
|
||||
|
||||
// Now that we know the exact set of input files that are to be
|
||||
// included in the output file, we want to redo symbol resolution.
|
||||
// This is because symbols defined by object files in archive files
|
||||
// may have risen as a result of mark_live_objects().
|
||||
//
|
||||
// To redo symbol resolution, we want to clear the state first.
|
||||
clear_symbols(ctx);
|
||||
|
||||
// COMDAT elimination needs to happen exactly here.
|
||||
//
|
||||
// It needs to be after archive extraction, otherwise we might
|
||||
// assign COMDAT leader to an archive member that is not supposed to
|
||||
// be extracted.
|
||||
//
|
||||
// It needs to happen before the final symbol resolution, otherwise
|
||||
// we could eliminate a symbol that is already resolved to and cause
|
||||
// dangling references.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
update_minimum(ref.group->owner, file->priority);
|
||||
for (ComdatGroup *g : file->lto_comdat_groups)
|
||||
if (g)
|
||||
update_minimum(g->owner, file->priority);
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
if (ref.group->owner != file->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (InputSection<E> *isec = file->sections[i].get())
|
||||
isec->is_alive = false;
|
||||
});
|
||||
|
||||
// Redo symbol resolution
|
||||
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
file->resolve_symbols(ctx);
|
||||
});
|
||||
|
||||
// Symbols with hidden visibility need to be resolved within the
|
||||
// output file. If a hidden symbol was resolved to a DSO, we'll redo
|
||||
// symbol resolution from scratch with the flag to skip that symbol
|
||||
// next time. This should be rare.
|
||||
std::atomic_bool flag = false;
|
||||
|
||||
std::atomic_bool redo = false;
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file && sym->visibility == STV_HIDDEN) {
|
||||
sym->skip_dso = true;
|
||||
flag = true;
|
||||
redo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
return;
|
||||
if (!redo)
|
||||
break;
|
||||
clear_symbols(ctx);
|
||||
}
|
||||
|
||||
// Now that we know the exact set of input files that are to be
|
||||
// included in the output file, we want to redo symbol resolution.
|
||||
// This is because symbols defined by object files in archive files
|
||||
// may have risen as a result of mark_live_objects().
|
||||
//
|
||||
// To redo symbol resolution, we want to clear the state first.
|
||||
clear_symbols(ctx);
|
||||
|
||||
// COMDAT elimination needs to happen exactly here.
|
||||
//
|
||||
// It needs to be after archive extraction, otherwise we might
|
||||
// assign COMDAT leader to an archive member that is not supposed to
|
||||
// be extracted.
|
||||
//
|
||||
// It needs to happen before the final symbol resolution, otherwise
|
||||
// we could eliminate a symbol that is already resolved to and cause
|
||||
// dangling references.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
update_minimum(ref.group->owner, file->priority);
|
||||
for (ComdatGroup *g : file->lto_comdat_groups)
|
||||
if (g)
|
||||
update_minimum(g->owner, file->priority);
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
if (ref.group->owner != file->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (InputSection<E> *isec = file->sections[i].get())
|
||||
isec->is_alive = false;
|
||||
});
|
||||
|
||||
// Redo symbol resolution
|
||||
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
file->resolve_symbols(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// Do link-time optimization. We pass all IR object files to the compiler
|
||||
@@ -1125,18 +1115,19 @@ void check_shlib_undefined(Context<E> &ctx) {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Obtain a list of known shared library names.
|
||||
// Skip test if we don't have a complete set of shared object files
|
||||
// for the program, because if there's a missing .so, an undefined
|
||||
// symbol might be defined by that library.
|
||||
std::unordered_set<std::string_view> sonames;
|
||||
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
|
||||
sonames.insert(file->soname);
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
// Skip the file if it depends on a file that we know nothing about.
|
||||
// This is because missing symbols may be provided by that unknown file.
|
||||
for (std::string_view needed : file->get_dt_needed(ctx))
|
||||
if (!sonames.contains(needed))
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
for (std::string_view soname : file->get_dt_needed(ctx))
|
||||
if (!sonames.contains(soname))
|
||||
return;
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
// Check if all undefined symbols have been resolved.
|
||||
for (i64 i = 0; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
@@ -1153,7 +1144,7 @@ void check_shlib_undefined(Context<E> &ctx) {
|
||||
// SharedFile<E>::mark_live_objects just for this pass. Therefore,
|
||||
// remove unneeded DSOs from the list now.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
file->is_reachable = false;
|
||||
file->is_reachable = !file->as_needed;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms())
|
||||
@@ -1169,10 +1160,6 @@ template <typename E>
|
||||
void check_symbol_types(Context<E> &ctx) {
|
||||
Timer t(ctx, "check_symbol_types");
|
||||
|
||||
std::vector<InputFile<E> *> files;
|
||||
append(files, ctx.objs);
|
||||
append(files, ctx.dsos);
|
||||
|
||||
auto canonicalize = [](u32 ty) -> u32 {
|
||||
if (ty == STT_GNU_IFUNC)
|
||||
return STT_FUNC;
|
||||
@@ -1181,23 +1168,35 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
return ty;
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<E> *file) {
|
||||
auto check = [&](InputFile<E> &file, Symbol<E> &sym,
|
||||
const ElfSym<E> &esym1, const ElfSym<E> &esym2) {
|
||||
if (sym.file && sym.file != &file &&
|
||||
esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
|
||||
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
<< ">>> defined in " << *sym.file << " as "
|
||||
<< stt_to_string<E>(esym1.st_type) << '\n'
|
||||
<< ">>> defined in " << file << " as "
|
||||
<< stt_to_string<E>(esym2.st_type);
|
||||
}
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++)
|
||||
if (Symbol<E> *sym = file->symbols[i];
|
||||
sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (!sym.file || sym.file == file)
|
||||
continue;
|
||||
if (Symbol<E> *sym = file->symbols[i];
|
||||
sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
|
||||
const ElfSym<E> &esym1 = sym.esym();
|
||||
const ElfSym<E> &esym2 = file->elf_syms[i];
|
||||
|
||||
if (esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
|
||||
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
<< ">>> defined in " << *sym.file << " as "
|
||||
<< stt_to_string<E>(esym1.st_type) << '\n'
|
||||
<< ">>> defined in " << *file << " as "
|
||||
<< stt_to_string<E>(esym2.st_type);
|
||||
}
|
||||
if (Symbol<E> *sym = file->symbols2[i])
|
||||
if (sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1441,7 +1440,7 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
|
||||
// create_range_extension_thunks is not thread-safe
|
||||
for (Chunk<E> *chunk : std::span(vec.begin(), tail.begin()))
|
||||
chunk->to_osec()->create_range_extension_thunks(ctx, true);
|
||||
chunk->to_osec()->create_range_extension_thunks(ctx);
|
||||
|
||||
tbb::parallel_for_each(tail, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_section_size(ctx);
|
||||
@@ -2764,7 +2763,7 @@ i64 set_osec_offsets(Context<E> &ctx) {
|
||||
if (ctx.phdr) {
|
||||
i64 sz = ctx.phdr->shdr.sh_size;
|
||||
ctx.phdr->update_shdr(ctx);
|
||||
if (sz != ctx.phdr->shdr.sh_size)
|
||||
if (sz < ctx.phdr->shdr.sh_size)
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -122,10 +122,6 @@ i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
if (sym.is_absolute())
|
||||
return INT64_MAX;
|
||||
|
||||
// Likewise, relocations against weak undefined symbols won't be relaxed.
|
||||
if (sym.esym().is_undef_weak())
|
||||
return INT64_MAX;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
|
||||
+116
-67
@@ -20,8 +20,8 @@
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64LE || MOLD_ARM64BE || MOLD_PPC32 || \
|
||||
MOLD_PPC64V1 || MOLD_PPC64V2
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE || MOLD_ARM64LE || MOLD_ARM64BE || \
|
||||
MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -45,68 +45,67 @@ static constexpr i64 max_thunk_size = 1024 * 1024;
|
||||
static constexpr i64 thunk_align = 16;
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, bool first_pass, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
// If the target section is in the same output section but
|
||||
// hasn't got any address yet, that's unreacahble.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (isec2 && isec.output_section == isec2->output_section &&
|
||||
isec2->offset == -1)
|
||||
static bool
|
||||
requires_thunk(Context<E> &ctx, InputSection<E> &isec, const ElfRel<E> &rel,
|
||||
bool first_pass) {
|
||||
if (!is_func_call_rel(rel))
|
||||
return false;
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
|
||||
// We don't know about the final file layout on the first pass, so
|
||||
// we assume pessimistically that all out-of-section relocations are
|
||||
// out-of-range. Excessive thunks will be removed on the second pass.
|
||||
if (first_pass) {
|
||||
// On the first pass, we pessimistically assume that all out-of-section
|
||||
// relocations are out of range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
// If the target section is in the same output section but
|
||||
// hasn't got any address yet, that's unreacahble.
|
||||
if (isec2->offset == -1)
|
||||
return true;
|
||||
|
||||
// Even if the target is the same section, we branch to its PLT
|
||||
// if it has one. So a symbol with a PLT is also considered an
|
||||
// out-of-section reference.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Thumb and ARM B instructions cannot be converted to BX, so we
|
||||
// always have to make them jump to a thunk to switch processor mode
|
||||
// even if their destinations are within their ranges.
|
||||
if constexpr (is_arm32<E>)
|
||||
if (bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
(rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_PLT32 && is_thumb))
|
||||
return true;
|
||||
|
||||
// On PowerPC, all PLT calls go through range extension thunks.
|
||||
if constexpr (is_ppc<E>)
|
||||
if (sym.has_plt(ctx))
|
||||
return true;
|
||||
|
||||
// PowerPC before Power9 lacks PC-relative load/store instructions.
|
||||
// Functions compiled for Power9 or earlier assume that r2 points to
|
||||
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
|
||||
// We need a thunk to recompute r2 for interworking.
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
if ((rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
|
||||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc()))
|
||||
return true;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 S = sym.get_addr(ctx, NO_OPD);
|
||||
i64 A = get_addend(isec, rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -branch_distance<E> <= val && val < branch_distance<E>;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool needs_shim(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
// Thumb and ARM B instructions cannot be converted to BX, so we
|
||||
// always have to make them jump to a thunk to switch processor mode
|
||||
// even if their destinations are within their ranges.
|
||||
if constexpr (is_arm32<E>) {
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
return (rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_PLT32 && is_thumb);
|
||||
}
|
||||
|
||||
// On PowerPC, all PLT calls go through range extension thunks.
|
||||
if constexpr (is_ppc32<E> || is_ppc64v1<E>)
|
||||
return sym.has_plt(ctx);
|
||||
|
||||
// PowerPC before Power9 lacks PC-relative load/store instructions.
|
||||
// Functions compiled for Power9 or earlier assume that r2 points to
|
||||
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
|
||||
// We need to a thunk to recompute r2 for interworking.
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
return sym.has_plt(ctx) ||
|
||||
(rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
|
||||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc());
|
||||
return false;
|
||||
return val < -branch_distance<E> || branch_distance<E> <= val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
bool first_pass) {
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
std::span<InputSection<E> *> m = members;
|
||||
if (m.empty())
|
||||
return;
|
||||
@@ -181,33 +180,21 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, thunk_align);
|
||||
thunks.emplace_back(std::make_unique<Thunk<E>>(*this, offset));
|
||||
|
||||
Thunk<E> &thunk = *thunks.back();
|
||||
std::mutex mu;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
// entries in the new thunk.
|
||||
std::mutex mu;
|
||||
|
||||
tbb::parallel_for(b, c, [&](i64 i) {
|
||||
InputSection<E> &isec = *m[i];
|
||||
|
||||
for (const ElfRel<E> &rel : isec.get_rels(ctx)) {
|
||||
if (!is_func_call_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
// Skip if we can directly branch to the destination.
|
||||
if (is_reachable(ctx, first_pass, isec, sym, rel) &&
|
||||
!needs_shim(ctx, sym, rel))
|
||||
continue;
|
||||
|
||||
// Add the symbol to the current thunk if it's not added already.
|
||||
if (!sym.flags.test_and_set()) {
|
||||
std::scoped_lock lock(mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
if (requires_thunk(ctx, isec, rel, true)) {
|
||||
if (Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
!sym.flags.test_and_set()) {
|
||||
std::scoped_lock lock(mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -226,6 +213,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
b = c;
|
||||
}
|
||||
|
||||
// Reset flags for future use
|
||||
for (; t < thunks.size(); t++)
|
||||
for (Symbol<E> *sym : thunks[t]->symbols)
|
||||
sym->flags = 0;
|
||||
@@ -233,15 +221,75 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
|
||||
this->shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
// create_range_extension_thunks() creates thunks with a pessimistic
|
||||
// assumption that all out-of-section references are out of range.
|
||||
// After computing output section addresses, we revisit all thunks to
|
||||
// remove unneeded symbols from them.
|
||||
//
|
||||
// We create more thunks than necessary and then eliminate some of
|
||||
// them later, instead of just creating thunks at this stage. This is
|
||||
// because we can safely shrink sections after assigning addresses to
|
||||
// them without worrying about making existing references to thunks go
|
||||
// out of range. On the other hand, if we insert thunks after
|
||||
// assigning addresses to sections, references to thunks could become
|
||||
// out of range due to the new extra gaps for thunks. Thus, the
|
||||
// creation of thunks is a two-pass process.
|
||||
template <>
|
||||
void remove_redundant_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "remove_redundant_thunks");
|
||||
set_osec_offsets(ctx);
|
||||
|
||||
// Gather output executable sections
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
osec->create_range_extension_thunks(ctx, false);
|
||||
sections.push_back(osec);
|
||||
|
||||
// Mark all symbols that actually need range extension thunks
|
||||
for (OutputSection<E> *osec : sections) {
|
||||
tbb::parallel_for_each(osec->members, [&](InputSection<E> *isec) {
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx))
|
||||
if (requires_thunk(ctx, *isec, rel, false))
|
||||
isec->file.symbols[rel.r_sym]->flags.test_and_set();
|
||||
});
|
||||
}
|
||||
|
||||
// Remove symbols from thunks if they don't actually need range
|
||||
// extension thunks
|
||||
std::vector<Symbol<E> *> syms;
|
||||
|
||||
for (OutputSection<E> *osec : sections) {
|
||||
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks) {
|
||||
append(syms, thunk->symbols);
|
||||
std::erase_if(thunk->symbols, [&](Symbol<E> *sym) { return !sym->flags; });
|
||||
}
|
||||
}
|
||||
|
||||
// Reset flags for future use
|
||||
for (Symbol<E> *sym : syms)
|
||||
sym->flags = 0;
|
||||
|
||||
// Recompute section sizes
|
||||
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
|
||||
std::span<InputSection<E> *> m = osec->members;
|
||||
std::span<std::unique_ptr<Thunk<E>>> t = osec->thunks;
|
||||
i64 offset = 0;
|
||||
|
||||
while (!m.empty() || !t.empty()) {
|
||||
if (!m.empty() && (t.empty() || m[0]->offset < t[0]->offset)) {
|
||||
offset = align_to(offset, 1 << m[0]->p2align);
|
||||
m[0]->offset = offset;
|
||||
offset += m[0]->sh_size;
|
||||
m = m.subspan(1);
|
||||
} else {
|
||||
offset = align_to(offset, thunk_align);
|
||||
t[0]->offset = offset;
|
||||
offset += t[0]->size();
|
||||
t = t.subspan(1);
|
||||
}
|
||||
}
|
||||
osec->shdr.sh_size = offset;
|
||||
});
|
||||
}
|
||||
|
||||
// When applying relocations, we want to know the address in a reachable
|
||||
@@ -259,7 +307,8 @@ void gather_thunk_addresses(Context<E> &ctx) {
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
sections.push_back(osec);
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
sections.push_back(osec);
|
||||
|
||||
ranges::stable_sort(sections, {}, [](OutputSection<E> *x) {
|
||||
return x->shdr.sh_addr;
|
||||
|
||||
+7
-2
@@ -30,8 +30,8 @@ endif()
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS)
|
||||
list(APPEND QEMU_ARCHS
|
||||
x86_64 i386 arm aarch64 aarch64_be ppc ppc64 ppc64le sparc64 sh4 sh4eb
|
||||
s390x riscv64 riscv32 m68k loongarch64)
|
||||
x86_64 i386 arm armeb aarch64 aarch64_be ppc ppc64 ppc64le sparc64
|
||||
sh4 sh4eb s390x riscv64 riscv32 m68k loongarch64)
|
||||
|
||||
LIST(APPEND TRIPLES
|
||||
x86_64-linux-gnu
|
||||
@@ -39,6 +39,7 @@ if(MOLD_ENABLE_QEMU_TESTS)
|
||||
aarch64-linux-gnu
|
||||
aarch64_be-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
armeb-linux-gnueabihf
|
||||
riscv64-linux-gnu
|
||||
powerpc-linux-gnu
|
||||
powerpc64-linux-gnu
|
||||
@@ -121,6 +122,10 @@ if(${MACHINE} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
|
||||
add_target(arm arm-linux-gnueabihf)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "armeb" OR (HAS_qemu-armeb AND HAS_armeb-linux-gnueabihf-gcc))
|
||||
add_target(armeb armeb-linux-gnueabihf)
|
||||
endif()
|
||||
|
||||
if(${MACHINE} STREQUAL "riscv64" OR (HAS_qemu-riscv64 AND HAS_riscv64-linux-gnu-gcc))
|
||||
add_target(riscv64 riscv64-linux-gnu)
|
||||
endif()
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
not $CC -B. -o $t/exe $t/a.o -mbe32 |& grep 'be32 is not supported'
|
||||
+3
-2
@@ -7,6 +7,7 @@ canonical_name() {
|
||||
case $1 in
|
||||
i?86) echo i686 ;;
|
||||
amd64) echo x86_64 ;;
|
||||
armeb*) echo armeb ;;
|
||||
arm*) echo arm ;;
|
||||
powerpc) echo ppc ;;
|
||||
powerpc64) echo ppc64 ;;
|
||||
@@ -64,7 +65,7 @@ t=$TESTDIR/$testname
|
||||
mkdir -p $t
|
||||
|
||||
case $MACHINE in
|
||||
x86_64 | i686 | arm)
|
||||
x86_64 | i686 | arm | armeb)
|
||||
tlsdesc_opt=-mtls-dialect=gnu2 ;;
|
||||
aarch64 | riscv* | loongarch*)
|
||||
tlsdesc_opt=-mtls-dialect=desc ;;
|
||||
@@ -101,7 +102,7 @@ supports_ifunc() {
|
||||
|
||||
supports_tlsdesc() {
|
||||
# musl's tlsdesc on arm32 seems to be broken
|
||||
[ $MACHINE = arm ] && is_musl && return 1
|
||||
[[ $MACHINE = arm* ]] && is_musl && return 1
|
||||
|
||||
# FreeBSD's loader doesn't seem to support TLSDESC relocs in an executable
|
||||
[ "$(uname)" = FreeBSD ] && return 1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# arm-linux-gnueabihf-objcopy crashes on x86-64
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
[ $MACHINE = riscv32 ] && skip
|
||||
|
||||
command -v zstdcat >& /dev/null || skip
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Currently, CREL is not supported on REL-type targets
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
|
||||
[ "$CC" = cc ] || skip
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
echo '{ global: foo; local: *; };' > $t/b.ver
|
||||
|
||||
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--default-symver -Wl,--version-script=$t/b.ver
|
||||
readelf --dyn-syms $t/c.so | grep -F ' foo@@c.so'
|
||||
+2
-2
@@ -22,8 +22,8 @@ void bar();
|
||||
int main() { bar(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so -Wl,--allow-shlib-undefined
|
||||
readelf --dynamic $t/exe1 | grep 'Dependency audit library:..*/a.so'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo
|
||||
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo -Wl,--allow-shlib-undefined
|
||||
readelf --dynamic $t/exe2 | grep 'Dependency audit library:..*foo:.*/a.so'
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
# musl libc does not support init/fini on ARM
|
||||
# https://github.com/rui314/mold/issues/951
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
|
||||
[[ $MACHINE = arm* -o $MACHINE = aarch64 ]] && is_musl && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -
|
||||
#include <cstdio>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# ARM32's strip command crashes on the output of this test for some reason.
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
|
||||
strip=$STRIP
|
||||
command -v $strip >& /dev/null || skip
|
||||
|
||||
@@ -9,4 +9,4 @@ echo 'int main() {}' | clang -c -o $t/a.o -xc -
|
||||
echo 'void foo() {}' | clang -c -o $t/b.o -xc - -flto
|
||||
|
||||
not ./mold -o /dev/null $t/a.o $t/b.o |&
|
||||
grep "b.o: don't know how to handle this LTO object file"
|
||||
grep "b.o: unable to handle this LTO object file"
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
|
||||
void foo();
|
||||
void bar() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc - -L$t -lbar
|
||||
void baz();
|
||||
void foo() { baz(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -c -xc -
|
||||
int bar();
|
||||
int main() { bar(); }
|
||||
EOF
|
||||
|
||||
not $CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lfoo -lbar |&
|
||||
grep 'libfoo.so: --no-allow-shlib-undefined: undefined symbol: baz'
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
|
||||
void foo();
|
||||
void bar() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbaz.so -xc - -L$t -lbar
|
||||
void bar();
|
||||
void baz() { bar(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -c -xc -
|
||||
int baz();
|
||||
int main() { baz(); }
|
||||
EOF
|
||||
|
||||
mv $t/libfoo.so $t/libfoo.so.bak
|
||||
echo | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lbar -lbaz
|
||||
|
||||
not $CC -B. -o $t/exe2 $t/a.o -Wl,--no-allow-shlib-undefined \
|
||||
-L$t -lfoo -lbar -lbaz |& grep 'no-allow-shlib-undefined: undefined symbol: foo'
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
void f1() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
|
||||
void f1();
|
||||
void f2() { f1(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
|
||||
void f1();
|
||||
void f3() { f1(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/libfoo.so -shared $t/a.o
|
||||
$CC -B. -o $t/libbar.so -shared $t/b.o -L$t -lfoo
|
||||
$CC -B. -o $t/libbaz.so -shared $t/c.o
|
||||
|
||||
|
||||
cat <<EOF | $CC -o $t/d.o -c -xc - -fPIC
|
||||
void f2();
|
||||
void f3();
|
||||
int main() { f2(); f3(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -L$t -lbar -lbaz
|
||||
+1
-1
@@ -3,10 +3,10 @@
|
||||
|
||||
[ $MACHINE = i686 ] && skip
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = arm ] && skip
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -shared -o $t/a.so -xc -
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
# ARM assembler has a differnet grammar than the others.
|
||||
# Concretely speaking, ARM as uses "@" as a start of a comment.
|
||||
[ $MACHINE = arm ] && skip
|
||||
[[ $MACHINE = arm* ]] && skip
|
||||
|
||||
# All data symbols need to be aligned to 2 byte boundaries on s390x,
|
||||
# so rodata.str1.1 in this file is invalid on s390x.
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
|
||||
__asm__(".symver foo_,foo@@VERSION");
|
||||
void foo_() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/c.version
|
||||
VERSION { global: foo; local: *; };
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/libfoo.so $t/a.o
|
||||
$CC -B. -shared -o $t/libbar.so $t/b.o -Wl,--version-script=$t/c.version
|
||||
|
||||
cat <<EOF | $CC -o $t/d.o -c -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
__asm__(".symver foo,foo@VERSION");
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -L$t -lfoo -lbar
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = armeb ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = armeb ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = armeb ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
mkdir -p $t/bin $t/sysroot/foo
|
||||
|
||||
@@ -9,5 +9,4 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/a.so $t/a.o |&
|
||||
grep 'warning: symbol type mismatch: times'
|
||||
$CC -B. -shared -o $t/b.so $t/a.o |& grep 'warning: symbol type mismatch: times'
|
||||
Reference in new issue
Block a user