Compare commits

..
1 Commits
Author SHA1 Message Date
Rui Ueyama 57f5c77174 ARC 2025-04-15 16:49:41 +09:00
391 changed files with 5294 additions and 10972 deletions

No files matched your search

+2
View File
@@ -3,6 +3,8 @@ name: Build all tarballs
on:
schedule:
- cron: '0 0 * * *'
release:
types: [ published ]
workflow_dispatch:
jobs:
+10 -24
View File
@@ -5,7 +5,7 @@ apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
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 -
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 -
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- --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 -
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 -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
@@ -27,39 +27,25 @@ 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 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
wget -O /usr/local/bin/qemu-loongarch64 -q 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- --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 -
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 -
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++
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++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
ln -sf /aarch64be/bin/aarch64_be-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
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 -
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 -
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++
@@ -71,5 +57,5 @@ done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
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 -
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 -
ln -s /sde/sde64 /usr/bin
-80
View File
@@ -1,80 +0,0 @@
name: Build & attach tarballs on tag push
on:
push:
tags: ['v[0-9]*']
permissions:
contents: write
id-token: write
env:
TAG: ${{ github.ref_name }}
jobs:
build:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: dist
pattern: '*'
merge-multiple: true
- name: Ensure release exists (create if missing)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
if gh release view "$TAG" >& /dev/null; then
echo "Existing release found for $TAG"
else
echo "Creating draft release for $TAG"
gh release create "$TAG" --draft \
--title "$TAG" \
--notes "Automated draft – fill the changelog later."
fi
- name: Upload tarballs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$TAG" dist/* --clobber
gh release view "$TAG" --json assets --jq '.assets[].name'
+26 -30
View File
@@ -44,7 +44,7 @@
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.40.1)
project(mold VERSION 2.37.1)
include(CMakeDependentOption)
include(CheckSymbolExists)
@@ -62,11 +62,7 @@ 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)
if(CMAKE_BUILD_TYPE MATCHES "Deb")
target_link_options(mold PRIVATE -Wl,--gdb-index)
endif()
target_link_options(mold PRIVATE -fuse-ld=mold -Wl,--gdb-index)
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
@@ -164,13 +160,13 @@ check_include_file(zstd.h HAVE_ZSTD_H)
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE zstd)
else()
set(ZSTD_BUILD_PROGRAMS OFF)
set(ZSTD_BUILD_CONTRIB OFF)
set(ZSTD_BUILD_TESTS OFF)
set(ZSTD_MULTITHREAD_SUPPORT OFF)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_BUILD_STATIC ON)
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
target_compile_definitions(libzstd_static PRIVATE
ZSTD_BUILD_STATIC=1
ZSTD_BUILD_SHARED=0
ZSTD_BUILD_PROGRAMS=0
ZSTD_MULTITHREAD_SUPPORT=0
ZSTD_BUILD_TESTS=0)
target_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
@@ -217,7 +213,7 @@ endif()
# to mold. If you want to link to the system's libtbb2.so, pass
# -DMOLD_USE_SYSTEM_TBB=ON.
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
if(MOLD_USE_SYSTEM_TBB)
find_package(TBB REQUIRED)
target_link_libraries(mold PRIVATE TBB::tbb)
else()
@@ -293,10 +289,10 @@ check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
add_custom_target(git_hash
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS mold-git-hash.h
BYPRODUCTS git-hash.cc
VERBATIM)
add_dependencies(mold git_hash)
@@ -314,19 +310,15 @@ 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 ARM32LE ARM32BE RV32LE RV32BE RV64LE
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
LOONGARCH32 LOONGARCH64)
X86_64 I386 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64 ARC)
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/arch-arm32.cc
src/arch-arm64.cc
src/arch-loongarch.cc
src/arch-riscv.cc
src/arch-sh4.cc
src/archive-file.cc
src/cmdline.cc
src/error.cc
src/filetype.cc
src/gc-sections.cc
src/gdb-index.cc
@@ -381,15 +373,20 @@ endforeach()
# Add other non-template source files.
target_sources(mold PRIVATE
lib/aho-corasick.cc
git-hash.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/mimalloc.cc
lib/multi-glob.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/arch-arc.cc
src/arch-arm32.cc
src/arch-i386.cc
src/arch-m68k.cc
src/arch-ppc32.cc
@@ -398,23 +395,22 @@ target_sources(mold PRIVATE
src/arch-s390x.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/compress.cc
src/config.cc
src/elf.cc
src/entry.cc
third-party/rust-demangle/rust-demangle.c
)
if(WIN32)
target_sources(mold PRIVATE
src/jobs-win32.cc
src/mapped-file-win32.cc
src/signal-win32.cc
lib/jobs-win32.cc
lib/mapped-file-win32.cc
lib/signal-win32.cc
)
else()
target_sources(mold PRIVATE
src/jobs-unix.cc
src/mapped-file-unix.cc
src/signal-unix.cc
lib/jobs-unix.cc
lib/mapped-file-unix.cc
lib/signal-unix.cc
)
endif()
+2 -2
View File
@@ -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.'cfg(target_os = "linux")']
[target.x86_64-unknown-linux-gnu]
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.'cfg(target_os = "linux")']
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
Executable
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash -x
set -e
apt-get install -y git bzip2 ninja-build pkg-config libglib2.0-dev python3-distutils-extra libpixman-1-dev
cd /
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/qemu.git
mkdir /qemu/build
cd /qemu/build
../configure --static --extra-cflags=-Wno-error
ninja
ln -s /qemu/build/qemu-arc /qemu/build/qemu-arc64 /usr/local/bin
apt-get install -y libgmp-dev libmpfr-dev texinfo bison yacc flex
cd /
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/binutils-gdb.git
cd /binutils-gdb
mkdir build
cd build
../configure --target=arc-linux
make -j$(nproc)
apt-get install -y mold
mkdir /build
cd /build
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DMOLD_USE_MOLD=1 /mold
ninja
TRIPLE=arc-linux-gnu bash -x /mold/test/hello-dynamic.sh
+5 -13
View File
@@ -126,10 +126,10 @@ RUN mkdir /build && \
make install && \
rm -rf /build
# Build LLVM 20
# Build LLVM 19
RUN mkdir /build && \
cd /build && \
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 && \
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 && \
mkdir b && \
cd b && \
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
@@ -195,22 +195,14 @@ 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=%ct)
timestamp="$(git log -1 --format=%ci)"
# 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 \
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
$setarch bash -c "
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image 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
@@ -220,7 +212,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
+1 -3
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "MOLD" "1" "June 2025" ""
.TH "MOLD" "1" "February 2025" ""
.SH "NAME"
\fBmold\fR \- a modern linker
.SH "SYNOPSIS"
@@ -70,8 +70,6 @@ Permit or do not permit mold to create a debug info file in the background\.
.TP
\fB\-\-fork\fR, \fB\-\-no\-fork\fR
Spawn a child process and let it do the actual linking\. When linking a large program, the OS kernel can take a few hundred milliseconds to terminate a \fBmold\fR process\. \fB\-\-fork\fR hides that latency\. By default, it does fork\.
.IP
Note that \fB\-\-fork\fR also hides the resource usage statistics reported by time(2), since it doesn't call waitpid(2) on the child process\. If you need those statistics, pass \fB\-\-no\-fork\fR\.
.TP
\fB\-\-perf\fR
Print performance statistics\.
-4
View File
@@ -159,10 +159,6 @@ but as `-o magic`.
program, the OS kernel can take a few hundred milliseconds to terminate a
`mold` process. `--fork` hides that latency. By default, it does fork.
Note that `--fork` also hides the resource usage statistics reported by
time(2), since it doesn't call waitpid(2) on the child process. If you
need those statistics, pass `--no-fork`.
* `--perf`:
Print performance statistics.
+1 -1
View File
@@ -25,7 +25,7 @@ gentoo)
emerge-webrsync
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
;;
arch | archarm | artix | endeavouros | manjaro | cachyos)
arch | archarm | artix | endeavouros)
pacman -Sy --needed --noconfirm base-devel cmake util-linux
;;
void)
+1 -1
View File
@@ -11,7 +11,7 @@ set -x
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian)
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,arc}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
-132
View File
@@ -1,132 +0,0 @@
// This file implements the Aho-Corasick algorithm to search multiple
// strings within an input string simultaneously. It is essentially a
// trie with additional links. For details, see
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
//
// We use it for simple glob patterns in version scripts or dynamic
// list files. Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// Aho-Corasick can do only substring search, so it cannot handle
// complex glob patterns such as `*foo*bar*`. We handle such patterns
// with the Glob class.
#include "lib.h"
#include <queue>
namespace mold {
bool AhoCorasick::can_handle(std::string_view pat) {
if (pat.starts_with('*'))
pat.remove_prefix(1);
if (pat.ends_with('*'))
pat.remove_suffix(1);
return pat.find_first_of("*?[") == pat.npos;
}
i64 AhoCorasick::find(std::string_view str) {
if (nodes.empty())
return -1;
i64 idx = 0;
i64 val = -1;
auto walk = [&](u8 c) {
for (i64 j = idx; j != -1; j = nodes[j].suffix_link) {
i64 child = nodes[j].children[c];
if (child != -1) {
idx = child;
val = std::max(val, nodes[child].value);
return;
}
}
idx = 0;
};
walk('\0');
for (u8 c : str)
walk(c);
walk('\0');
return val;
}
bool AhoCorasick::add(std::string_view pat, i64 val) {
assert(can_handle(pat));
if (nodes.empty())
nodes.resize(1);
i64 idx = 0;
auto walk = [&](u8 c) {
if (nodes[idx].children[c] == -1) {
nodes[idx].children[c] = nodes.size();
nodes.resize(nodes.size() + 1);
}
idx = nodes[idx].children[c];
};
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
// substring matching, so we use \0 as a beginning/end-of-string
// markers.
if (!pat.starts_with('*'))
walk('\0');
for (u8 c : pat)
if (c != '*')
walk(c);
if (!pat.ends_with('*'))
walk('\0');
nodes[idx].value = std::max(nodes[idx].value, val);
return true;
}
void AhoCorasick::compile() {
if (nodes.empty())
return;
fix_suffix_links(0);
fix_values();
}
void AhoCorasick::fix_suffix_links(i64 idx) {
for (i64 i = 0; i < 256; i++) {
i64 child = nodes[idx].children[i];
if (child == -1)
continue;
i64 j = nodes[idx].suffix_link;
for (; j != -1; j = nodes[j].suffix_link) {
if (nodes[j].children[i] != -1) {
nodes[child].suffix_link = j;
break;
}
}
if (j == -1)
nodes[child].suffix_link = 0;
fix_suffix_links(child);
}
}
void AhoCorasick::fix_values() {
std::queue<i64> queue;
queue.push(0);
do {
i64 idx = queue.front();
queue.pop();
for (i64 child : nodes[idx].children) {
if (child != -1) {
i64 suffix = nodes[child].suffix_link;
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
queue.push(child);
}
}
} while (!queue.empty());
}
} // namespace mold
+9 -24
View File
@@ -23,11 +23,12 @@
// see the contents of libc.a by running `ar t
// /usr/lib/x86_64-linux-gnu/libc.a`.
#include "mold.h"
#pragma once
#include "common.h"
namespace mold {
namespace {
struct ArHdr {
char ar_name[16];
char ar_date[12];
@@ -73,11 +74,10 @@ struct ArHdr {
return {ar_name, sizeof(ar_name)};
}
};
}
template <typename E>
template <typename Context>
std::vector<MappedFile *>
read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
read_thin_archive_members(Context &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
@@ -123,9 +123,8 @@ read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
return vec;
}
template <typename E>
std::vector<MappedFile *>
read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
template <typename Context>
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
@@ -162,27 +161,13 @@ read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
return vec;
}
template <typename E>
std::vector<MappedFile *>
read_archive_members(Context<E> &ctx, MappedFile *mf) {
template <typename Context>
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
std::string_view str = mf->get_contents();
if (str.starts_with("!<arch>\n"))
return read_fat_archive_members(ctx, mf);
assert(str.starts_with("!<thin>\n"));
return read_thin_archive_members(ctx, mf);
}
using E = MOLD_TARGET;
template std::vector<MappedFile *>
read_thin_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_fat_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_archive_members(Context<E> &, MappedFile *);
} // namespace mold
-50
View File
@@ -1,50 +0,0 @@
// This is the same as std::atomic except that the default memory
// order is relaxed instead of sequential consistency.
#pragma once
#include <atomic>
namespace mold {
template <typename T>
struct Atomic : std::atomic<T> {
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
using std::atomic<T>::atomic;
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
Atomic<T> &operator=(const Atomic<T> &other) {
store(other.load());
return *this;
}
void operator=(T val) { store(val); }
operator T() const { return load(); }
void store(T val, std::memory_order order = relaxed) {
std::atomic<T>::store(val, order);
}
T load(std::memory_order order = relaxed) const {
return std::atomic<T>::load(order);
}
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
bool test_and_set() {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return load() || exchange(true);
}
};
} // namespace mold
-78
View File
@@ -1,78 +0,0 @@
#pragma once
#include "integers.h"
#include <cassert>
#include <vector>
namespace mold {
class BitvectorProxy {
public:
BitvectorProxy(u64 &word, size_t pos)
: word(word), mask(1ULL << pos) {}
BitvectorProxy &operator=(bool val) {
if (val)
word |= mask;
else
word &= ~mask;
return *this;
}
BitvectorProxy &operator=(const BitvectorProxy &other) {
return *this = (bool)other;
}
operator bool() const {
return word & mask;
}
private:
u64 &word;
u64 mask;
};
class Bitvector {
public:
Bitvector() = default;
Bitvector(i64 n) : size(n), words((n + 63) / 64) {}
void resize(i64 n) {
words.clear();
words.resize((n + 63) / 64);
size = n;
}
Bitvector &operator|=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] |= x.words[i];
return *this;
}
Bitvector &operator&=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] &= x.words[i];
return *this;
}
Bitvector &operator<<=(size_t n) {
assert(n == 1);
for (i64 i = words.size() - 1; i > 0; i--)
words[i] = (words[i] << 1) | (words[i - 1] >> 63);
words[0] <<= 1;
return *this;
}
BitvectorProxy operator[](size_t pos) {
assert(pos < size);
return {words[pos / 64], pos % 64};
}
i64 size = 0;
std::vector<u64> words;
};
} // namespace mold
+351 -48
View File
@@ -1,8 +1,7 @@
#pragma once
#include "atomics.h"
#include "config.h"
#include "integers.h"
#include "bitvector.h"
#include <array>
#include <atomic>
@@ -65,10 +64,199 @@ namespace ranges = std::ranges;
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
inline char *output_tmpfile;
inline u8 *output_buffer_start = nullptr;
inline u8 *output_buffer_end = nullptr;
extern std::string mold_git_hash;
std::string errno_string();
void cleanup();
void install_signal_handler();
inline u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
//
// Error output
//
// Some C++ stdlibs don't support std::osyncstream even though
// it's is in the C++20 standard. So we implement it ourselves.
class SyncStream {
public:
SyncStream(std::ostream &out) : out(out) {}
~SyncStream() {
emit();
}
template <typename T> SyncStream &operator<<(T &&val) {
ss << std::forward<T>(val);
return *this;
}
void emit() {
if (emitted)
return;
std::scoped_lock lock(mu);
out << ss.str() << '\n';
emitted = true;
}
private:
std::ostream &out;
std::stringstream ss;
bool emitted = false;
static inline std::mutex mu;
};
template <typename Context>
class Out {
public:
Out(Context &ctx) {}
template <typename T> Out &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cout};
};
static std::string_view fatal_mono = "mold: fatal: ";
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
static std::string_view error_mono = "mold: error: ";
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
static std::string_view warning_mono = "mold: warning: ";
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
template <typename Context>
class Fatal {
public:
Fatal(Context &ctx) {
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
}
[[noreturn]] ~Fatal() {
out.emit();
cleanup();
_exit(1);
}
template <typename T> Fatal &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename Context>
class Error {
public:
Error(Context &ctx) {
if (ctx.arg.noinhibit_exec) {
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
} else {
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
}
}
template <typename T> Error &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename Context>
class Warn {
public:
Warn(Context &ctx) {
if (ctx.arg.suppress_warnings)
return;
out.emplace(std::cerr);
if (ctx.arg.fatal_warnings) {
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
} else {
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
}
}
template <typename T> Warn &operator<<(T &&val) {
if (out)
*out << std::forward<T>(val);
return *this;
}
private:
std::optional<SyncStream> out;
};
//
// Atomics
//
// This is the same as std::atomic except that the default memory
// order is relaxed instead of sequential consistency.
template <typename T>
struct Atomic : std::atomic<T> {
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
using std::atomic<T>::atomic;
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
Atomic<T> &operator=(const Atomic<T> &other) {
store(other.load());
return *this;
}
void operator=(T val) { store(val); }
operator T() const { return load(); }
void store(T val, std::memory_order order = relaxed) {
std::atomic<T>::store(val, order);
}
T load(std::memory_order order = relaxed) const {
return std::atomic<T>::load(order);
}
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
bool test_and_set() {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return load() || exchange(true);
}
};
//
// mimalloc.cc
//
void set_mimalloc_options();
//
// perf.cc
//
@@ -479,7 +667,7 @@ public:
// Since the shard is circular, we need to handle the last entries
// as if they were next to the first entries.
while (begin < end && entries[end - 1].key)
while (entries[end - 1].key)
vec.push_back(entries + --end);
// Find entries contiguous in the buckets and sort them.
@@ -549,72 +737,58 @@ private:
};
//
// aho-corasick.cc
// glob.cc
//
class AhoCorasick {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return nodes.empty(); }
void compile();
i64 find(std::string_view str);
class Glob {
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
static bool can_handle(std::string_view str);
private:
struct TrieNode {
TrieNode() { children.fill(-1); }
i64 value = -1;
i32 suffix_link = -1;
std::array<i32, 256> children;
struct Element {
Element(Kind k) : kind(k) {}
Kind kind;
std::string str;
std::bitset<256> bitset;
};
void fix_suffix_links(i64 idx);
void fix_values();
public:
static std::optional<Glob> compile(std::string_view pat);
bool match(std::string_view str);
std::vector<TrieNode> nodes;
private:
Glob(std::vector<Element> &&vec) : elements(vec) {}
static bool do_match(std::string_view str, std::span<Element> elements);
std::vector<Element> elements;
};
//
// glob.cc
// multi-glob.cc
//
class MultiGlob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return patterns.empty(); }
void compile();
i64 find(std::string_view str);
bool empty() const { return strings.empty(); }
std::optional<i64> find(std::string_view str);
struct State {
std::bitset<256> incoming_edge;
bool is_star = false;
};
struct GlobPattern {
std::vector<State> states;
private:
struct TrieNode {
i64 value = -1;
TrieNode *suffix_link = nullptr;
std::unique_ptr<TrieNode> children[256];
};
private:
std::vector<GlobPattern> patterns;
Bitvector start_states;
Bitvector star_mask;
Bitvector char_mask[256];
};
void compile();
void fix_suffix_links(TrieNode &node);
void fix_values();
i64 find_aho_corasick(std::string_view str);
class Glob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return multi_glob.empty() && aho_corasick.empty(); }
i64 find(std::string_view str);
private:
std::vector<std::string> strings;
std::unique_ptr<TrieNode> root;
std::vector<std::pair<Glob, i64>> globs;
std::once_flag once;
bool is_compiled = false;
MultiGlob multi_glob;
AhoCorasick aho_corasick;
bool prefix_match = false;
};
//
@@ -642,6 +816,13 @@ std::string get_self_path();
std::optional<std::string_view> demangle_cpp(std::string_view name);
std::optional<std::string_view> demangle_rust(std::string_view name);
//
// jbos.cc
//
void acquire_global_lock();
void release_global_lock();
//
// crc32.cc
//
@@ -649,6 +830,36 @@ std::optional<std::string_view> demangle_rust(std::string_view name);
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
std::vector<u8> crc32_solve(u32 current, u32 desired);
//
// compress.cc
//
class Compressor {
public:
virtual void write_to(u8 *buf) = 0;
virtual ~Compressor() {}
i64 compressed_size = 0;
};
class ZlibCompressor : public Compressor {
public:
ZlibCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
u64 checksum = 0;
};
class ZstdCompressor : public Compressor {
public:
ZstdCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
};
//
// tar.cc
//
@@ -673,4 +884,96 @@ private:
std::string basedir;
};
//
// Memory-mapped file
//
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
class MappedFile {
public:
~MappedFile() { unmap(); }
void unmap();
void close_fd();
void reopen_fd(const std::string &path);
template <typename Context>
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile;
mf->name = name;
mf->data = data + start;
mf->size = size;
mf->parent = this;
ctx.mf_pool.emplace_back(mf);
return mf;
}
std::string_view get_contents() {
return std::string_view((char *)data, size);
}
i64 get_offset() const {
return parent ? (data - parent->data + parent->get_offset()) : 0;
}
// Returns a string that uniquely identify a file that is possibly
// in an archive.
std::string get_identifier() const {
if (parent) {
// We use the file offset within an archive as an identifier
// because archive members may have the same name.
return parent->name + ":" + std::to_string(get_offset());
}
if (thin_parent) {
// If this is a thin archive member, the filename part is
// guaranteed to be unique.
return thin_parent->name + ":" + name;
}
return name;
}
std::string name;
u8 *data = nullptr;
i64 size = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
// For --dependency-file
bool is_dependency = true;
#ifdef _WIN32
HANDLE fd = INVALID_HANDLE_VALUE;
#else
int fd = -1;
#endif
};
MappedFile *open_file_impl(const std::string &path, std::string &error);
template <typename Context>
MappedFile *open_file(Context &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
std::string error;
MappedFile *mf = open_file_impl(path, error);
if (!error.empty())
Fatal(ctx) << error;
if (mf)
ctx.mf_pool.emplace_back(mf);
return mf;
}
template <typename Context>
MappedFile *must_open_file(Context &ctx, std::string path) {
MappedFile *mf = open_file(ctx, path);
if (!mf)
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
return mf;
}
} // namespace mold
+185
View File
@@ -0,0 +1,185 @@
// This file implements a multi-threaded zlib and zstd compression
// routine.
//
// zlib-compressed data can be merged just by concatenation as long as
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
// split input data into multiple shards, compress them individually
// and concatenate them. We then append a header, a trailer and a
// checksum so that the concatenated data is valid zlib-format data.
//
// zstd-compressed data can be merged in the same way.
//
// Using threads to compress data has a downside. Since the dictionary
// is reset on boundaries of shards, compression ratio is sacrificed
// a little bit. However, if a shard size is large enough, that loss
// is negligible in practice.
#include "common.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
#include <zstd.h>
#define CHECK(fn) \
do { \
[[maybe_unused]] int r = (fn); \
assert(r == Z_OK); \
} while (0)
namespace mold {
static constexpr i64 SHARD_SIZE = 1024 * 1024;
static std::vector<std::string_view> split(std::string_view input) {
std::vector<std::string_view> shards;
while (input.size() >= SHARD_SIZE) {
shards.push_back(input.substr(0, SHARD_SIZE));
input = input.substr(SHARD_SIZE);
}
if (!input.empty())
shards.push_back(input);
return shards;
}
static std::vector<u8> zlib_compress(std::string_view input) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, we chose compression
// level 1.
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
// Set an input buffer
strm.avail_in = input.size();
strm.next_in = (u8 *)input.data();
// Set an output buffer. deflateBound() returns an upper bound
// on the compression size. +16 for Z_SYNC_FLUSH.
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
// Compress data. It writes all compressed bytes except the last
// partial byte, so up to 7 bits can be held to be written to the
// buffer.
strm.avail_out = buf.size();
strm.next_out = buf.data();
CHECK(deflate(&strm, Z_BLOCK));
// This is a workaround for libbacktrace before 2022-04-06.
//
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
// three bit value indicating the start of an uncompressed data
// block followed by four byte data 00 00 ff ff which indicates that
// the length of the block is zero. libbacktrace uses its own zlib
// inflate routine, and it had a bug that if that particular three
// bit value happens to end at a byte boundary, it accidentally
// skipped the next byte.
//
// In order to avoid triggering that bug, we should avoid calling
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
// If it's 5, we insert an empty block consisting of 10 bits so
// that the bit position is 7 in the next byte.
//
// https://github.com/ianlancetaylor/libbacktrace/pull/87
int nbits;
deflatePending(&strm, Z_NULL, &nbits);
if (nbits == 5)
CHECK(deflatePrime(&strm, 10, 2));
CHECK(deflate(&strm, Z_SYNC_FLUSH));
assert(strm.avail_out > 0);
buf.resize(buf.size() - strm.avail_out);
buf.shrink_to_fit();
deflateEnd(&strm);
return buf;
}
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
std::vector<u64> adlers(inputs.size());
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = zlib_compress(inputs[i]);
});
// Combine checksums
checksum = adlers[0];
for (i64 i = 1; i < inputs.size(); i++)
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
// Comput the total size
compressed_size = 8; // the header and the trailer
for (std::vector<u8> &shard : shards)
compressed_size += shard.size();
}
void ZlibCompressor::write_to(u8 *buf) {
// Write a zlib-format header
buf[0] = 0x78;
buf[1] = 0x9c;
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 2; // +2 for header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
});
// Write a trailer
u8 *end = buf + compressed_size;
end[-6] = 3;
end[-5] = 0;
// Write a checksum
*(ub32 *)(end - 4) = checksum;
}
static std::vector<u8> zstd_compress(std::string_view input) {
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
constexpr int LEVEL = 3; // compression level; must be between 1 to 22
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
LEVEL);
assert(!ZSTD_isError(sz));
buf.resize(sz);
buf.shrink_to_fit();
return buf;
}
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
shards[i] = zstd_compress(inputs[i]);
});
compressed_size = 0;
for (std::vector<u8> &shard : shards)
compressed_size += shard.size();
}
void ZstdCompressor::write_to(u8 *buf) {
// Copy compressed data
std::vector<i64> offsets(shards.size());
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
});
}
} // namespace mold
+1 -1
View File
@@ -1,4 +1,4 @@
#include "lib.h"
#include "common.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "lib.h"
#include "common.h"
#include <cstdlib>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "lib.h"
#include "common.h"
#include <filesystem>
#include <sys/stat.h>
+2 -2
View File
@@ -54,14 +54,14 @@ dir=gentoo/$git_hash
mkdir -p "$dir"/success "$dir"/failure
$podman chrt --idle 0 nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
$podman 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 chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
$podman nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
if [ $? = 0 ]; then
mv "$dir"/"$filename".ld "$dir"/success
else
+77 -173
View File
@@ -1,94 +1,17 @@
// This file implements a glob matcher that can run multiple glob patterns
// against an input string. mold uses the glob matcher for symbol name
// patterns in a version script or a dynamic list file. Since we may need to
// match hundreds of glob patterns against millions of symbol names, the
// speed of the matcher is very important.
//
// The pattern match implemented in this file is NFA-based, although the
// cost of the function is O(n*m), where n is the number of NFA states and m
// is the length of the input string. We do not use recursion or
// backtracking, unlike a generic NFA-based regular expression matcher. This
// is doable because glob patterns are very limited subsets of regexes.
//
// Here is the explanation of the algorithm. Observe that the only "tricky"
// meta-character in a glob pattern is "*", which matches zero or more
// characters. Other characters and meta-characters always match a single
// input character. So the key of the algorithm is to handle "*" efficiently.
//
// We can represent a glob pattern "a*b*" with three NFA states: q_start, q1
// and q_accept, with the following transition functions:
//
// δ(q_start, "a") = q1
// δ(q1, <any character>) = q1
// δ(q1, "b") = q_accept
// δ(q_accept, <any character>) = q_accept
//
// We can construct such an NFA in a straightforward manner. We maintain NFA
// states as a list, with the initial contents being the start state. Each
// character except for "*" creates a new NFA state, adds a transition from
// the last state in the list to the new one, and appends the new state at
// the end of the list. "*" sets the "is_star" flag on the last NFA state.
// The flag indicates that the state machine can remain in the state for any
// input character.
//
// An NFA constructed this way doesn't have any complicated loops,
// ε-transitions, or anything like that. Each state has only one incoming
// edge. The only loops in the state transition are the self-loops on states
// followed by a "*". Aside from that, the state machine progresses linearly
// from the start state to the accept state.
//
// Each state of an NFA can be represented by a single bit. If a bit is 1,
// the non-deterministic state machine is in that state. Otherwise, it's
// not. Observe that a state with the "is_star" flag will continued to be 1
// once it becomes 1, since the state machine can loop over the state on any
// input character.
//
// With that observation, we can represent an NFA with a bit vector of N
// bits, where N is the number of NFA states. For each input character, bit
// M becomes 1 if
//
// - bit M-1 is 1 and there's a transition from state_{M-1} to state_M
// with the given character, or
// - bit M is 1 and state_M's "is_star" flag is 1.
//
// Initially, the 0th bit is 1 for the start state. At each step, the bits
// propagate from least significant to most significant positions, at most
// one bit at a time. If the most significant bit is 1 after the entire
// input has been processed, the string matches.
//
// This propagation can be implemented with bitwise OR, bitwise AND, and a
// one-bit bit shift on the bit vector. All these operations are very cheap.
//
// We can combine multiple glob matchers into a single matcher by simply
// concatenating the bit vectors of their state machines.
#include "lib.h"
#include "common.h"
#include <cstring>
namespace mold {
static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
std::vector<MultiGlob::State> vec(1);
std::optional<Glob> Glob::compile(std::string_view pat) {
std::vector<Element> vec;
while (!pat.empty()) {
u8 c = pat[0];
pat = pat.substr(1);
std::bitset<256> chars;
switch (c) {
case '*':
vec.back().is_star = true;
continue;
case '?':
chars.set();
break;
case '\\':
if (pat.empty())
return {};
chars[pat[0]] = true;
pat = pat.substr(1);
break;
case '[': {
// Here are a few bracket pattern examples:
//
@@ -96,14 +19,17 @@ static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [^a-z]: Any character except lowercase letters
bool negate = false;
bool closed = false;
vec.emplace_back(BRACKET);
std::bitset<256> &bitset = vec.back().bitset;
bool negate = false;
if (!pat.empty() && pat[0] == '^') {
negate = true;
pat = pat.substr(1);
}
bool closed = false;
while (!pat.empty()) {
if (pat[0] == ']') {
pat = pat.substr(1);
@@ -133,9 +59,9 @@ static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
return {};
for (i64 i = start; i <= end; i++)
chars[i] = true;
bitset[i] = true;
} else {
chars[pat[0]] = true;
bitset[(u8)pat[0]] = true;
pat = pat.substr(1);
}
}
@@ -144,109 +70,87 @@ static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
return {};
if (negate)
chars.flip();
bitset.flip();
break;
}
case '?':
vec.emplace_back(QUESTION);
break;
case '*':
vec.emplace_back(STAR);
break;
case '\\':
if (pat.empty())
return {};
if (vec.empty() || vec.back().kind != STRING)
vec.emplace_back(STRING);
vec.back().str += pat[0];
pat = pat.substr(1);
break;
default:
chars[c] = true;
if (vec.empty() || vec.back().kind != STRING)
vec.emplace_back(STRING);
vec.back().str += c;
break;
}
vec.push_back({chars, false});
}
return vec;
}
// Instead of returning just a match/no match boolean value, our glob
// matcher returns an integer value associated with each given pattern.
// If multiple patterns match at the same time, the largest associated
// value will be returned by find().
bool MultiGlob::add(std::string_view pat, i64 val) {
std::vector<State> vec = parse_glob(pat);
if (vec.empty())
return false;
patterns.push_back({std::move(vec), val});
return true;
}
void MultiGlob::compile() {
if (patterns.empty())
return;
ranges::stable_sort(patterns, ranges::greater(), &GlobPattern::value);
std::vector<State> states;
for (GlobPattern &p : patterns)
append(states, p.states);
i64 sz = states.size();
start_states.resize(sz);
for (i64 pos = 0; GlobPattern &p : patterns) {
start_states[pos] = true;
pos += p.states.size();
}
star_mask.resize(sz);
for (i64 i = 0; i < sz; i++)
if (states[i].is_star)
star_mask[i] = true;
for (i64 i = 0; i < 256; i++) {
char_mask[i].resize(sz);
for (i64 j = 0; j < sz; j++)
if (states[j].incoming_edge[i])
char_mask[i][j] = true;
}
return {Glob{std::move(vec)}};
}
i64 MultiGlob::find(std::string_view str) {
if (patterns.empty())
return -1;
Bitvector bits = start_states;
Bitvector tmp;
for (u8 c : str) {
// This is equivalent to
//
// bits = (bits & star_mask) | ((bits << 1) & char_mask[c])
//
// but we update the existing objects in place to avoid allocating
// temporary objects.
tmp = bits;
tmp &= star_mask;
bits <<= 1;
bits &= char_mask[c];
bits |= tmp;
}
for (i64 pos = 0; GlobPattern &p : patterns) {
pos += p.states.size();
if (bits[pos - 1])
return p.value;
}
return -1;
bool Glob::match(std::string_view str) {
return do_match(str, elements);
}
bool Glob::add(std::string_view pat, i64 val) {
assert(val >= 0);
assert(!is_compiled);
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
while (!elements.empty()) {
Element &e = elements[0];
elements = elements.subspan(1);
// If the pattern requires only a single substring search, the
// Aho-Corasick algorithm is even faster than our glob matcher.
if (aho_corasick.can_handle(pat))
return aho_corasick.add(pat, val);
return multi_glob.add(pat, val);
}
switch (e.kind) {
case STRING:
if (!str.starts_with(e.str))
return false;
str = str.substr(e.str.size());
break;
case STAR:
if (elements.empty())
return true;
i64 Glob::find(std::string_view str) {
std::call_once(once, [&] {
multi_glob.compile();
aho_corasick.compile();
is_compiled = true;
});
// Patterns like "*foo*bar*" should be much more common than more
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
// the former case here.
if (elements[0].kind == STRING) {
for (;;) {
size_t pos = str.find(elements[0].str);
if (pos == str.npos)
return false;
if (do_match(str.substr(pos + elements[0].str.size()),
elements.subspan(1)))
return true;
str = str.substr(pos + 1);
}
}
return std::max(multi_glob.find(str), aho_corasick.find(str));
// Other cases are handled here.
for (i64 j = 0; j < str.size(); j++)
if (do_match(str.substr(j), elements))
return true;
return false;
case QUESTION:
if (str.empty())
return false;
str = str.substr(1);
break;
case BRACKET:
if (str.empty() || !e.bitset[str[0]])
return false;
str = str.substr(1);
break;
}
}
return str.empty();
}
} // namespace mold
+1 -1
View File
@@ -4,7 +4,7 @@
// For more info, read
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
#include "lib.h"
#include "common.h"
#include <cmath>
+1 -1
View File
@@ -9,7 +9,7 @@
// mold processes to just 1 for each user. It is intended to be used as
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
#include "mold.h"
#include "common.h"
#include <cstdlib>
#include <fcntl.h>
File renamed without changes.
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
namespace mold {
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
namespace mold {
+24
View File
@@ -0,0 +1,24 @@
#include "common.h"
// Including mimalloc-new-delete.h overrides new/delete operators.
// We need it only when we are using mimalloc as a dynamic library.
#if MOLD_USE_SYSTEM_MIMALLOC
# include <mimalloc-new-delete.h>
#endif
// Silence mimalloc warning messages that users can just ignore.
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
namespace mold {
void set_mimalloc_options() {
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
}
}
#else
namespace mold {
void set_mimalloc_options() {}
}
#endif
+186
View File
@@ -0,0 +1,186 @@
// This file implements the Aho-Corasick algorithm to match multiple
// glob patterns to symbol strings as quickly as possible.
//
// Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// `*` is a wildcard that matches any substring. We sometimes have
// hundreds of glob patterns and have to match them against millions
// of symbol strings.
//
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
// We handle such patterns with the Glob class. Glob is relatively
// slow, but complex patterns are rare in practice, so it should be
// OK.
#include "common.h"
#include <queue>
#include <regex>
namespace mold {
std::optional<i64> MultiGlob::find(std::string_view str) {
std::call_once(once, [&] { compile(); });
i64 val = -1;
// Match against simple glob patterns
if (root)
val = find_aho_corasick(str);
// Match against complex glob patterns
for (std::pair<Glob, i64> &glob : globs)
if (glob.first.match(str))
val = std::max(val, glob.second);
if (val == -1)
return {};
return val;
}
i64 MultiGlob::find_aho_corasick(std::string_view str) {
TrieNode *node = root.get();
i64 val = -1;
auto walk = [&](u8 c) {
for (;;) {
if (node->children[c]) {
node = node->children[c].get();
val = std::max(val, node->value);
return;
}
if (!node->suffix_link)
return;
node = node->suffix_link;
}
};
walk('\0');
for (u8 c : str) {
if (prefix_match && node == root.get())
return val;
walk(c);
}
walk('\0');
return val;
}
static bool is_simple_pattern(std::string_view pat) {
static std::regex re(R"(\*?[^*[?]+\*?)", std::regex_constants::optimize);
return std::regex_match(pat.begin(), pat.end(), re);
}
static std::string handle_stars(std::string_view pat) {
std::string str(pat);
// Convert "foo" -> "\0foo\0", "*foo" -> "foo\0", "foo*" -> "\0foo"
// and "*foo*" -> "foo". Aho-Corasick can do only substring matching,
// so we use \0 as beginning/end-of-string markers.
if (str.starts_with('*') && str.ends_with('*'))
return str.substr(1, str.size() - 2);
if (str.starts_with('*'))
return str.substr(1) + "\0"s;
if (str.ends_with('*'))
return "\0"s + str.substr(0, str.size() - 1);
return "\0"s + str + "\0"s;
}
bool MultiGlob::add(std::string_view pat, i64 val) {
assert(!is_compiled);
assert(!pat.empty());
strings.emplace_back(pat);
// Complex glob pattern
if (!is_simple_pattern(pat)) {
if (std::optional<Glob> glob = Glob::compile(pat)) {
globs.emplace_back(std::move(*glob), val);
return true;
}
return false;
}
// Simple glob pattern
if (!root)
root.reset(new TrieNode);
TrieNode *node = root.get();
for (u8 c : handle_stars(pat)) {
if (!node->children[c])
node->children[c].reset(new TrieNode);
node = node->children[c].get();
}
node->value = std::max(node->value, val);
return true;
}
void MultiGlob::compile() {
is_compiled = true;
if (root) {
fix_suffix_links(*root);
fix_values();
// If no pattern starts with '*', set prefix_match to true.
// We'll use this flag for optimization.
prefix_match = true;
for (i64 i = 1; i < 256; i++) {
if (root->children[i]) {
prefix_match = false;
break;
}
}
}
}
void MultiGlob::fix_suffix_links(TrieNode &node) {
for (i64 i = 0; i < 256; i++) {
if (!node.children[i])
continue;
TrieNode &child = *node.children[i];
TrieNode *cur = node.suffix_link;
for (;;) {
if (!cur) {
child.suffix_link = root.get();
break;
}
if (cur->children[i]) {
child.suffix_link = cur->children[i].get();
break;
}
cur = cur->suffix_link;
}
fix_suffix_links(child);
}
}
void MultiGlob::fix_values() {
std::queue<TrieNode *> queue;
queue.push(root.get());
do {
TrieNode *node = queue.front();
queue.pop();
for (std::unique_ptr<TrieNode> &child : node->children) {
if (!child)
continue;
child->value = std::max(child->value, child->suffix_link->value);
queue.push(child.get());
}
} while (!queue.empty());
}
} // namespace mold
+1 -1
View File
@@ -1,4 +1,4 @@
#include "lib.h"
#include "common.h"
#include <functional>
#include <iomanip>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "lib.h"
#include "common.h"
#include <random>
+4 -3
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include <csignal>
#include <tbb/version.h>
@@ -28,7 +28,8 @@ void cleanup() {
// for the first time.
//
// If a disk becomes full as a result of a write to an mmap'ed memory
// region, the failure of the write is reported as a SIGBUS. This
// region, the failure of the write is reported as a SIGBUS or structured
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
// signal handler catches that signal and prints out a user-friendly
// error message. Without this, it is very hard to realize that the
// disk might be full.
@@ -73,7 +74,7 @@ void install_signal_handler() {
sigaction(SIGBUS, &action, NULL);
// OneTBB 2021.9.0 has the interface version 12090.
if (TBB_runtime_interface_version() <= 12090) {
if (TBB_runtime_interface_version() < 12090) {
sigabrt_msg = "mold: aborted\n"
"mold: mold with libtbb version 2021.9.0 or older is known to be unstable "
"under heavy load. Your libtbb version is " +
+1 -1
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include <windows.h>
+1 -1
View File
@@ -1,6 +1,6 @@
// This file contains functions to create a tar file.
#include "lib.h"
#include "common.h"
#ifdef _WIN32
# define ftruncate _chsize_s
+5 -5
View File
@@ -17,11 +17,11 @@ if(EXISTS "${SOURCE_DIR}/.git/HEAD")
endif()
# Create new file contents and update a given file if necessary.
if("${HASH}" STREQUAL "")
set(NEW_CONTENTS "")
else()
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
endif()
set(NEW_CONTENTS "#include <string>
namespace mold {
std::string mold_git_hash = \"${HASH}\";
}
")
if(EXISTS "${OUTPUT_FILE}")
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
+285
View File
@@ -0,0 +1,285 @@
// r31 is blink. r30 is I believe a scratch register.
//
// Programs might use one of several branch, jump, and link instructions
// to control execution flow through direct and indirect function calls
// and branching. For function calling, the conditional branch-and-link
// instruction has a maximum branch range of ±1 MiB, and the target
// address is 32-bit-aligned. The unconditional branch-and-link format has
// a maximum branch range of ±16 MiB.
//
// https://github.com/foss-for-synopsys-dwc-arc-processors/arc-ABI-manual/blob/master/ARCv2_ABI.pdf
#include "mold.h"
namespace mold {
using E = ARC;
// An integer-like class for the mixed-endian encoding
class M32 {
public:
operator u32() const {
return (buf[1] << 24) | (buf[0] << 16) | (buf[3] << 8) | buf[2];
}
M32 &operator=(u32 x) {
buf[0] = x >> 16;
buf[1] = x >> 24;
buf[2] = x;
buf[3] = x >> 8;
return *this;
}
M32 &operator&=(u32 x) { return *this = *this & x; }
M32 &operator|=(u32 x) { return *this = *this | x; }
u8 buf[4];
};
static void write_disp7u(u8 *loc, u32 val) {
*(ul16 *)loc |= (bits(val, 6, 3) << 4) | bits(val, 2, 0);
}
static void write_disp9(u8 *loc, u32 val) {
*(M32 *)loc |= bits(val, 8, 0);
}
static void write_disp9ls(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 7, 0) << 16) | (bit(val, 8) << 15);
}
static void write_disp9s(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 8, 0);
}
static void write_disp10u(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 9, 0);
}
static void write_disp13s(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 12, 0);
}
static void write_disp21h(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 1) << 17) | (bits(val, 20, 11) << 6);
}
static void write_disp21w(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 2) << 18) | (bits(val, 20, 11) << 6);
}
static void write_disp25h(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 1) << 17) | (bits(val, 20, 11) << 6) |
bits(val, 24, 21);
}
static void write_disp25w(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 2) << 18) | (bits(val, 20, 11) << 6) |
bits(val, 24, 21);
}
static void write_disps9(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 10, 2);
}
static void write_disps12(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 5, 0) << 6) | bits(val, 11, 6);
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul16 insn[] = {
0x2730, 0x7f8b, 0, 0, // ld r11, [pcl,0]
0x2730, 0x7f8a, 0, 0, // ld r10, [pcl,0]
0x2020, 0x0280, // j [r10]
0, 0, // (address of GOTPLT)
};
memcpy(buf, insn, sizeof(insn));
*(M32 *)(buf + 4) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
*(M32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
*(M32 *)(buf + 20) = ctx.gotplt->shdr.sh_addr;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul16 insn[] = {
0x2730, 0x7f8c, 0, 0, // ld r12, [pcl,0]
0x2021, 0x0300, // j.d [r12]
0x240a, 0x1fc0, // mov r12,pcl
};
memcpy(buf, insn, sizeof(insn));
*(M32 *)(buf + 4) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx);
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul16 insn[] = {
0x2730, 0x7f8c, 0, 0, // ld r12, [pcl,0]
0x2021, 0x0300, // j.d [r12]
0x240a, 0x1fc0, // mov r12,pcl
};
memcpy(buf, insn, sizeof(insn));
*(M32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx);
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_ARC_32:
*(ul32 *)loc = val;
break;
case R_ARC_32_PCREL:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_ARC_32:
*(ul32 *)loc = S + A;
break;
case R_ARC_32_ME:
*(M32 *)loc = S + A;
break;
case R_ARC_S25H_PCREL:
case R_ARC_S25H_PCREL_PLT:
write_disp25h(loc, align_to(S + A - P, 2));
break;
case R_ARC_S25W_PCREL:
case R_ARC_S25W_PCREL_PLT:
write_disp25w(loc, align_to(S + A - P, 4));
break;
case R_ARC_PC32:
*(M32 *)loc = S + A - P;
break;
case R_ARC_32_PCREL:
*(ul32 *)loc = S + A - P;
break;
case R_ARC_GOTPC32:
*(M32 *)loc = GOT + G + A - P;
break;
case R_ARC_TLS_LE_32:
*(M32 *)loc = S + A - ctx.tp_addr;
break;
case R_ARC_TLS_IE_GOT:
*(M32 *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_ARC_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
switch (rel.r_type) {
case R_ARC_32:
break;
case R_ARC_32_ME:
scan_absrel(ctx, sym, rel);
break;
case R_ARC_S25H_PCREL:
case R_ARC_S25W_PCREL:
case R_ARC_32_PCREL:
case R_ARC_PC32:
scan_pcrel(ctx, sym, rel);
break;
case R_ARC_S25H_PCREL_PLT:
case R_ARC_S25W_PCREL_PLT:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_ARC_GOTPC32:
sym.flags |= NEEDS_GOT;
break;
case R_ARC_TLS_LE_32:
check_tlsle(ctx, sym, rel);
break;
case R_ARC_TLS_IE_GOT:
sym.flags |= NEEDS_GOTTP;
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return 0x406;
}
} // namespace mold
+103 -173
View File
@@ -31,21 +31,18 @@
//
// 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 = MOLD_TARGET;
using E = ARM32;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
U32<E> *arm = (U32<E> *)loc;
U16<E> *thm = (U16<E> *)loc;
ul32 *arm = (ul32 *)loc;
ul16 *thm = (ul16 *)loc;
switch (rel.r_type) {
case R_ARM_ABS32:
@@ -62,7 +59,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
return (I32<E>)*arm;
return (il32)*arm;
case R_ARM_THM_JUMP8:
return sign_extend(thm[0], 8) << 1;
case R_ARM_THM_JUMP11:
@@ -128,7 +125,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);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
}
static void write_thm_b21(u8 *loc, u32 val) {
@@ -138,7 +135,7 @@ static void write_thm_b21(u8 *loc, u32 val) {
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
U16<E> *buf = (U16<E> *)loc;
ul16 *buf = (ul16 *)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;
}
@@ -152,7 +149,7 @@ static void write_thm_b25(u8 *loc, u32 val) {
u32 imm10 = bits(val, 21, 12);
u32 imm11 = bits(val, 11, 1);
U16<E> *buf = (U16<E> *)loc;
ul16 *buf = (ul16 *)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;
}
@@ -163,7 +160,7 @@ static void write_thm_mov(u8 *loc, u32 val) {
u32 imm3 = bits(val, 10, 8);
u32 imm8 = bits(val, 7, 0);
U16<E> *buf = (U16<E> *)loc;
ul16 *buf = (ul16 *)loc;
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
}
@@ -187,13 +184,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:
*(U32<E> *)loc = val;
*(ul32 *)loc = val;
break;
case R_ARM_THM_JUMP8:
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
*(ul16 *)loc = (*(ul16 *)loc & 0xff00) | bits(val, 8, 1);
break;
case R_ARM_THM_JUMP11:
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
break;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
@@ -203,7 +200,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
@@ -212,7 +209,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
write_arm_mov(loc, val);
break;
case R_ARM_PREL31:
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
@@ -239,7 +236,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
};
memcpy(buf, insn, sizeof(insn));
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
static constexpr ul32 plt_entry[] = {
@@ -252,13 +249,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));
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
*(ul32 *)(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));
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
@@ -270,16 +267,24 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
case R_NONE:
break;
case R_ARM_ABS32:
*(U32<E> *)loc = val;
*(ul32 *)loc = val;
break;
case R_ARM_REL32:
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
*(ul32 *)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);
@@ -307,11 +312,7 @@ 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 = [&] {
auto it = ranges::upper_bound(output_section->thunks, P, {},
[](std::unique_ptr<Thunk<E>> &thunk) {
return thunk->get_addr();
});
return (*it)->get_addr();
return get_reachable_thunk(*output_section, P).get_addr();
};
switch (rel.r_type) {
@@ -319,13 +320,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_ARM_TARGET1:
break;
case R_ARM_REL32:
*(U32<E> *)loc = S + A - P;
*(ul32 *)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.
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
*(ul32 *)loc = 0x8000'f3af; // NOP.W
break;
}
@@ -336,61 +337,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)) {
*(U16<E> *)(loc + 2) |= 0x1000; // BL
*(ul16 *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, val1);
} else if (!T && is_int(val2, 25)) {
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
*(ul16 *)(loc + 2) &= ~0x1000; // BLX
write_thm_b25(loc, val2);
} else {
*(U16<E> *)(loc + 2) |= 0x1000; // BL
*(ul16 *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
}
break;
}
case R_ARM_BASE_PREL:
*(U32<E> *)loc = GOT + A - P;
*(ul32 *)loc = GOT + A - P;
break;
case R_ARM_GOTOFF32:
*(U32<E> *)loc = ((S + A) | T) - GOT;
*(ul32 *)loc = ((S + A) | T) - GOT;
break;
case R_ARM_GOT_PREL:
case R_ARM_TARGET2:
*(U32<E> *)loc = GOT + G + A - P;
*(ul32 *)loc = GOT + G + A - P;
break;
case R_ARM_GOT_BREL:
*(U32<E> *)loc = G + A;
*(ul32 *)loc = G + A;
break;
case R_ARM_CALL: {
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
*(ul32 *)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 = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
i64 val = S + A - P;
if (is_int(val, 26)) {
if (T) {
*(U32<E> *)loc = 0xfa00'0000; // BLX
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
*(ul32 *)loc = 0xfa00'0000; // BLX
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
} else {
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(val, 25, 2);
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(val, 25, 2);
}
} else {
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
}
break;
}
case R_ARM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
@@ -403,26 +404,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;
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
}
case R_ARM_PLT32:
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
*(ul32 *)loc = 0xe320'f000; // NOP
} else {
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP8:
check(S + A - P, -(1 << 8), 1 << 8);
*(U16<E> *)loc &= 0xff00;
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
*(ul16 *)loc &= 0xff00;
*(ul16 *)loc |= bits(S + A - P, 8, 1);
break;
case R_ARM_THM_JUMP11:
check(S + A - P, -(1 << 11), 1 << 11);
*(U16<E> *)loc &= 0xf800;
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
*(ul16 *)loc &= 0xf800;
*(ul16 *)loc |= bits(S + A - P, 11, 1);
break;
case R_ARM_THM_JUMP19:
check(S + A - P, -(1 << 20), 1 << 20);
@@ -430,7 +431,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_THM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0x8000'f3af; // NOP
*(ul32 *)loc = 0x8000'f3af; // NOP
break;
}
@@ -453,8 +454,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);
*(U32<E> *)loc &= 0x8000'0000;
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
*(ul32 *)loc &= 0x8000'0000;
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
break;
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov(loc, (S + A) | T);
@@ -472,19 +473,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:
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDM32:
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDO32:
*(U32<E> *)loc = S + A - ctx.dtp_addr;
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_ARM_TLS_IE32:
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_ARM_TLS_LE32:
*(U32<E> *)loc = S + A - ctx.tp_addr;
*(ul32 *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
// ARM32 TLSDESC uses the following code sequence to materialize
@@ -513,35 +514,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.
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
} else if (sym.has_gottp(ctx)) {
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
} else {
*(U32<E> *)loc = S - ctx.tp_addr;
*(ul32 *)loc = S - ctx.tp_addr;
}
break;
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
*(U32<E> *)loc = 0xeb00'0000; // bl 0
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
*(ul32 *)loc = 0xeb00'0000; // bl 0
*(ul32 *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
} else if (sym.has_gottp(ctx)) {
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
*(U32<E> *)loc = 0xe320'f000; // nop
*(ul32 *)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);
*(U16<E> *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else if (sym.has_gottp(ctx)) {
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
// we use two instructions instead.
*(U16<E> *)loc = 0x4478; // add r0, pc
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
*(ul16 *)loc = 0x4478; // add r0, pc
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
} else {
*(U32<E> *)loc = 0x8000'f3af; // nop.w
*(ul32 *)loc = 0x8000'f3af; // nop.w
}
break;
default:
@@ -572,15 +573,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))
*(U32<E> *)loc = *val;
*(ul32 *)loc = *val;
else
*(U32<E> *)loc = S + A;
*(ul32 *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U32<E> *)loc = *val;
*(ul32 *)loc = *val;
else
*(U32<E> *)loc = S + A - ctx.dtp_addr;
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
@@ -702,7 +703,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
for (Symbol<E> *sym : symbols) {
memcpy(buf, entry, sizeof(entry));
*(U32<E> *)(buf + 12) = sym->get_addr(ctx) - P - 16;
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
buf += sizeof(entry);
P += sizeof(entry);
@@ -711,14 +712,10 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
template <>
u64 get_eflags(Context<E> &ctx) {
if constexpr (E::is_le)
return EF_ARM_EABI_VER5;
else
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
return EF_ARM_EABI_VER5;
}
template <>
void create_arm_exidx_section<E>(Context<E> &ctx) {
void create_arm_exidx_section(Context<E> &ctx) {
for (i64 i = 0; i < ctx.chunks.size(); i++) {
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
@@ -735,20 +732,28 @@ void create_arm_exidx_section<E>(Context<E> &ctx) {
}
}
template <>
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
void Arm32ExidxSection::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
}
template <>
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
void Arm32ExidxSection::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;
@@ -767,8 +772,7 @@ static u64 get_text_end(Context<E> &ctx) {
// likely that it's due to some historical reason.
//
// This function returns contents of .ARM.exidx.
template <>
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
std::vector<u8> Arm32ExidxSection::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
@@ -784,8 +788,8 @@ std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
const u32 CANTUNWIND = 1;
struct Entry {
U32<E> addr;
U32<E> val;
ul32 addr;
ul32 val;
};
// We reserve one extra slot for the sentinel
@@ -816,13 +820,19 @@ std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
std::sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
return a.addr < b.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 tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
num_entries -= tail.size();
auto it = std::unique(ent, ent + num_entries,
[](const Entry &a, const Entry &b) {
return a.val == b.val;
});
num_entries = it - ent;
buf.resize(num_entries * sizeof(Entry));
// Make addresses relative to themselves.
@@ -836,84 +846,4 @@ std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
return buf;
}
template <>
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
this->shdr.sh_size = get_contents(ctx).size();
}
template <>
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
std::vector<u8> contents = get_contents(ctx);
assert(this->shdr.sh_size == contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
#if MOLD_ARM32BE
// Even though using ARM32 in big-endian mode is very rare, the processor
// technically supports both little- and big-endian modes. There are two
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
// data are encoded in big-endian. In BE8, instructions are encoded in
// little-endian, and only data is in big-endian. BE8 is the de facto
// standard for ARMv6 or later. We support only BE8.
//
// A tricky thing is that instructions in an object file are always
// big-endian if the file is compiled for big-endian mode. In other words,
// the compiler always emit code in BE32 if -mbig-endian is specified. It
// is the linker's responsibility to rewrite instructions from big-endian
// to little-endian for an BE8 output. This function does that.
//
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
// Thumb instructions, and data. We need to distinguish them to swap 4
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
// and $d, respectively. We use mapping symbols to determine what to do
// with the text section.
//
// This function is called after we copy the input section contents to the
// output file. We rewrite instructions in the output buffer in place.
void arm32be_swap_bytes(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
// Collect mapping symbols
std::vector<Symbol<E> *> syms;
for (Symbol<E> *sym : file->get_local_syms())
if (InputSection<E> *isec = sym->get_input_section())
if (isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
if (std::string_view x = sym->name();
x == "$a" || x.starts_with("$a.") ||
x == "$t" || x.starts_with("$t.") ||
x == "$d" || x.starts_with("$d."))
syms.push_back(sym);
// Group mapping symbols by input section and sort by address
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
});
// Swap bytes
for (i64 i = 0; i < syms.size(); i++) {
Symbol<E> &sym = *syms[i];
if (sym.name().starts_with("$d"))
continue;
InputSection<E> &isec = *sym.get_input_section();
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
u8 *start = base + sym.value;
u8 *end;
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
end = base + syms[i + 1]->value;
else
end = base + isec.sh_size;
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
for (u8 *p = start; p < end; p += sz)
std::reverse(p, p + sz);
}
});
}
#endif
} // namespace mold
#endif
+1 -1
View File
@@ -66,7 +66,7 @@ static u64 higher(u64 val, u64 pc) {
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
//
// Compensating all the sign-extensions is a bit complicated. The
// psABI gives the following formula.
// psABI gave the following formula.
val = val + 0x8000'0000 + ((val & 0x800) ? (0x1000 - 0x1'0000'0000) : 0);
return page(val) - page(pc - 8);
}
+12 -5
View File
@@ -484,12 +484,17 @@ static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
}
static ElfRel<E> *
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
return (u64)r.r_offset;
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
[](const ElfRel<E> &r, i64 offset) {
return r.r_offset < offset;
});
if (it == rels.end() || it->r_offset != offset)
if (it == rels.end())
return nullptr;
if (it->r_offset != offset)
return nullptr;
return &*it;
}
@@ -504,7 +509,9 @@ 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() || it->r_offset != offset)
if (it == syms.end())
return nullptr;
if (it->r_offset != offset)
return nullptr;
return it->sym;
}
+12 -6
View File
@@ -303,8 +303,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_RISCV_32:
if (E::is_64)
*(U32<E> *)loc = S + A;
assert(E::is_64);
*(U32<E> *)loc = S + A;
break;
case R_RISCV_64:
break;
@@ -321,6 +321,12 @@ 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;
@@ -467,7 +473,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 a0.
// a TP-relative address in x0.
//
// .L0:
// auipc tX, 0
@@ -767,6 +773,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S:
case R_RISCV_TPREL_ADD:
check_tlsle(ctx, sym, rel);
break;
case R_RISCV_64:
@@ -776,9 +785,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_PCREL_LO12_S:
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S:
case R_RISCV_TPREL_ADD:
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL:
+18 -30
View File
@@ -1,4 +1,3 @@
#include "config.h"
#include "mold.h"
#include <filesystem>
@@ -588,7 +587,6 @@ 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;
@@ -707,20 +705,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("no-dynamic-linker")) {
ctx.arg.dynamic_linker = "";
} else if (read_flag("v")) {
Out(ctx) << mold_version;
Out(ctx) << get_mold_version();
version_shown = true;
} else if (read_flag("version")) {
Out(ctx) << mold_version;
Out(ctx) << get_mold_version();
exit(0);
} else if (read_flag("V")) {
Out(ctx) << mold_version
Out(ctx) << get_mold_version()
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
<< " aarch64elf\n aarch64linux\n aarch64elfb\n"
<< " aarch64linuxb\n armelf_linux_eabi\n elf64lriscv\n"
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
<< " shelf_linux\n elf64loongarch\n elf32loongarch";
<< " shelf_linux\n elf64loongarch\n elf32loongarch\n"
<< " arclinux";
version_shown = true;
} else if (read_arg("mllvm")) {
ctx.arg.plugin_opt.emplace_back(arg);
@@ -734,9 +733,7 @@ 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 = ARM32LE::name;
} else if (arg == "armelfb_linux_eabi") {
ctx.arg.emulation = ARM32BE::name;
ctx.arg.emulation = ARM32::name;
} else if (arg == "elf64lriscv") {
ctx.arg.emulation = RV64LE::name;
} else if (arg == "elf64briscv") {
@@ -765,6 +762,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.emulation = LOONGARCH64::name;
} else if (arg == "elf32loongarch") {
ctx.arg.emulation = LOONGARCH32::name;
} else if (arg == "arclinux") {
ctx.arg.emulation = ARC::name;
} else {
Fatal(ctx) << "unknown -m argument: " << arg;
}
@@ -859,8 +858,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("sysroot")) {
ctx.arg.sysroot = arg;
} else if (read_arg("unique")) {
if (!ctx.arg.unique.add(arg, 1))
std::optional<Glob> pat = Glob::compile(arg);
if (!pat)
Fatal(ctx) << "-unique: invalid glob pattern: " << arg;
ctx.arg.unique = std::move(*pat);
} else if (read_arg("unresolved-symbols")) {
if (arg == "report-all" || arg == "ignore-in-shared-libs")
report_undefined = true;
@@ -980,11 +981,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.execute_only = true;
} else if (read_arg("compress-debug-sections")) {
if (arg == "zlib" || arg == "zlib-gabi")
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
ctx.arg.compress_debug_sections = COMPRESS_ZLIB;
else if (arg == "zstd")
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
ctx.arg.compress_debug_sections = COMPRESS_ZSTD;
else if (arg == "none")
ctx.arg.compress_debug_sections = ELFCOMPRESS_NONE;
ctx.arg.compress_debug_sections = COMPRESS_NONE;
else
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
} else if (read_arg("wrap")) {
@@ -1293,10 +1294,6 @@ 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)
@@ -1309,9 +1306,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")) {
allow_shlib_undefined = true;
ctx.arg.allow_shlib_undefined = true;
} else if (read_flag("no-allow-shlib-undefined")) {
allow_shlib_undefined = false;
ctx.arg.allow_shlib_undefined = false;
} else if (read_arg("O")) {
} else if (read_flag("EB")) {
} else if (read_flag("EL")) {
@@ -1418,11 +1415,6 @@ 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;
@@ -1443,9 +1435,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.relocatable)
ctx.arg.static_ = true;
if (ctx.arg.static_)
ctx.arg.dynamic_linker = "";
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_SHUFFLE) {
if (shuffle_sections_seed)
ctx.arg.shuffle_sections_seed = *shuffle_sections_seed;
@@ -1499,9 +1488,6 @@ 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();
@@ -1512,7 +1498,9 @@ 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) {
-85
View File
@@ -1,85 +0,0 @@
// This file implements multi-threaded compression routines. We simply
// create multiple compressed data blocks and concatenate them together.
// Tools supporting compressed sections generally read compressed sections
// until the section end to handle multiple concatenated streams.
//
// Using threads to compress data has a downside. Since the dictionary
// is not shared between compressed streams, the compression ratio is
// slightly sacrificed. However, if the stream size is large enough,
// that loss is negligible in practice.
#include "mold.h"
#include <zlib.h>
#include <zstd.h>
namespace mold {
Compressor::~Compressor() {
for (std::span<u8> shard : shards)
delete[] shard.data();
}
static std::vector<std::span<u8>> split(std::span<u8> input) {
constexpr int BLOCK_SIZE = 1024 * 1024;
std::vector<std::span<u8>> vec;
while (!input.empty()) {
i64 sz = std::min<i64>(BLOCK_SIZE, input.size());
vec.push_back(input.subspan(0, sz));
input = input.subspan(sz);
}
return vec;
}
static std::span<u8> zlib_compress(std::span<u8> input) {
// compression level; must be between 0 to 9
int level = Z_BEST_SPEED; // = 1
unsigned long bufsize = compressBound(input.size());
u8 *buf = new u8[bufsize];
[[maybe_unused]] int r = compress2(buf, &bufsize, input.data(),
input.size(), level);
assert(r == Z_OK);
return {buf, (size_t)bufsize};
}
static std::span<u8> zstd_compress(std::span<u8> input) {
// compression level; must be between 1 to 22
int level = ZSTD_CLEVEL_DEFAULT; // = 3
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
u8 *buf = new u8[bufsize];
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
assert(!ZSTD_isError(sz));
return {buf, sz};
}
Compressor::Compressor(i64 format, u8 *buf, i64 size) {
assert(format == ELFCOMPRESS_ZLIB || format == ELFCOMPRESS_ZSTD);
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
if (format == ELFCOMPRESS_ZLIB)
shards[i] = zlib_compress(inputs[i]);
else
shards[i] = zstd_compress(inputs[i]);
});
compressed_size = 0;
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
void Compressor::write_to(u8 *buf) {
std::vector<i64> offsets(shards.size());
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
});
}
} // namespace mold
+12
View File
@@ -0,0 +1,12 @@
#include "mold.h"
namespace mold {
std::string get_mold_version() {
if (mold_git_hash.empty())
return "mold "s + MOLD_VERSION + " (compatible with GNU ld)";
return "mold "s + MOLD_VERSION + " (" + mold_git_hash +
"; compatible with GNU ld)";
}
} // namespace mold
+73 -10
View File
@@ -1,12 +1,11 @@
#include "elf.h"
#include <sstream>
namespace mold {
static std::string unknown_type(u32 r_type) {
std::stringstream ss;
ss << "unknown (0x" << std::hex << r_type << ")";
return ss.str();
char buf[50];
snprintf(buf, sizeof(buf), "unknown (0x%x)", r_type);
return buf;
}
#define CASE(x) case x: return #x
@@ -233,7 +232,7 @@ std::string rel_to_string<ARM64BE>(u32 r_type) {
}
template <>
std::string rel_to_string<ARM32LE>(u32 r_type) {
std::string rel_to_string<ARM32>(u32 r_type) {
switch (r_type) {
CASE(R_ARM_NONE);
CASE(R_ARM_PC24);
@@ -374,11 +373,6 @@ std::string rel_to_string<ARM32LE>(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) {
@@ -1044,4 +1038,73 @@ std::string rel_to_string<LOONGARCH32>(u32 r_type) {
return rel_to_string<LOONGARCH64>(r_type);
}
template <>
std::string rel_to_string<ARC>(u32 r_type) {
switch (r_type) {
CASE(R_ARC_8);
CASE(R_ARC_16);
CASE(R_ARC_24);
CASE(R_ARC_32);
CASE(R_ARC_N8);
CASE(R_ARC_N16);
CASE(R_ARC_N24);
CASE(R_ARC_N32);
CASE(R_ARC_SDA);
CASE(R_ARC_SECTOFF);
CASE(R_ARC_S21H_PCREL);
CASE(R_ARC_S21W_PCREL);
CASE(R_ARC_S25H_PCREL);
CASE(R_ARC_S25W_PCREL);
CASE(R_ARC_SDA32);
CASE(R_ARC_SDA_LDST);
CASE(R_ARC_SDA_LDST1);
CASE(R_ARC_SDA_LDST2);
CASE(R_ARC_SDA16_LD);
CASE(R_ARC_SDA16_LD1);
CASE(R_ARC_SDA16_LD2);
CASE(R_ARC_S13_PCREL);
CASE(R_ARC_W);
CASE(R_ARC_32_ME);
CASE(R_ARC_32_ME_S);
CASE(R_ARC_N32_ME);
CASE(R_ARC_SECTOFF_ME);
CASE(R_ARC_SDA32_ME);
CASE(R_ARC_W_ME);
CASE(R_ARC_SECTOFF_ME_1);
CASE(R_ARC_SECTOFF_ME_2);
CASE(R_ARC_SECTOFF_1);
CASE(R_ARC_SECTOFF_2);
CASE(R_ARC_SDA_12);
CASE(R_ARC_SDA16_ST2);
CASE(R_ARC_32_PCREL);
CASE(R_ARC_PC32);
CASE(R_ARC_GOT32);
CASE(R_ARC_GOTPC32);
CASE(R_ARC_PLT32);
CASE(R_ARC_COPY);
CASE(R_ARC_GLOB_DAT);
CASE(R_ARC_JMP_SLOT);
CASE(R_ARC_RELATIVE);
CASE(R_ARC_GOTOFF);
CASE(R_ARC_GOTPC);
CASE(R_ARC_S21W_PCREL_PLT);
CASE(R_ARC_S25H_PCREL_PLT);
CASE(R_ARC_JLI_SECTOFF);
CASE(R_ARC_TLS_DTPMOD);
CASE(R_ARC_TLS_TPOFF);
CASE(R_ARC_TLS_GD_GOT);
CASE(R_ARC_TLS_GD_LD);
CASE(R_ARC_TLS_GD_CALL);
CASE(R_ARC_TLS_IE_GOT);
CASE(R_ARC_TLS_DTPOFF);
CASE(R_ARC_TLS_DTPOFF_S9);
CASE(R_ARC_TLS_LE_S9);
CASE(R_ARC_TLS_LE_32);
CASE(R_ARC_S25W_PCREL_PLT);
CASE(R_ARC_S21H_PCREL_PLT);
CASE(R_ARC_NPS_CMEM16);
}
return unknown_type(r_type);
}
} // namespace mold
+98 -16
View File
@@ -13,8 +13,7 @@ struct X86_64;
struct I386;
struct ARM64LE;
struct ARM64BE;
struct ARM32LE;
struct ARM32BE;
struct ARM32;
struct RV64LE;
struct RV64BE;
struct RV32LE;
@@ -29,6 +28,7 @@ struct SH4LE;
struct SH4BE;
struct LOONGARCH64;
struct LOONGARCH32;
struct ARC;
template <typename E> struct ElfSym;
template <typename E> struct ElfShdr;
@@ -89,6 +89,7 @@ enum : u32 {
SHT_HIOS = 0x6fffffff,
SHT_X86_64_UNWIND = 0x70000001,
SHT_ARM_EXIDX = 0x70000001,
SHT_ARC_ATTRIBUTES = 0x70000001,
SHT_ARM_ATTRIBUTES = 0x70000003,
SHT_RISCV_ATTRIBUTES = 0x70000003,
SHT_LOUSER = 0x80000000,
@@ -236,6 +237,7 @@ enum : u32 {
EM_SPARC64 = 43,
EM_X86_64 = 62,
EM_AARCH64 = 183,
EM_ARC_COMPACT2 = 195,
EM_RISCV = 243,
EM_LOONGARCH = 258,
};
@@ -353,16 +355,14 @@ enum : u32 {
};
enum : u32 {
ELFCOMPRESS_NONE = 0,
ELFCOMPRESS_ZLIB = 1,
ELFCOMPRESS_ZSTD = 2,
};
enum : u32 {
EF_ARM_ABI_FLOAT_SOFT = 0x200,
EF_ARM_ABI_FLOAT_HARD = 0x400,
EF_ARM_BE8 = 0x800000,
EF_ARM_EABI_VER5 = 0x5000000,
EF_ARM_ABI_FLOAT_SOFT = 0x00000200,
EF_ARM_ABI_FLOAT_HARD = 0x00000400,
EF_ARM_EABI_VER5 = 0x05000000,
};
enum : u32 {
@@ -1365,6 +1365,71 @@ enum : u32 {
R_LARCH_TLS_DESC_PCREL20_S2 = 126,
};
enum : u32 {
R_ARC_8 = 1,
R_ARC_16 = 2,
R_ARC_24 = 3,
R_ARC_32 = 4,
R_ARC_N8 = 8,
R_ARC_N16 = 9,
R_ARC_N24 = 10,
R_ARC_N32 = 11,
R_ARC_SDA = 12,
R_ARC_SECTOFF = 13,
R_ARC_S21H_PCREL = 14,
R_ARC_S21W_PCREL = 15,
R_ARC_S25H_PCREL = 16,
R_ARC_S25W_PCREL = 17,
R_ARC_SDA32 = 18,
R_ARC_SDA_LDST = 19,
R_ARC_SDA_LDST1 = 20,
R_ARC_SDA_LDST2 = 21,
R_ARC_SDA16_LD = 22,
R_ARC_SDA16_LD1 = 23,
R_ARC_SDA16_LD2 = 24,
R_ARC_S13_PCREL = 25,
R_ARC_W = 26,
R_ARC_32_ME = 27,
R_ARC_32_ME_S = 105,
R_ARC_N32_ME = 28,
R_ARC_SECTOFF_ME = 29,
R_ARC_SDA32_ME = 30,
R_ARC_W_ME = 31,
R_ARC_SECTOFF_ME_1 = 41,
R_ARC_SECTOFF_ME_2 = 42,
R_ARC_SECTOFF_1 = 43,
R_ARC_SECTOFF_2 = 44,
R_ARC_SDA_12 = 45,
R_ARC_SDA16_ST2 = 48,
R_ARC_32_PCREL = 49,
R_ARC_PC32 = 50,
R_ARC_GOT32 = 59,
R_ARC_GOTPC32 = 51,
R_ARC_PLT32 = 52,
R_ARC_COPY = 53,
R_ARC_GLOB_DAT = 54,
R_ARC_JMP_SLOT = 55,
R_ARC_RELATIVE = 56,
R_ARC_GOTOFF = 57,
R_ARC_GOTPC = 58,
R_ARC_S21W_PCREL_PLT = 60,
R_ARC_S25H_PCREL_PLT = 61,
R_ARC_JLI_SECTOFF = 63,
R_ARC_TLS_DTPMOD = 66,
R_ARC_TLS_TPOFF = 68,
R_ARC_TLS_GD_GOT = 69,
R_ARC_TLS_GD_LD = 70,
R_ARC_TLS_GD_CALL = 71,
R_ARC_TLS_IE_GOT = 72,
R_ARC_TLS_DTPOFF = 67,
R_ARC_TLS_DTPOFF_S9 = 73,
R_ARC_TLS_LE_S9 = 74,
R_ARC_TLS_LE_32 = 75,
R_ARC_S25W_PCREL_PLT = 76,
R_ARC_S21H_PCREL_PLT = 77,
R_ARC_NPS_CMEM16 = 78,
};
// Returns true if a given relocation is of type used for direct
// function call.
template <typename E>
@@ -1813,8 +1878,7 @@ 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_arm32le = std::same_as<E, ARM32LE>;
template <typename E> concept is_arm32be = std::same_as<E, ARM32BE>;
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
@@ -1829,9 +1893,9 @@ template <typename E> concept is_sh4le = std::same_as<E, SH4LE>;
template <typename E> concept is_sh4be = std::same_as<E, SH4BE>;
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
template <typename E> concept is_arc = std::same_as<E, ARC>;
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> concept is_arm32 = is_arm32le<E> || is_arm32be<E>;
template <typename E> concept is_arm64 = is_arm64le<E> || is_arm64be<E>;
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
@@ -1925,7 +1989,7 @@ struct ARM64BE : ARM64LE {
static constexpr bool is_le = false;
};
struct ARM32LE {
struct ARM32 {
static constexpr std::string_view name = "arm32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
@@ -1955,11 +2019,6 @@ struct ARM32LE {
};
};
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;
@@ -2238,4 +2297,27 @@ struct LOONGARCH32 {
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26, R_LARCH_CALL36 };
};
struct ARC {
static constexpr std::string_view name = "arc";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 8192;
static constexpr u32 e_machine = EM_ARC_COMPACT2;
static constexpr u32 plt_hdr_size = 24;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0, 0, 0, 0 };
static constexpr u32 R_COPY = R_ARC_COPY;
static constexpr u32 R_GLOB_DAT = R_ARC_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ARC_JMP_SLOT;
static constexpr u32 R_ABS = R_ARC_32;
static constexpr u32 R_RELATIVE = R_ARC_RELATIVE;
static constexpr u32 R_DTPOFF = R_ARC_TLS_DTPOFF;
static constexpr u32 R_TPOFF = R_ARC_TLS_TPOFF;
static constexpr u32 R_DTPMOD = R_ARC_TLS_DTPMOD;
static constexpr u32 R_FUNCALL[] = { R_ARC_S25W_PCREL_PLT };
};
} // namespace mold
-32
View File
@@ -1,32 +0,0 @@
#include "config.h"
#include "mold.h"
#include "mold-git-hash.h"
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
#endif
#if MOLD_USE_SYSTEM_MIMALLOC
// Including mimalloc-new-delete.h overrides the new/delete operators.
// We need it only when using mimalloc as a dynamic library.
// This header should be included in only one source file, so we do
// it in this file.
# include <mimalloc-new-delete.h>
#endif
std::string mold::mold_version =
#ifdef MOLD_GIT_HASH
"mold " MOLD_VERSION " (" MOLD_GIT_HASH "; compatible with GNU ld)";
#else
"mold " MOLD_VERSION " (compatible with GNU ld)";
#endif
int main(int argc, char **argv) {
#if MOLD_USE_MIMALLOC
// Silence mimalloc warnings that users can ignore
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
#endif
return mold::mold_main<mold::X86_64>(argc, argv);
}
-55
View File
@@ -1,55 +0,0 @@
#include "mold.h"
namespace mold {
static std::string_view fatal_mono = "mold: fatal: ";
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
static std::string_view error_mono = "mold: error: ";
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
static std::string_view warning_mono = "mold: warning: ";
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
template <typename E>
Fatal<E>::Fatal(Context<E> &ctx) {
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
}
template <typename E>
[[noreturn]] Fatal<E>::~Fatal() {
out.emit();
cleanup();
_exit(1);
}
template <typename E>
Error<E>::Error(Context<E> &ctx) {
if (ctx.arg.noinhibit_exec) {
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
} else {
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
}
}
template <typename E>
Warn<E>::Warn(Context<E> &ctx) {
if (ctx.arg.suppress_warnings)
return;
out.emplace(std::cerr);
if (ctx.arg.fatal_warnings) {
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
} else {
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
}
}
using E = MOLD_TARGET;
template class Fatal<E>;
template class Error<E>;
template class Warn<E>;
} // namespace mold
+4 -1
View File
@@ -1,4 +1,5 @@
#include "mold.h"
#include "../lib/archive-file.h"
namespace mold {
@@ -138,7 +139,7 @@ static std::string_view get_elf_type(u8 *buf) {
case EM_X86_64:
return X86_64::name;
case EM_ARM:
return is_le ? ARM32LE::name : ARM32BE::name;
return ARM32::name;
case EM_AARCH64:
return is_le ? ARM64LE::name : ARM64BE::name;
case EM_RISCV:
@@ -159,6 +160,8 @@ static std::string_view get_elf_type(u8 *buf) {
return is_le ? SH4LE::name : SH4BE::name;
case EM_LOONGARCH:
return is_64 ? LOONGARCH64::name : LOONGARCH32::name;
case EM_ARC_COMPACT2:
return ARC::name;
default:
return "";
}
+2 -4
View File
@@ -629,10 +629,8 @@ static std::vector<Compunit> read_compunits(Context<E> &ctx) {
template <typename E>
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
if (chunk->is_compressed) {
CompressedSection<E> &sec = *(CompressedSection<E> *)chunk;
return {sec.uncompressed_data.get(), (size_t)sec.chdr.ch_size};
}
if (chunk->is_compressed)
return chunk->uncompressed_data;
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
}
+22 -60
View File
@@ -112,7 +112,7 @@ std::string_view InputFile<E>::get_source_name() const {
template <typename E>
static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
return !(shdr.sh_flags & SHF_ALLOC) && name.starts_with(".debug_");
return !(shdr.sh_flags & SHF_ALLOC) && name.starts_with(".debug");
}
template <typename E>
@@ -317,6 +317,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
}
}
if constexpr (is_arc<E>)
if (shdr.sh_type == SHT_ARC_ATTRIBUTES)
continue;
switch (shdr.sh_type) {
case SHT_GROUP: {
// Get the signature of this section group.
@@ -425,7 +429,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
continue;
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
InputSection<E> *isec = this->sections[i].get();
// Save .llvm_addrsig for --icf=safe.
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
@@ -447,17 +450,19 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
this->has_ctors = true;
if (name == ".eh_frame")
eh_frame_sections.push_back(isec);
if (name == ".debug_info" && !(shdr.sh_flags & SHF_ALLOC))
debug_info = isec;
eh_frame_sections.push_back(this->sections[i].get());
if constexpr (is_ppc32<E>)
if (name == ".got2")
extra.got2 = isec;
extra.got2 = this->sections[i].get();
// Save debug sections for --gdb-index.
if (ctx.arg.gdb_index) {
InputSection<E> *isec = this->sections[i].get();
if (name == ".debug_info")
debug_info = isec;
// If --gdb-index is given, contents of .debug_gnu_pubnames and
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
// in an output file is just a waste of space.
@@ -998,7 +1003,6 @@ 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,7 +1010,6 @@ 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;
}
}
}
@@ -1206,7 +1209,7 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
if (ctx.symtab_shndx)
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + idx;
symtab_base[idx] = *to_output_esym(ctx, sym, strtab_off, xindex);
symtab_base[idx] = to_output_esym(ctx, sym, strtab_off, xindex);
strtab_off += write_string(strtab_base + strtab_off, sym.name());
};
@@ -1274,48 +1277,23 @@ 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);
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]));
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
if (is_default) {
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());
std::string_view mangled_name = save_string(
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
}
}
@@ -1422,22 +1400,6 @@ 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;
}
}
}
}
@@ -1556,7 +1518,7 @@ void SharedFile<E>::populate_symtab(Context<E> &ctx) {
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) +
this->global_symtab_idx + i;
*symtab++ = *to_output_esym(ctx, *sym, strtab_off, xindex);
*symtab++ = to_output_esym(ctx, *sym, strtab_off, xindex);
strtab_off += write_string(strtab + strtab_off, sym->name());
i++;
}
+24 -38
View File
@@ -63,16 +63,16 @@ void InputSection<E>::uncompress(Context<E> &ctx) {
return;
u8 *buf = new u8[sh_size];
copy_contents_to(ctx, buf, sh_size);
copy_contents(ctx, buf);
contents = std::string_view((char *)buf, sh_size);
ctx.string_pool.emplace_back(buf);
uncompressed = true;
}
template <typename E>
void InputSection<E>::copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz) {
void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
memcpy(buf, contents.data(), sz);
memcpy(buf, contents.data(), contents.size());
return;
}
@@ -84,35 +84,16 @@ void InputSection<E>::copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz) {
switch (hdr.ch_type) {
case ELFCOMPRESS_ZLIB: {
z_stream s = {};
inflateInit(&s);
s.next_in = (u8 *)data.data();
s.avail_in = data.size();
s.next_out = buf;
s.avail_out = sz;
int r;
while (s.total_out < sz && (r = inflate(&s, Z_NO_FLUSH)) == Z_OK);
if (s.total_out < sz && r != Z_STREAM_END)
Fatal(ctx) << *this << ": uncompress failed: " << s.msg;
inflateEnd(&s);
unsigned long size = sh_size;
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
Fatal(ctx) << *this << ": uncompress failed";
assert(size == sh_size);
break;
}
case ELFCOMPRESS_ZSTD: {
ZSTD_DCtx *dctx = ZSTD_createDCtx();
ZSTD_inBuffer in = { data.data(), data.size() };
ZSTD_outBuffer out = { buf, (size_t)sz };
while (out.pos < out.size) {
size_t r = ZSTD_decompressStream(dctx, &out, &in);
if (ZSTD_isError(r))
Fatal(ctx) << *this << ": uncompress failed: " << ZSTD_getErrorName(r);
if (r == 0 && out.pos < out.size)
Fatal(ctx) << *this << ": uncompress failed: premature end of input";
}
ZSTD_freeDCtx(dctx);
case ELFCOMPRESS_ZSTD:
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
Fatal(ctx) << *this << ": ZSTD_decompress failed";
break;
}
default:
Fatal(ctx) << *this << ": unsupported compression type: 0x"
<< std::hex << hdr.ch_type;
@@ -133,6 +114,12 @@ static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
<< sym << "' can not be used; recompile with -fPIC";
break;
case COPYREL:
// Create a copy relocation
if (!sym.file->is_dso) {
assert(sym.esym().is_undef_weak());
Error(ctx) << isec << ": cannot create a copy relocation for "
<< sym <<"; recompile with -fPIE or -fPIC";
}
sym.flags |= NEEDS_COPYREL;
break;
case PLT:
@@ -247,7 +234,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
if (deltas.empty()) {
// If a section is not relaxed, we can copy it as a one big chunk.
copy_contents_to(ctx, buf, sh_size);
copy_contents(ctx, buf);
} else {
// A relaxed section is copied piece-wise.
memcpy(buf, contents.data(), deltas[0].offset);
@@ -263,7 +250,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
}
}
} else {
copy_contents_to(ctx, buf, sh_size);
copy_contents(ctx, buf);
}
// Apply relocations
@@ -356,8 +343,8 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
template <typename E>
MergeableSection<E>::MergeableSection(Context<E> &ctx, MergedSection<E> &parent,
std::unique_ptr<InputSection<E>> &isec)
: parent(parent), p2align(isec->p2align), input_section(std::move(isec)) {
input_section->uncompress(ctx);
: parent(parent), p2align(isec->p2align), section(std::move(isec)) {
section->uncompress(ctx);
std::scoped_lock lock(parent.mu);
parent.members.push_back(this);
@@ -393,9 +380,9 @@ static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
// We do not support mergeable sections that have relocations.
template <typename E>
void MergeableSection<E>::split_contents(Context<E> &ctx) {
std::string_view data = input_section->contents;
std::string_view data = section->contents;
if (data.size() > UINT32_MAX)
Fatal(ctx) << *input_section << ": mergeable section too large";
Fatal(ctx) << *section << ": mergeable section too large";
i64 entsize = parent.shdr.sh_entsize;
@@ -405,13 +392,12 @@ void MergeableSection<E>::split_contents(Context<E> &ctx) {
frag_offsets.push_back(pos);
size_t end = find_null(data, pos, entsize);
if (end == data.npos)
Fatal(ctx) << *input_section << ": string is not null terminated";
Fatal(ctx) << *section << ": string is not null terminated";
pos = end + entsize;
}
} else {
if (data.size() % entsize)
Fatal(ctx) << *input_section
<< ": section size is not multiple of sh_entsize";
Fatal(ctx) << *section << ": section size is not multiple of sh_entsize";
frag_offsets.reserve(data.size() / entsize);
for (i64 pos = 0; pos < data.size(); pos += entsize)
+9 -11
View File
@@ -29,8 +29,8 @@
//
// 4. Continue the rest of the linking process as usual.
//
// When gcc or clang inovkes ld, they pass `-plugin /path/to/linker-plugin.so`
// to the linker. The given .so file provides a way to call the compiler
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
// the linker. The given .so file provides a way to call the compiler
// backend.
//
// The linker plugin API is documented at
@@ -425,7 +425,7 @@ get_api_version(const char *plugin_identifier,
if (LAPI_V1 < minimal_api_supported)
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
std::string version = mold_version + "\0"s;
std::string version = get_mold_version() + "\0"s;
*linker_identifier = "mold";
*linker_version = version.data();
@@ -600,10 +600,10 @@ create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
template <typename E>
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
if (ctx.arg.plugin.empty())
Fatal(ctx) << mf->name << ": 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.";
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.";
load_lto_plugin(ctx);
@@ -670,10 +670,8 @@ 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) {
std::string_view key = save_string(ctx, psym.comdat_key);
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, key);
}
if (psym.comdat_key)
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, psym.comdat_key);
}
obj->symbol_strtab = save_string(ctx, strtab);
+29 -29
View File
@@ -1,9 +1,11 @@
#include "mold.h"
#include "../lib/archive-file.h"
#include <cstring>
#include <functional>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/global_control.h>
#include <unordered_set>
#ifdef _WIN32
@@ -13,6 +15,13 @@
# include <unistd.h>
#endif
#ifdef MOLD_X86_64
int main(int argc, char **argv) {
mold::set_mimalloc_options();
return mold::mold_main<mold::X86_64>(argc, argv);
}
#endif
namespace mold {
template <typename E>
@@ -35,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->as_needed =
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
@@ -56,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->as_needed =
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
@@ -72,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->as_needed = rctx.as_needed;
file->is_reachable = !rctx.as_needed;
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
@@ -297,8 +306,8 @@ int mold_main(int argc, char **argv) {
acquire_global_lock();
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
// Handle --wrap options if any.
for (std::string_view name : ctx.arg.wrap)
@@ -487,8 +496,7 @@ int mold_main(int argc, char **argv) {
// create as few segments as possible.
sort_output_sections(ctx);
// Handle --separate-debug-file.
if (ctx.gnu_debuglink)
if (!ctx.arg.separate_debug_file.empty())
separate_debug_sections(ctx);
// Compute sizes of output sections while assigning offsets
@@ -506,10 +514,6 @@ int mold_main(int argc, char **argv) {
// be added to .dynsym.
sort_dynsyms(ctx);
// Sort .debug_info contents so that DWARF32 debug info precedes that of
// DWARF64. This is to mitigate the possibility of a relocation overflow.
sort_debug_info_sections(ctx);
// Print reports about undefined symbols, if needed.
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
report_undef_errors(ctx);
@@ -532,6 +536,13 @@ 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);
@@ -551,14 +562,6 @@ 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);
@@ -575,8 +578,8 @@ int mold_main(int argc, char **argv) {
// GOT or PLT addresses have a correct final value.
// If --compress-debug-sections is given, compress .debug_* sections
// using zlib or zstd.
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE) {
// using zlib.
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
compress_debug_sections(ctx);
filesize = set_osec_offsets(ctx);
}
@@ -598,9 +601,6 @@ int mold_main(int argc, char **argv) {
// Copy input sections to the output file and apply relocations.
copy_chunks(ctx);
if constexpr (is_arm32be<E>)
arm32be_swap_bytes(ctx);
if constexpr (is_x86_64<E>)
if (ctx.arg.z_rewrite_endbr)
rewrite_endbr(ctx);
@@ -612,7 +612,7 @@ int mold_main(int argc, char **argv) {
// .gdb_index's contents cannot be constructed before applying
// relocations to other debug sections. We have relocated debug
// sections now, so write the .gdb_index section.
if (ctx.gdb_index && !ctx.gnu_debuglink)
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
write_gdb_index(ctx);
// .note.gnu.build-id section contains a cryptographic hash of the
@@ -621,7 +621,7 @@ int mold_main(int argc, char **argv) {
if (ctx.buildid)
write_build_id(ctx);
if (ctx.gnu_debuglink)
if (!ctx.arg.separate_debug_file.empty())
write_gnu_debuglink(ctx);
t_copy.stop();
@@ -642,7 +642,7 @@ int mold_main(int argc, char **argv) {
if (ctx.arg.print_map)
print_map(ctx);
if (ctx.gnu_debuglink)
if (!ctx.arg.separate_debug_file.empty())
write_separate_debug_file(ctx);
// Show stats numbers
+8 -6
View File
@@ -19,12 +19,14 @@ static Map<E> get_map(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file == file && sym->get_type() != STT_SECTION) {
if (InputSection<E> *isec = sym->get_input_section()) {
typename Map<E>::accessor acc;
map.insert(acc, {isec, {}});
acc->second.push_back(sym);
}
if (sym->file != file || sym->get_type() == STT_SECTION)
continue;
if (InputSection<E> *isec = sym->get_input_section()) {
assert(file == &isec->file);
typename Map<E>::accessor acc;
map.insert(acc, {isec, {}});
acc->second.push_back(sym);
}
}
});
+48 -308
View File
@@ -1,6 +1,6 @@
#pragma once
#include "../lib/lib.h"
#include "../lib/common.h"
#include "elf.h"
#include <cassert>
@@ -16,7 +16,6 @@
#include <string_view>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/global_control.h>
#include <tbb/spin_mutex.h>
#include <tbb/task_group.h>
#include <type_traits>
@@ -50,207 +49,7 @@ struct ReaderContext;
template <typename E>
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
extern std::string mold_version;
//
// error.cc
//
// Some C++ stdlibs don't support std::osyncstream even though
// it's is in the C++20 standard. So we implement it ourselves.
class SyncStream {
public:
SyncStream(std::ostream &out) : out(out) {}
~SyncStream() { emit(); }
void emit() {
if (!emitted) {
std::scoped_lock lock(mu);
out << ss.str() << '\n';
emitted = true;
}
}
template <typename T> SyncStream &operator<<(T &&val) {
ss << std::forward<T>(val);
return *this;
}
private:
std::ostream &out;
std::stringstream ss;
bool emitted = false;
static inline std::mutex mu;
};
template <typename E>
class Out {
public:
Out(Context<E> &ctx) {}
template <typename T> Out &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cout};
};
template <typename E>
class Fatal {
public:
Fatal(Context<E> &ctx);
[[noreturn]] ~Fatal();
template <typename T> Fatal &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename E>
class Error {
public:
Error(Context<E> &ctx);
template <typename T> Error &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename E>
class Warn {
public:
Warn(Context<E> &ctx);
template <typename T> Warn &operator<<(T &&val) {
if (out)
*out << std::forward<T>(val);
return *this;
}
private:
std::optional<SyncStream> out;
};
//
// signal-unix.cc
//
inline char *output_tmpfile = nullptr;
inline u8 *output_buffer_start = nullptr;
inline u8 *output_buffer_end = nullptr;
std::string errno_string();
void cleanup();
void install_signal_handler();
//
// mapped-file-unix.cc
//
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
class MappedFile {
public:
~MappedFile() { unmap(); }
void unmap();
void close_fd();
void reopen_fd(const std::string &path);
template <typename E>
MappedFile *slice(Context<E> &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile;
mf->name = name;
mf->data = data + start;
mf->size = size;
mf->parent = this;
ctx.mf_pool.emplace_back(mf);
return mf;
}
std::string_view get_contents() {
return std::string_view((char *)data, size);
}
i64 get_offset() const {
return parent ? (data - parent->data + parent->get_offset()) : 0;
}
// Returns a string that uniquely identify a file that is possibly
// in an archive.
std::string get_identifier() const {
if (parent) {
// We use the file offset within an archive as an identifier
// because archive members may have the same name.
return parent->name + ":" + std::to_string(get_offset());
}
if (thin_parent) {
// If this is a thin archive member, the filename part is
// guaranteed to be unique.
return thin_parent->name + ":" + name;
}
return name;
}
std::string name;
u8 *data = nullptr;
i64 size = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
// For --dependency-file
bool is_dependency = true;
#ifdef _WIN32
HANDLE fd = INVALID_HANDLE_VALUE;
#else
int fd = -1;
#endif
};
MappedFile *open_file_impl(const std::string &path, std::string &error);
template <typename E>
MappedFile *open_file(Context<E> &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
std::string error;
MappedFile *mf = open_file_impl(path, error);
if (!error.empty())
Fatal(ctx) << error;
if (mf)
ctx.mf_pool.emplace_back(mf);
return mf;
}
template <typename E>
MappedFile *must_open_file(Context<E> &ctx, std::string path) {
MappedFile *mf = open_file(ctx, path);
if (!mf)
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
return mf;
}
//
// jobs-unix.cc
//
void acquire_global_lock();
void release_global_lock();
std::string get_mold_version();
//
// Mergeable section fragments
@@ -266,13 +65,9 @@ struct __attribute__((aligned(4))) SectionFragment {
}
MergedSection<E> &output_section;
i64 offset = -1;
u32 offset = -1;
Atomic<u8> p2align = 0;
Atomic<bool> is_alive = false;
// True if this fragment must be placed within 2^32 bytes from the
// start of the output section.
Atomic<bool> is_32bit = false;
};
// Additional class members for dynamic symbols. Because most symbols
@@ -467,9 +262,9 @@ struct FdeRecord {
template <typename E>
struct InputSectionExtras {};
template <is_arm32 E>
struct InputSectionExtras<E> {
InputSection<E> *exidx = nullptr;
template <>
struct InputSectionExtras<ARM32> {
InputSection<ARM32> *exidx = nullptr;
};
struct RelocDelta {
@@ -499,7 +294,7 @@ public:
InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx);
void uncompress(Context<E> &ctx);
void copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz);
void copy_contents(Context<E> &ctx, u8 *buf);
void scan_relocations(Context<E> &ctx);
void write_to(Context<E> &ctx, u8 *buf);
void apply_reloc_alloc(Context<E> &ctx, u8 *base);
@@ -606,22 +401,6 @@ template <typename E>
std::string_view
get_machine_type(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf);
//
// compress.cc
//
class Compressor {
public:
Compressor(i64 format, u8 *buf, i64 size);
void write_to(u8 *buf);
~Compressor();
i64 compressed_size = 0;
protected:
std::vector<std::span<u8>> shards;
};
//
// output-chunks.cc
//
@@ -670,13 +449,14 @@ public:
// For --gdb-index
bool is_compressed = false;
std::vector<u8> uncompressed_data;
// Some synethetic sections add local symbols to the output.
// For example, range extension thunks adds function_name@thunk
// symbol for each thunk entry. The following members are used
// for such synthesizing symbols.
virtual void compute_symtab_size(Context<E> &ctx) {}
virtual void populate_symtab(Context<E> &ctx) {}
virtual void compute_symtab_size(Context<E> &ctx) {};
virtual void populate_symtab(Context<E> &ctx) {};
i64 local_symtab_idx = 0;
i64 num_local_symtab = 0;
@@ -790,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);
void create_range_extension_thunks(Context<E> &ctx, bool first_pass);
std::vector<InputSection<E> *> members;
std::vector<std::unique_ptr<Thunk<E>>> thunks;
@@ -1017,8 +797,8 @@ public:
};
template <typename E>
std::optional<ElfSym<E>>
to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name, U32<E> *shndx);
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
U32<E> *shndx);
template <typename E>
class SymtabSection : public Chunk<E> {
@@ -1347,11 +1127,9 @@ public:
CompressedSection(Context<E> &ctx, Chunk<E> &chunk);
void copy_buf(Context<E> &ctx) override;
std::unique_ptr<u8[]> uncompressed_data;
ElfChdr<E> chdr = {};
private:
std::optional<Compressor> compressor;
ElfChdr<E> chdr = {};
std::unique_ptr<Compressor> compressor;
};
template <typename E>
@@ -1523,11 +1301,11 @@ public:
std::string_view get_contents(i64 idx);
MergedSection<E> &parent;
u8 p2align = 0;
std::unique_ptr<InputSection<E>> input_section;
std::vector<SectionFragment<E> *> fragments;
u8 p2align = 0;
private:
std::unique_ptr<InputSection<E>> section;
std::vector<u32> frag_offsets;
std::vector<u32> hashes;
};
@@ -1559,7 +1337,6 @@ 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;
@@ -1576,7 +1353,6 @@ public:
std::string_view shstrtab;
std::string_view symbol_strtab;
bool as_needed = false;
bool has_init_array = false;
bool has_ctors = false;
@@ -1653,7 +1429,6 @@ public:
bool is_lto_obj = false;
bool is_gcc_offload_obj = false;
bool is_rust_obj = false;
bool is_dwarf32 = false;
i64 fde_idx = 0;
i64 fde_offset = 0;
@@ -1712,7 +1487,6 @@ public:
std::string soname;
std::vector<std::string_view> version_strings;
std::vector<Symbol<E> *> symbols2;
std::vector<ElfSym<E>> elf_syms2;
private:
@@ -1793,22 +1567,6 @@ template <typename E>
std::vector<DynamicPattern>
parse_dynamic_list(Context<E> &ctx, std::string_view path);
//
// archive-file.cc
//
template <typename E>
std::vector<MappedFile *>
read_thin_archive_members(Context<E> &ctx, MappedFile *mf);
template <typename E>
std::vector<MappedFile *>
read_fat_archive_members(Context<E> &ctx, MappedFile *mf);
template <typename E>
std::vector<MappedFile *>
read_archive_members(Context<E> &ctx, MappedFile *mf);
//
// lto.cc
//
@@ -1929,7 +1687,6 @@ template <typename E> void scan_relocations(Context<E> &);
template <typename E> void compute_imported_symbol_weakness(Context<E> &);
template <typename E> void construct_relr(Context<E> &);
template <typename E> void sort_dynsyms(Context<E> &);
template <typename E> void sort_debug_info_sections(Context<E> &);
template <typename E> void create_output_symtab(Context<E> &);
template <typename E> void report_undef_errors(Context<E> &);
template <typename E> void create_reloc_sections(Context<E> &);
@@ -1960,34 +1717,28 @@ void rewrite_endbr(Context<X86_64> &ctx);
// arch-arm32.cc
//
template <is_arm32 E>
class Arm32ExidxSection : public Chunk<E> {
class Arm32ExidxSection : public Chunk<ARM32> {
public:
Arm32ExidxSection(OutputSection<E> &osec) : output_section(osec) {
Arm32ExidxSection(OutputSection<ARM32> &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<E> &ctx) override;
void update_shdr(Context<E> &ctx) override;
void remove_duplicate_entries(Context<E> &ctx);
void copy_buf(Context<E> &ctx) override;
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;
private:
std::vector<u8> get_contents(Context<E> &ctx);
std::vector<u8> get_contents(Context<ARM32> &ctx);
OutputSection<E> &output_section;
OutputSection<ARM32> &output_section;
};
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);
template <> u64 get_eflags(Context<ARM32> &ctx);
void create_arm_exidx_section(Context<ARM32> &ctx);
//
// arch-riscv.cc
@@ -2057,6 +1808,12 @@ public:
template <> u64 get_eflags(Context<PPC64V2> &ctx);
//
// arch-arc.cc
//
template <> u64 get_eflags(Context<ARC> &ctx);
//
// main.cc
//
@@ -2080,6 +1837,8 @@ struct BuildId {
i64 hash_size = 0;
};
typedef enum { COMPRESS_NONE, COMPRESS_ZLIB, COMPRESS_ZSTD } CompressKind;
typedef enum {
UNRESOLVED_ERROR,
UNRESOLVED_WARN,
@@ -2135,9 +1894,9 @@ struct ContextExtras<E> {
NotePropertySection<E> *note_property = nullptr;
};
template <is_arm32 E>
struct ContextExtras<E> {
Arm32ExidxSection<E> *exidx = nullptr;
template <>
struct ContextExtras<ARM32> {
Arm32ExidxSection *exidx = nullptr;
};
template <is_riscv E>
@@ -2175,7 +1934,7 @@ struct ContextExtras<SPARC64> {
template <typename E>
struct Context {
Context() {
arg.entry = get_symbol(*this, "_start");
arg.entry = get_symbol(*this, is_arc<E> ? "__start" : "_start");
arg.fini = get_symbol(*this, "_fini");
arg.init = get_symbol(*this, "_init");
@@ -2197,8 +1956,8 @@ struct Context {
BsymbolicKind Bsymbolic = BSYMBOLIC_NONE;
BuildId build_id;
CetReportKind z_cet_report = CET_REPORT_NONE;
Glob undefined_glob;
Glob unique;
CompressKind compress_debug_sections = COMPRESS_NONE;
MultiGlob undefined_glob;
SeparateCodeKind z_separate_code = NOSEPARATE_CODE;
ShuffleSectionsKind shuffle_sections = SHUFFLE_SECTIONS_NONE;
Symbol<E> *entry = nullptr;
@@ -2208,7 +1967,6 @@ 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;
@@ -2282,12 +2040,12 @@ struct Context {
bool z_shstk = false;
bool z_start_stop_visibility_protected = false;
bool z_text = false;
i64 compress_debug_sections = ELFCOMPRESS_NONE;
i64 filler = -1;
i64 spare_dynamic_tags = 5;
i64 spare_program_headers = 0;
i64 thread_count = 0;
i64 z_stack_size = 0;
std::optional<Glob> unique;
std::optional<u64> physical_image_base;
std::optional<std::vector<Symbol<E> *>> retain_symbols_file;
std::string Map;
@@ -2326,7 +2084,6 @@ struct Context {
u64 shuffle_sections_seed = 0;
} arg;
std::optional<tbb::global_control> global_limit;
std::vector<VersionPattern> version_patterns;
std::vector<DynamicPattern> dynamic_list_patterns;
i64 default_version = VER_NDX_UNSPECIFIED;
@@ -2635,9 +2392,6 @@ 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.
@@ -2897,7 +2651,7 @@ InputSection<E>::get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag) {
return {};
std::string_view str = name();
if (!str.starts_with(".debug_"))
if (!str.starts_with(".debug"))
return {};
// If the section was dead due to ICF, we don't want to emit debug
@@ -2941,8 +2695,8 @@ template <typename E>
std::string_view MergeableSection<E>::get_contents(i64 i) {
i64 cur = frag_offsets[i];
if (i == frag_offsets.size() - 1)
return input_section->contents.substr(cur);
return input_section->contents.substr(cur, frag_offsets[i + 1] - cur);
return section->contents.substr(cur);
return section->contents.substr(cur, frag_offsets[i + 1] - cur);
}
template <typename E>
@@ -2980,11 +2734,6 @@ 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);
@@ -3272,16 +3021,12 @@ 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>;
auto it = ranges::lower_bound(vec, lo);
if (it == vec.end())
Fatal(ctx) << "range extension thunk out of range: " << *this;
i64 disp = *it - P;
u64 val = *ranges::lower_bound(vec, lo);
i64 disp = val - P;
if (disp < -branch_distance<E> || branch_distance<E> <= disp)
Fatal(ctx) << "range extension thunk out of range: " << *this;
return *it;
return val;
}
template <typename E>
@@ -3291,11 +3036,6 @@ 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();
}
+76 -98
View File
@@ -834,6 +834,20 @@ 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) {
@@ -1162,9 +1176,6 @@ 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;
@@ -1304,16 +1315,18 @@ 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});
} 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 {
// We know the symbol's exact run-time address at link-time.
add({idx, sym->get_addr(ctx, NO_PLT)});
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())
add({idx, sym->get_addr(ctx, NO_PLT), E::R_RELATIVE});
else
add({idx, sym->get_addr(ctx, NO_PLT)});
}
// Create GOT entries for TLVs.
@@ -1725,8 +1738,8 @@ static u64 get_symbol_size(Symbol<E> &sym) {
}
template <typename E>
std::optional<ElfSym<E>>
to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name, U32<E> *shn_xindex) {
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
U32<E> *shn_xindex) {
ElfSym<E> esym;
memset(&esym, 0, sizeof(esym));
@@ -1835,8 +1848,7 @@ to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name, U32<E> *shn_xindex)
if (0 <= shndx && shndx < SHN_LORESERVE) {
esym.st_shndx = shndx;
} else if (SHN_LORESERVE <= shndx) {
if (!shn_xindex)
return {};
assert(shn_xindex);
esym.st_shndx = SHN_XINDEX;
*shn_xindex = shndx;
}
@@ -1870,17 +1882,7 @@ void DynsymSection<E>::copy_buf(Context<E> &ctx) {
for (i64 i = 1; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
std::optional<ElfSym<E>> esym = to_output_esym(ctx, sym, offset, nullptr);
if (!esym) {
Error(ctx) << ctx.arg.output
<< ": .dynsym: too many output sections: "
<< (ctx.shdr->shdr.sh_size / sizeof(ElfShdr<E>))
<< " requested, but ELF allows at most 65279";
return;
}
buf[sym.get_dynsym_idx(ctx)] = *esym;
buf[sym.get_dynsym_idx(ctx)] = to_output_esym(ctx, sym, offset, nullptr);
offset += sym.name().size() + 1;
}
}
@@ -1992,7 +1994,7 @@ get_merged_output_name(Context<E> &ctx, std::string_view name, u64 flags,
i64 entsize, i64 addralign) {
if (ctx.arg.relocatable && !ctx.arg.relocatable_merge_sections)
return name;
if (!ctx.arg.unique.empty() && ctx.arg.unique.find(name) != -1)
if (ctx.arg.unique && ctx.arg.unique->match(name))
return name;
// GCC seems to create sections named ".rodata.strN.<mangled-symbol-name>.M"
@@ -2104,7 +2106,7 @@ static void add_comment_strings(Context<E> &ctx) {
};
// Add an identification string to .comment.
add(mold_version);
add(get_mold_version());
// Embed command line arguments for debugging.
char *env = getenv("MOLD_DEBUG");
@@ -2142,54 +2144,43 @@ void MergedSection<E>::compute_section_size(Context<E> &ctx) {
if (!resolved)
resolve(ctx);
std::vector<i64> sizes(map.NUM_SHARDS * 2);
std::vector<i64> sizes(map.NUM_SHARDS);
tbb::parallel_for((i64)0, map.NUM_SHARDS, [&](i64 i) {
using Entry = typename decltype(map)::Entry;
std::vector<Entry *> entries = map.get_sorted_entries(i);
i64 off1 = 0;
i64 off2 = 0;
i64 offset = 0;
for (Entry *ent : entries) {
SectionFragment<E> &frag = ent->value;
if (frag.is_alive) {
if (frag.is_32bit) {
off1 = align_to(off1, 1 << frag.p2align);
frag.offset = off1;
off1 += ent->keylen;
} else {
off2 = align_to(off2, 1 << frag.p2align);
frag.offset = off2;
off2 += ent->keylen;
}
offset = align_to(offset, 1 << frag.p2align);
frag.offset = offset;
offset += ent->keylen;
}
}
sizes[i] = off1;
sizes[i + map.NUM_SHARDS] = off2;
sizes[i] = offset;
});
i64 shard_size = map.nbuckets / map.NUM_SHARDS;
shard_offsets.resize(sizes.size() + 1);
shard_offsets.resize(map.NUM_SHARDS + 1);
for (i64 i = 1; i < sizes.size() + 1; i++)
for (i64 i = 1; i < map.NUM_SHARDS + 1; i++)
shard_offsets[i] =
align_to(shard_offsets[i - 1] + sizes[i - 1], this->shdr.sh_addralign);
this->shdr.sh_size = shard_offsets.back();
tbb::parallel_for((i64)1, map.NUM_SHARDS, [&](i64 i) {
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
SectionFragment<E> &frag = map.entries[j].value;
if (frag.is_alive) {
if (frag.is_32bit)
frag.offset += shard_offsets[i];
else
frag.offset += shard_offsets[i + map.NUM_SHARDS];
}
if (frag.is_alive)
frag.offset += shard_offsets[i];
}
});
this->shdr.sh_size = shard_offsets[map.NUM_SHARDS];
if (this->shdr.sh_size > UINT32_MAX)
Fatal(ctx) << this->name << ": output section too large";
}
template <typename E>
@@ -2205,11 +2196,8 @@ void MergedSection<E>::write_to(Context<E> &ctx, u8 *buf) {
// There might be gaps between strings to satisfy alignment requirements.
// If that's the case, we need to zero-clear them.
if (this->shdr.sh_addralign > 1 &&
this->shdr.sh_addralign != this->shdr.sh_entsize) {
this->shdr.sh_addralign != this->shdr.sh_entsize)
memset(buf + shard_offsets[i], 0, shard_offsets[i + 1] - shard_offsets[i]);
i64 j = map.NUM_SHARDS + i;
memset(buf + shard_offsets[j], 0, shard_offsets[j + 1] - shard_offsets[j]);
}
// Copy strings
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
@@ -2459,30 +2447,21 @@ void EhFrameRelocSection<E>::copy_buf(Context<E> &ctx) {
template <typename E>
void CopyrelSection<E>::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
assert(!ctx.arg.shared);
if (sym->has_copyrel)
return;
if (!sym->file->is_dso) {
assert(sym->esym().is_undef_weak());
Error(ctx) << *sym->file << ": cannot create a copy relocation for "
<< *sym <<"; recompile with -fPIE or -fPIC";
return;
}
assert(!ctx.arg.shared);
assert(sym->file->is_dso);
if (sym->esym().st_visibility == STV_PROTECTED) {
if (sym->esym().st_visibility == STV_PROTECTED)
Error(ctx) << *sym->file
<< ": cannot create a copy relocation for protected symbol '"
<< *sym << "'; recompile with -fPIC";
return;
}
if (!ctx.arg.z_copyreloc) {
if (!ctx.arg.z_copyreloc)
Error(ctx) << "-z nocopyreloc: " << *sym->file
<< ": cannot create a copy relocation for symbol '" << *sym
<< "'; recompile with -fPIC";
return;
}
symbols.push_back(sym);
@@ -2655,14 +2634,6 @@ 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;
@@ -2838,33 +2809,42 @@ void NotePropertySection<E>::copy_buf(Context<E> &ctx) {
template <typename E>
CompressedSection<E>::CompressedSection(Context<E> &ctx, Chunk<E> &chunk) {
// Allocate a temporary buffer to write uncompressed contents. Note
// that we use u8[] instead of std::vector<u8> to avoid the cost of
// zero-initialization, as sh_size can be very large.
std::unique_ptr<u8[]> buf(new u8[chunk.shdr.sh_size]);
assert(chunk.name.starts_with(".debug"));
this->name = chunk.name;
this->is_compressed = true;
// Write uncompressed contents and then compress them
chunk.write_to(ctx, buf.get());
compressor.emplace(ctx.arg.compress_debug_sections, buf.get(),
chunk.shdr.sh_size);
this->uncompressed_data.resize(chunk.shdr.sh_size);
u8 *buf = this->uncompressed_data.data();
chunk.write_to(ctx, buf);
switch (ctx.arg.compress_debug_sections) {
case COMPRESS_ZLIB:
chdr.ch_type = ELFCOMPRESS_ZLIB;
compressor.reset(new ZlibCompressor(buf, chunk.shdr.sh_size));
break;
case COMPRESS_ZSTD:
chdr.ch_type = ELFCOMPRESS_ZSTD;
compressor.reset(new ZstdCompressor(buf, chunk.shdr.sh_size));
break;
default:
unreachable();
}
// Compute header field values
chdr.ch_type = ctx.arg.compress_debug_sections;
chdr.ch_size = chunk.shdr.sh_size;
chdr.ch_addralign = chunk.shdr.sh_addralign;
this->name = chunk.name;
this->shndx = chunk.shndx;
this->is_compressed = true;
this->shdr = chunk.shdr;
this->shdr.sh_flags |= SHF_COMPRESSED;
this->shdr.sh_addralign = 1;
this->shdr.sh_size = sizeof(chdr) + compressor->compressed_size;
this->shndx = chunk.shndx;
// We can discard the uncompressed contents unless --gdb-index is given
if (ctx.arg.gdb_index)
this->uncompressed_data = std::move(buf);
// We don't need to keep the original data unless --gdb-index is given.
if (!ctx.arg.gdb_index) {
this->uncompressed_data.clear();
this->uncompressed_data.shrink_to_fit();
}
}
template <typename E>
@@ -3047,8 +3027,6 @@ template class GnuDebuglinkSection<E>;
template Chunk<E> *find_chunk(Context<E> &, u32);
template Chunk<E> *find_chunk(Context<E> &, std::string_view);
template i64 to_phdr_flags(Context<E> &ctx, Chunk<E> *chunk);
template std::optional<ElfSym<E>>
to_output_esym(Context<E> &, Symbol<E> &, u32, U32<E> *);
template ElfSym<E> to_output_esym(Context<E> &, Symbol<E> &, u32, U32<E> *);
} // namespace mold
+166 -318
View File
@@ -1,7 +1,6 @@
#include "config.h"
#include "mold.h"
#include "blake3.h"
#include <blake3.h>
#include <fstream>
#include <functional>
#include <optional>
@@ -27,10 +26,8 @@ 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 == ARM32LE::name)
return mold_main<ARM32LE>(argc, argv);
if (target == ARM32BE::name)
return mold_main<ARM32BE>(argc, argv);
if (target == ARM32::name)
return mold_main<ARM32>(argc, argv);
if (target == RV64LE::name)
return mold_main<RV64LE>(argc, argv);
if (target == RV64BE::name)
@@ -59,6 +56,8 @@ int redo_main(Context<E> &ctx, int argc, char **argv) {
return mold_main<LOONGARCH32>(argc, argv);
if (target == LOONGARCH64::name)
return mold_main<LOONGARCH64>(argc, argv);
if (target == ARC::name)
return mold_main<ARC>(argc, argv);
abort();
}
@@ -207,8 +206,7 @@ static void mark_live_objects(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
if (!file->is_reachable) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file == file &&
ctx.arg.undefined_glob.find(sym->name()) != -1) {
if (sym->file == file && ctx.arg.undefined_glob.find(sym->name())) {
file->is_reachable = true;
sym->gc_root = true;
break;
@@ -219,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 : roots)
if (!file->as_needed)
file->is_reachable = true;
for (InputFile<E> *file : ctx.objs)
if (file->is_reachable)
roots.push_back(file);
std::erase_if(roots, [](InputFile<E> *file) { return !file->is_reachable; });
for (InputFile<E> *file : ctx.dsos)
if (file->is_reachable)
roots.push_back(file);
tbb::parallel_for_each(roots, [&](InputFile<E> *file,
tbb::feeder<InputFile<E> *> &feeder) {
@@ -251,15 +249,25 @@ 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) {
std::vector<InputFile<E> *> files;
append(files, ctx.objs);
append(files, ctx.dsos);
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];
tbb::parallel_for_each(files, [](InputFile<E> *file) {
std::span<Symbol<E> *> syms = file->get_global_syms();
for (i64 i = 0; i < syms.size(); i++)
if (syms[i]->is_versioned_default)
syms[i] = (Symbol<E> *)syms[i]->origin;
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;
}
}
});
}
@@ -304,68 +312,69 @@ 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 redo = false;
std::atomic_bool flag = 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;
redo = true;
flag = true;
}
}
}
});
if (!redo)
break;
if (!flag)
return;
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
@@ -489,7 +498,7 @@ static std::string_view
get_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
if (ctx.arg.relocatable && !ctx.arg.relocatable_merge_sections)
return name;
if (!ctx.arg.unique.empty() && ctx.arg.unique.find(name) != -1)
if (ctx.arg.unique && ctx.arg.unique->match(name))
return name;
if (flags & SHF_MERGE)
return name;
@@ -1042,7 +1051,7 @@ void write_repro_file(Context<E> &ctx) {
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
tar->append("response.txt", create_response_file(ctx));
tar->append("version.txt", mold_version + "\n");
tar->append("version.txt", get_mold_version() + "\n");
std::unordered_set<std::string_view> seen;
@@ -1118,41 +1127,35 @@ void check_shlib_undefined(Context<E> &ctx) {
return false;
};
// Skip test if we don't have a complete set of shared object files
// for the program, because if there's a missing .so, an undefined
// symbol might be defined by that library.
auto do_test = [&] {
std::unordered_set<std::string_view> sonames;
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
sonames.insert(file->soname);
// Obtain a list of known shared library names.
std::unordered_set<std::string_view> sonames;
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
sonames.insert(file->soname);
for (SharedFile<E> *file : ctx.dsos)
for (std::string_view soname : file->get_dt_needed(ctx))
if (!sonames.contains(soname))
return false;
return true;
};
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
// Skip the file if it depends on a file that we know nothing about.
// This is because missing symbols may be provided by that unknown file.
for (std::string_view needed : file->get_dt_needed(ctx))
if (!sonames.contains(needed))
return;
if (do_test()) {
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
// Check if all undefined symbols have been resolved.
for (i64 i = 0; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
if (esym.is_undef() && !esym.is_weak() && !sym.file &&
!is_sparc_register(esym))
Error(ctx) << *file << ": --no-allow-shlib-undefined: undefined symbol: "
<< sym;
}
});
}
// Check if all undefined symbols have been resolved.
for (i64 i = 0; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
if (esym.is_undef() && !esym.is_weak() && !sym.file &&
!is_sparc_register(esym))
Error(ctx) << *file << ": --no-allow-shlib-undefined: undefined symbol: "
<< sym;
}
});
// Beyond this point, DSOs that are not referenced directly by any
// object file are not needed. They were kept by
// SharedFile<E>::mark_live_objects just for this pass. Therefore,
// remove unneeded DSOs from the list now.
for (SharedFile<E> *file : ctx.dsos)
file->is_reachable = !file->as_needed;
file->is_reachable = false;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms())
@@ -1168,6 +1171,10 @@ 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;
@@ -1176,35 +1183,23 @@ void check_symbol_types(Context<E> &ctx) {
return ty;
};
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) {
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<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]);
Symbol<E> &sym = *file->symbols[i];
if (!sym.file || sym.file == file)
continue;
if (Symbol<E> *sym = file->symbols2[i])
if (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);
}
}
});
}
@@ -1302,142 +1297,6 @@ void sort_ctor_dtor(Context<E> &ctx) {
}
}
// Returns true if a given section contains a DWARF32 debug record.
// `isec` must be a .debug_info section.
template <typename E>
static bool is_dwarf32(Context<E> &ctx, InputSection<E> *isec) {
if (!isec)
return true;
if (isec->sh_size < 12) {
// The section is too short. This is a user error, but instead of
// being nitpicky about it, we simply handle it on a garbage-in,
// garbage-out basis.
return true;
}
// A .debug_info section contains compilation units (CUs). A 32-bit CU
// starts with a 32-bit size field, while a 64-bit CU starts with a
// magic number 0xffff'ffff followed by a 64-bit size field.
//
// Note that size doesn't take the size field itself into account, so
// the actual size of a 64-bit CU including the size field is 12 bytes
// larger than the value in the size field.
u8 buf[12];
isec->copy_contents_to(ctx, buf, 12);
if (*(U32<E> *)buf != 0xffff'ffff)
return true;
if (*(U64<E> *)(buf + 4) + 12 == isec->sh_size)
return false;
// An input .debug_info section usually contains a single CU. However,
// if the linker combines multiple object files using `-r`, the
// resulting object file may have a .debug_info section with as many CUs
// as there are in the input files. Therefore, if the first CU doesn't
// cover the entire .debug_info section, we need to keep reading until
// the end of the section.
//
// An input .debug_info section may be compressed using zlib or zstd, so
// we need to uncompress it before accessing `isec->contents`.
isec->uncompress(ctx);
u8 *p = (u8 *)isec->contents.data() + *(U64<E> *)(buf + 4) + 12;
u8 *end = (u8 *)isec->contents.data() + isec->sh_size;
while (end - p >= 12) {
if (*(U32<E> *)p != 0xffff'ffff)
return true;
p += *(U64<E> *)(p + 4) + 12;
}
return false;
}
// Debug sections in an input object file refer to other debug sections in
// the same file using section offsets. The offsets are 64 bits in DWARF64
// and 32 bits in DWARF32.
//
// GCC and Clang emit DWARF32 debug info by default even for 64-bit code.
// That is, 64-bit values are used for addresses, and 32-bit values for
// references between debug sections. This makes sense for ordinary programs
// because it reduces the size of the debug info sections.
//
// You can change the format to DWARF64 by passing `-gdwarf64`. Therefore,
// the "right" approach to build an extremely large program in debug mode is
// to recompile everything with `-gdwarf64`. However, that’s often not
// feasiable for various reasons.
//
// If we don't do anything about it, a relocation overflow could occur if
// any output debug section exceeds 4 GiB in size, making it almost
// impossible for users to link an object file compiled without `-gdwarf64`
// to an extremely large program.
//
// This function works around the issue by sorting output debug section
// contents so that DWARF32 input sections are at the start of the output
// section followed by DWARF64 input sections. By doing this, we can avoid
// relocation overflow until the total size of DWARF32 input sections alone
// exceeds 4 GiB.
template <typename E>
void sort_debug_info_sections(Context<E> &ctx) {
Timer t(ctx, "sort_debug_info_sections");
// True if mold is running under ctest
bool is_in_test = false;
if (char *env = getenv("MOLD_DEBUG"); env && env[0])
is_in_test = true;
// Get lists of output debug sections that need sorting
std::vector<OutputSection<E> *> vec1;
std::vector<MergedSection<E> *> vec2;
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (!(osec->shdr.sh_flags & SHF_ALLOC) && osec->name.starts_with(".debug_"))
if (osec->shdr.sh_size >= UINT32_MAX || is_in_test)
vec1.push_back(osec);
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
if (!(osec->shdr.sh_flags & SHF_ALLOC) && osec->name.starts_with(".debug_"))
if (osec->shdr.sh_size >= UINT32_MAX || is_in_test)
vec2.push_back(osec.get());
if (vec1.empty() && vec2.empty())
return;
// Read each input file's .debug_info to record whether the file contains
// DWARF32 or DWARF64
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->is_dwarf32 = is_dwarf32(ctx, file->debug_info);
});
// Unless we have a mix of DWARF32 and DWARF64, it doesn't make sense to
// sort sections.
if (ranges::all_of(ctx.objs, &ObjectFile<E>::is_dwarf32) ||
ranges::none_of(ctx.objs, &ObjectFile<E>::is_dwarf32))
return;
// Reorder input sections in the output section so that DWARF32
// precededs DWARF64
tbb::parallel_for_each(vec1, [&](OutputSection<E> *osec) {
ranges::stable_partition(osec->members, [](InputSection<E> *isec) {
return isec->file.is_dwarf32;
});
osec->compute_section_size(ctx);
});
// Reorder strings in .debug_str and the like
tbb::parallel_for_each(vec2, [&](MergedSection<E> *osec) {
tbb::parallel_for_each(osec->members, [&](MergeableSection<E> *m) {
if (m->input_section->file.is_dwarf32)
for (SectionFragment<E> *frag : m->fragments)
frag->is_32bit = true;
});
});
tbb::parallel_for_each(vec2, [&](MergedSection<E> *osec) {
osec->compute_section_size(ctx);
});
}
// .ctors/.dtors serves the same purpose as .init_array/.fini_array,
// albeit with very subtly differences. Both contain pointers to
// initializer/finalizer functions. The runtime executes them one by one
@@ -1512,8 +1371,7 @@ void shuffle_sections(Context<E> &ctx) {
auto is_eligible = [](OutputSection<E> *osec) {
if (osec) {
std::string_view name = osec->name;
return (osec->shdr.sh_flags & SHF_ALLOC) &&
name != ".init" && name != ".fini" &&
return name != ".init" && name != ".fini" &&
name != ".ctors" && name != ".dtors" &&
name != ".init_array" && name != ".preinit_array" &&
name != ".fini_array";
@@ -1585,7 +1443,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);
chunk->to_osec()->create_range_extension_thunks(ctx, true);
tbb::parallel_for_each(tail, [&](Chunk<E> *chunk) {
chunk->compute_section_size(ctx);
@@ -1760,8 +1618,7 @@ void scan_relocations(Context<E> &ctx) {
ctx.got->add_tlsdesc_symbol(ctx, sym);
if (sym->flags & NEEDS_COPYREL) {
if (ctx.arg.z_relro && sym->file->is_dso &&
((SharedFile<E> *)sym->file)->is_readonly(sym))
if (ctx.arg.z_relro && ((SharedFile<E> *)sym->file)->is_readonly(sym))
ctx.copyrel_relro->add_symbol(ctx, sym);
else
ctx.copyrel->add_symbol(ctx, sym);
@@ -2006,8 +1863,8 @@ void apply_version_script(Context<E> &ctx) {
// Assign versions to symbols specified with `extern "C++"` or
// wildcard patterns first.
Glob matcher;
Glob cpp_matcher;
MultiGlob matcher;
MultiGlob cpp_matcher;
// The "local:" label has a special meaning in the version script.
// It can appear in any VERSION clause, and it hides matched symbols
@@ -2044,14 +1901,18 @@ void apply_version_script(Context<E> &ctx) {
continue;
std::string_view name = sym->name();
i64 match = matcher.find(name);
i64 match = -1;
if (std::optional<i64> idx = matcher.find(name))
match = std::max(match, *idx);
// Match non-mangled symbols against the C++ pattern as well.
// Weird, but required to match other linkers' behavior.
if (!cpp_matcher.empty()) {
if (std::optional<std::string_view> s = demangle_cpp(name))
name = *s;
match = std::max(match, cpp_matcher.find(name));
if (std::optional<i64> idx = cpp_matcher.find(name))
match = std::max(match, *idx);
}
if (match != -1)
@@ -2229,8 +2090,8 @@ void compute_import_export(Context<E> &ctx) {
// exported so that they are interposable. In other words, symbols
// that did not match will be bound locally within the output file,
// effectively turning them into protected symbols.
Glob matcher;
Glob cpp_matcher;
MultiGlob matcher;
MultiGlob cpp_matcher;
auto handle_match = [&](Symbol<E> *sym) {
if (ctx.arg.shared) {
@@ -2270,12 +2131,12 @@ void compute_import_export(Context<E> &ctx) {
std::string_view name = sym->name();
if (matcher.find(name) != -1) {
if (matcher.find(name)) {
handle_match(sym);
} else if (!cpp_matcher.empty()) {
if (std::optional<std::string_view> s = demangle_cpp(name))
name = *s;
if (cpp_matcher.find(name) != -1)
if (cpp_matcher.find(name))
handle_match(sym);
}
}
@@ -2553,15 +2414,6 @@ void sort_output_sections(Context<E> &ctx) {
sort_output_sections_by_order(ctx);
}
template <typename E>
static i64 get_tls_segment_alignment(Context<E> &ctx) {
i64 val = 1;
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->shdr.sh_flags & SHF_TLS)
val = std::max<i64>(val, chunk->shdr.sh_addralign);
return val;
}
// This function assigns virtual addresses to output sections. Assigning
// addresses is a bit tricky because we want to pack sections as tightly
// as possible while not violating the constraints imposed by the hardware
@@ -2606,12 +2458,8 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
std::vector<Chunk<E> *> &chunks = ctx.chunks;
u64 addr = ctx.arg.image_base;
auto is_tls = [](Chunk<E> *chunk) {
return chunk->shdr.sh_flags & SHF_TLS;
};
auto is_tbss = [](Chunk<E> *chunk) {
return (chunk->shdr.sh_flags & SHF_TLS) && (chunk->shdr.sh_type == SHT_NOBITS);
return (chunk->shdr.sh_type == SHT_NOBITS) && (chunk->shdr.sh_flags & SHF_TLS);
};
for (i64 i = 0; i < chunks.size(); i++) {
@@ -2668,12 +2516,6 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
}
}
// TLS sections are included only in PT_LOAD but also in PT_TLS.
// We align the first TLS section so that the PT_TLS segment starts
// at an address that meets the segment's alignment requirement.
if (is_tls(chunks[i]) && (i == 0 || !is_tls(chunks[i - 1])))
addr = align_to(addr, get_tls_segment_alignment(ctx));
// TLS BSS sections are laid out so that they overlap with the
// subsequent non-tbss sections. Overlapping is fine because a STT_TLS
// segment contains an initialization image for newly-created threads,
@@ -2924,7 +2766,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;
}
@@ -2959,10 +2801,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
};
auto stop = [](Symbol<E> *sym, auto &chunk, i64 bias = 0) {
auto stop = [](Symbol<E> *sym, auto &chunk) {
if (sym && chunk) {
sym->set_output_section(chunk);
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size + bias;
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size;
}
};
@@ -2999,9 +2841,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
// such executable lacks the .dynamic section and thus there's no way
// to find ifunc relocations other than these symbols.
if (ctx.reldyn && ctx.arg.static_ && !ctx.arg.pie) {
stop(ctx.__rel_iplt_start, ctx.reldyn,
-get_num_irelative_relocs(ctx) * sizeof(ElfRel<E>));
stop(ctx.__rel_iplt_start, ctx.reldyn);
stop(ctx.__rel_iplt_end, ctx.reldyn);
ctx.__rel_iplt_start->value -=
get_num_irelative_relocs(ctx) * sizeof(ElfRel<E>);
} else {
// If the symbols are not ncessary, we turn them to absolute
// symbols at address 0.
@@ -3103,13 +2946,17 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
// PPC64's _{save,rest}gpr{0,1}_{14,15,16,...,31} symbols
if constexpr (is_ppc64v2<E>)
for (i64 i = 0; i < ppc64_save_restore_insns.size(); i++)
if (std::string_view label = ppc64_save_restore_insns[i].first;
!label.empty())
if constexpr (is_ppc64v2<E>) {
i64 offset = 0;
for (std::pair<std::string_view, u32> p : ppc64_save_restore_insns) {
std::string_view label = p.first;
if (!label.empty())
if (Symbol<E> *sym = get_symbol(ctx, label);
sym->file == ctx.internal_obj)
start(sym, ctx.extra.save_restore, i * 4);
start(sym, ctx.extra.save_restore, offset);
offset += 4;
}
}
// __start_ and __stop_ symbols
for (Chunk<E> *chunk : sections) {
@@ -3157,22 +3004,25 @@ template <typename E>
void compress_debug_sections(Context<E> &ctx) {
Timer t(ctx, "compress_debug_sections");
// Since this pass is embarassingly parallel, we want to use all
// available cores.
ctx.global_limit.reset();
tbb::parallel_for((i64)0, (i64)ctx.chunks.size(), [&](i64 i) {
Chunk<E> &chunk = *ctx.chunks[i];
if (!(chunk.shdr.sh_flags & SHF_ALLOC) && chunk.shdr.sh_size &&
chunk.name.starts_with(".debug_")) {
Chunk<E> *comp = new CompressedSection<E>(ctx, chunk);
ctx.chunk_pool.emplace_back(comp);
ctx.chunks[i] = comp;
}
if ((chunk.shdr.sh_flags & SHF_ALLOC) || chunk.shdr.sh_size == 0 ||
!chunk.name.starts_with(".debug"))
return;
Chunk<E> *comp = new CompressedSection<E>(ctx, chunk);
ctx.chunk_pool.emplace_back(comp);
ctx.chunks[i] = comp;
});
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
if (ctx.shstrtab)
ctx.shstrtab->update_shdr(ctx);
if (ctx.ehdr)
ctx.ehdr->update_shdr(ctx);
if (ctx.shdr)
ctx.shdr->update_shdr(ctx);
}
// BLAKE3 is a cryptographic hash function just like SHA256.
@@ -3361,10 +3211,9 @@ void write_separate_debug_file(Context<E> &ctx) {
});
append(ctx.chunks, ctx.debug_chunks);
sort_debug_info_sections(ctx);
// Handle --compress-debug-info
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE)
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
compress_debug_sections(ctx);
// Write to the debug info file as if it were a regular output file.
@@ -3541,7 +3390,6 @@ template void create_reloc_sections(Context<E> &);
template void copy_chunks(Context<E> &);
template void construct_relr(Context<E> &);
template void sort_dynsyms(Context<E> &);
template void sort_debug_info_sections(Context<E> &);
template void create_output_symtab(Context<E> &);
template void apply_version_script(Context<E> &);
template void parse_symbol_version(Context<E> &);
+4
View File
@@ -122,6 +122,10 @@ i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
if (sym.is_absolute())
return INT64_MAX;
// Likewise, relocations against weak undefined symbols won't be relaxed.
if (sym.esym().is_undef_weak())
return INT64_MAX;
// Compute a distance between the relocated place and the symbol.
i64 S = sym.get_addr(ctx);
i64 A = rel.r_addend;
+1 -2
View File
@@ -1,4 +1,3 @@
#include "config.h"
#include "mold.h"
#include <csignal>
@@ -105,7 +104,7 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
setenv("LD_PRELOAD", dso_path.c_str(), 1);
setenv("MOLD_PATH", self.c_str(), 1);
// If ld, ld.lld or ld.gold is specified, run mold instead
// If ld, ld.lld or ld.gold is specified, run mold itself
if (std::string cmd = path_filename(argv[2]);
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
std::vector<char *> args;
+68 -117
View File
@@ -20,8 +20,8 @@
// we don't need to try too hard to reduce thunk size to the absolute
// minimum.
#if MOLD_ARM32LE || MOLD_ARM32BE || MOLD_ARM64LE || MOLD_ARM64BE || \
MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
#if MOLD_ARM32 || MOLD_ARM64LE || MOLD_ARM64BE || MOLD_PPC32 || \
MOLD_PPC64V1 || MOLD_PPC64V2
#include "mold.h"
@@ -45,67 +45,68 @@ static constexpr i64 max_thunk_size = 1024 * 1024;
static constexpr i64 thunk_align = 16;
template <typename E>
static bool
requires_thunk(Context<E> &ctx, InputSection<E> &isec, const ElfRel<E> &rel,
bool first_pass) {
if (!is_func_call_rel(rel))
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)
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 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;
return false;
// 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 true;
return false;
}
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are reachable.
if constexpr (is_arm32<E>)
if (bool is_thumb = sym.get_addr(ctx) & 1;
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
(rel.r_type == R_ARM_PLT32 && is_thumb) ||
(rel.r_type == R_ARM_THM_JUMP24 && !is_thumb))
return true;
// On PowerPC, all PLT calls go through range extension thunks.
if constexpr (is_ppc<E>)
if (sym.has_plt(ctx))
return true;
// PowerPC before Power9 lacks PC-relative load/store instructions.
// Functions compiled for Power9 or earlier assume that r2 points to
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
// We need a thunk to recompute r2 for interworking.
if constexpr (is_ppc64v2<E>)
if ((rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc()))
return true;
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 S = sym.get_addr(ctx, NO_OPD);
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return val < -branch_distance<E> || branch_distance<E> <= val;
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;
}
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
bool first_pass) {
std::span<InputSection<E> *> m = members;
if (m.empty())
return;
@@ -121,7 +122,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
// We manage progress using four offsets which increase monotonically.
// The locations they point to are always A <= B <= C <= D.
//
// Input sections between B and C are the current batch.
// Input sections between B and C are in the current batch.
//
// A is the input section with the smallest address than can reach
// from the current batch.
@@ -180,21 +181,33 @@ 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 (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);
}
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);
}
}
});
@@ -213,7 +226,6 @@ 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;
@@ -221,75 +233,15 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
this->shdr.sh_size = offset;
}
// create_range_extension_thunks() creates thunks with a pessimistic
// assumption that all out-of-section references are out of range.
// After computing output section addresses, we revisit all thunks to
// remove unneeded entries from them.
//
// We create more thunks than necessary and then eliminate some of
// them later, instead of just creating thunks at this stage. This is
// because we can safely shrink sections after assigning addresses to
// them without worrying about making existing references to thunks go
// out of range. On the other hand, if we insert thunks after
// assigning addresses to sections, references to thunks could become
// out of range due to the new extra gaps for thunks. Thus, the
// creation of thunks is a two-pass process.
template <>
void remove_redundant_thunks(Context<E> &ctx) {
Timer t(ctx, "remove_redundant_thunks");
set_osec_offsets(ctx);
// Gather output executable sections
std::vector<OutputSection<E> *> sections;
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_flags & SHF_EXECINSTR)
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;
});
osec->create_range_extension_thunks(ctx, false);
}
// When applying relocations, we want to know the address in a reachable
@@ -307,8 +259,7 @@ 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())
if (osec->shdr.sh_flags & SHF_EXECINSTR)
sections.push_back(osec);
sections.push_back(osec);
ranges::stable_sort(sections, {}, [](OutputSection<E> *x) {
return x->shdr.sh_addr;
+1 -1
View File
@@ -162,7 +162,7 @@ u64 get_tp_addr(const ElfPhdr<E> &phdr) {
// and %a0/%a1 on s390x) refers to past the end of the TLS block for
// historical reasons. TLVs are accessed with negative offsets from TP.
return align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
} else if constexpr (is_arm<E> || is_sh4<E>) {
} else if constexpr (is_arm<E> || is_sh4<E> || is_arc<E>) {
// On ARM and SH4, the runtime appends two words at the beginning
// of TLV template image when copying TLVs to the TLS block, so we need
// to offset it.
+8 -8
View File
@@ -30,8 +30,8 @@ endif()
if(MOLD_ENABLE_QEMU_TESTS)
list(APPEND QEMU_ARCHS
x86_64 i386 arm armeb aarch64 aarch64_be ppc ppc64 ppc64le sparc64
sh4 sh4eb s390x riscv64 riscv32 m68k loongarch64)
x86_64 i386 arm aarch64 aarch64_be ppc ppc64 ppc64le sparc64 sh4 sh4eb
s390x riscv64 riscv32 m68k loongarch64 arc)
LIST(APPEND TRIPLES
x86_64-linux-gnu
@@ -39,7 +39,6 @@ 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
@@ -50,7 +49,8 @@ if(MOLD_ENABLE_QEMU_TESTS)
sh4aeb-linux-gnu
riscv32-linux-gnu
m68k-linux-gnu
loongarch64-linux-gnu)
loongarch64-linux-gnu
arc-linux-gnu)
foreach(ARCH IN LISTS QEMU_ARCHS)
find_program(HAS_qemu-${ARCH} qemu-${ARCH})
@@ -122,10 +122,6 @@ 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()
@@ -189,3 +185,7 @@ endif()
if(${MACHINE} STREQUAL "loongarch64" OR (HAS_qemu-loongarch64 AND HAS_loongarch64-linux-gnu-gcc))
add_target(loongarch64 loongarch64-linux-gnu)
endif()
if(${MACHINE} STREQUAL "arc" OR (HAS_qemu-loongarch64 AND HAS_arc-linux-gnu-gcc))
add_target(arc arc-linux-gnu)
endif()
-8
View File
@@ -1,8 +0,0 @@
#!/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'
+23 -18
View File
@@ -1,28 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
int fn1() { return 42; }
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
int foo() {
return 0;
}
EOF
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc - -L$t -lfoo
int fn1();
int fn2() { return fn1(); }
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
__attribute__((weak)) int foo();
int bar() {
if (foo) return foo();
return 0;
}
EOF
cat <<EOF | $CC -o $t/libbaz.so -shared -fPIC -Wl,-soname,libbaz.so -xc - -L$t -lbar
int fn2();
int fn3() { return fn2(); }
cat <<EOF | $CC -xc -c -o $t/c.o -
int bar();
int main() {
return bar();
}
EOF
cat <<EOF | $CC -o $t/a.o -c -xc -
int fn3();
int main() { fn3(); }
EOF
$CC -B. -shared -o $t/libfoo.so $t/a.o
$CC -B. -shared -o $t/libbar.so $t/b.o
$CC -B. -o $t/exe $t/c.o -L$t -Wl,--as-needed -lfoo -lbar
$CC -B. -o $t/exe $t/a.o -L$t -Wl,--as-needed -lbaz -lbar
readelf -W --dynamic $t/exe > $t/log2
not grep libfoo $t/log2
not grep libbar $t/log2
grep libbaz $t/log2
readelf --dynamic $t/exe > $t/log
not grep libfoo.so $t/log
grep libbar.so $t/log
+2 -3
View File
@@ -7,7 +7,6 @@ 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 ;;
@@ -65,7 +64,7 @@ t=$TESTDIR/$testname
mkdir -p $t
case $MACHINE in
x86_64 | i686 | arm | armeb)
x86_64 | i686 | arm)
tlsdesc_opt=-mtls-dialect=gnu2 ;;
aarch64 | riscv* | loongarch*)
tlsdesc_opt=-mtls-dialect=desc ;;
@@ -102,7 +101,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
+1 -1
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -6,16 +6,16 @@ cat <<EOF > $t/a.h
#define B 99
EOF
cat <<EOF | $CC -o $t/b.o -c -xc - -I$t -g3
cat <<EOF | $GCC -o $t/b.o -c -xc - -I$t -g3
#include "a.h"
extern int z();
int main () { return z() - 122; }
EOF
cat <<EOF | $CC -o $t/c.o -c -xc - -I$t -g3
cat <<EOF | $GCC -o $t/c.o -c -xc - -I$t -g3
#include "a.h"
int z() { return A + B; }
EOF
$CC -B. -o $t/exe $t/b.o $t/c.o
$GCC -B. -o $t/exe $t/b.o $t/c.o
$OBJDUMP --dwarf=macro $t/exe | not grep 'DW_MACRO_import -.* 0x0$'
-11
View File
@@ -1,11 +0,0 @@
#!/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
View File
@@ -22,8 +22,8 @@ void bar();
int main() { bar(); }
EOF
$CC -B. -o $t/exe1 $t/c.o $t/b.so -Wl,--allow-shlib-undefined
$CC -B. -o $t/exe1 $t/c.o $t/b.so
readelf --dynamic $t/exe1 | grep 'Dependency audit library:..*/a.so'
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo -Wl,--allow-shlib-undefined
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo
readelf --dynamic $t/exe2 | grep 'Dependency audit library:..*foo:.*/a.so'
+1 -1
View File
@@ -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();
+4 -7
View File
@@ -6,11 +6,8 @@ cat <<EOF | $CC -c -o $t/a.o -x assembler -
_start:
EOF
./mold -o $t/exe1 $t/a.o
readelf -WS $t/exe1 | not grep -F .interp
./mold -o $t/exe $t/a.o
readelf --sections $t/exe | not grep -F .interp
./mold -o $t/exe2 $t/a.o --dynamic-linker=/foo/bar
readelf -WS $t/exe2 | grep -F .interp
./mold -o $t/exe3 $t/a.o --dynamic-linker=/foo/bar -static
readelf -WS $t/exe3 | not grep -F .interp
./mold -o $t/exe $t/a.o --dynamic-linker=/foo/bar
readelf --sections $t/exe | grep -F .interp
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
[[ $MACHINE = arm* ]] && skip
[ $MACHINE = arm ] && skip
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -
#include <cstdio>
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -o $t/a.o -xc -
cat <<EOF | $GCC -c -o $t/a.o -xc -
#include <stdio.h>
__attribute__((section(".gnu.warning.foo")))
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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: unable to handle this LTO object file"
grep "b.o: don't know how to handle this LTO object file"
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
seq 1 100000 | sed 's/.*/.globl foo&\n.section .foo&,"aw"\nfoo&:.word 0\n/g' |
$CC -c -xassembler -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o - -fPIC
extern int foo100000;
int bar() { return foo100000; }
EOF
not $CC -B. -shared -o $t/c.so $t/a.o $t/b.o |& grep -F 'too many output sections'
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
seq 1 100000 | sed 's/.*/.section .data.&,"aw"\n.word 0\n/g' |
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' |
$CC -c -xassembler -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o -
File renamed without changes.
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
void foo();
void bar() { foo(); }
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc - -L$t -lbar
void baz();
void foo() { baz(); }
EOF
cat <<EOF | $CC -c -o $t/a.o -c -xc -
int bar();
int main() { bar(); }
EOF
not $CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lfoo -lbar |&
grep 'libfoo.so: --no-allow-shlib-undefined: undefined symbol: baz'
-2
View File
@@ -1,8 +1,6 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
-2
View File
@@ -1,8 +1,6 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
-31
View File
@@ -1,31 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
void foo();
void bar() { foo(); }
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbaz.so -xc - -L$t -lbar
void bar();
void baz() { bar(); }
EOF
cat <<EOF | $CC -c -o $t/a.o -c -xc -
int baz();
int main() { baz(); }
EOF
mv $t/libfoo.so $t/libfoo.so.bak
echo | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
$CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lbar -lbaz
not $CC -B. -o $t/exe2 $t/a.o -Wl,--no-allow-shlib-undefined \
-L$t -lfoo -lbar -lbaz |& grep 'no-allow-shlib-undefined: undefined symbol: foo'
-31
View File
@@ -1,31 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
void f1() {}
EOF
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
void f1();
void f2() { f1(); }
EOF
cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
void f1();
void f3() { f1(); }
EOF
$CC -B. -o $t/libfoo.so -shared $t/a.o
$CC -B. -o $t/libbar.so -shared $t/b.o -L$t -lfoo
$CC -B. -o $t/libbaz.so -shared $t/c.o
cat <<EOF | $CC -o $t/d.o -c -xc - -fPIC
void f2();
void f3();
int main() { f2(); f3(); }
EOF
$CC -B. -o $t/exe $t/d.o -L$t -lbar -lbaz
+1 -1
View File
@@ -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 -
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -6,12 +6,12 @@ nm mold | grep '__tsan_init' && skip
test_cflags -g3 -gz || skip
cat <<EOF | $CC -c -o $t/a.o -xc - -g3 -gz
cat <<EOF | $GCC -c -o $t/a.o -xc - -g3 -gz
#include <stdio.h>
void hello() { printf("Hello world\n"); }
EOF
cat <<EOF | $CC -c -o $t/b.o -xc - -g3 -gz
cat <<EOF | $GCC -c -o $t/b.o -xc - -g3 -gz
void hello();
int main() { hello(); }
EOF
+1 -1
View File
@@ -4,7 +4,7 @@
# OneTBB isn't tsan-clean
nm mold | grep '__tsan_init' && skip
seq 1 80000 | sed 's/.*/.section .data.&,"aw"\n.word 0\n/g' |
seq 1 80000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' |
$CC -c -xassembler -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o -
+1 -1
View File
@@ -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.
-27
View File
@@ -1,27 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
nm mold | grep '__tsan_init' && skip
command -v flock >& /dev/null || skip
cat <<EOF > $t/a.c
int x = 1;
void foo() {}
EOF
$CC -o $t/a.o -c -g -gdwarf32 $t/a.c || skip
cat <<EOF > $t/b.c
int y = 1;
int main() {}
EOF
$CC -o $t/b.o -c -g -gdwarf64 $t/b.c
MOLD_DEBUG=1 $CC -B. -o $t/exe $t/a.o $t/b.o -g -Wl,--separate-debug-file
flock $t/exe.dbg true
readelf -p .debug_line_str $t/exe.dbg > $t/str
if grep -Fw a.c $t/str; then
grep -A10 -Fw a.c $t/str | grep -Fw b.c
fi
+3 -3
View File
@@ -17,7 +17,7 @@ $CC -c -o $t/a.o $t/a.c -g
$CC -B. -o $t/exe1 $t/a.o -Wl,--separate-debug-file
readelf -SW $t/exe1 | grep -F .gnu_debuglink
flock $t/exe1.dbg true
flock $t/exe1 true
gdb $t/exe1 -ex 'list main' -ex 'quit' | grep -F printf
@@ -25,7 +25,7 @@ $CC -c -o $t/a.o $t/a.c -g
$CC -B. -o $t/exe2 $t/a.o -Wl,--separate-debug-file,--no-build-id
readelf -SW $t/exe2 | grep -F .gnu_debuglink
flock $t/exe2.dbg true
flock $t/exe2 true
gdb $t/exe2 -ex 'list main' -ex 'quit' | grep -F printf
@@ -33,6 +33,6 @@ $CC -c -o $t/a.o $t/a.c -g
$CC -B. -o $t/exe3 $t/a.o -Wl,--separate-debug-file,--compress-debug-sections=zlib
readelf -SW $t/exe3 | grep -F .gnu_debuglink
flock $t/exe3.dbg true
flock $t/exe3 true
readelf -W --sections $t/exe3.dbg | grep '\.debug_info .*C'
gdb $t/exe3 -ex 'list main' -ex 'quit' | grep -F printf
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc - -g -gdwarf32 -Wl,--compress-debug-sections=zlib || skip
void foo() {}
EOF
cat <<EOF | $CC -o $t/b.o -c -xc - -g -gdwarf64 -Wl,--compress-debug-sections=zstd || skip
int main() {}
EOF
# Test if DWARF32 precedes DWARF64 in the output .debug_info
MOLD_DEBUG=1 $CC -B. -o $t/exe1 $t/a.o $t/b.o -g -Wl,-Map=$t/map1
grep -A10 -F '/a.o:(.debug_info)' $t/map1 | grep -F '/b.o:(.debug_info)'
MOLD_DEBUG=1 $CC -B. -o $t/exe2 $t/b.o $t/a.o -g -Wl,-Map=$t/map2
grep -A10 -F '/a.o:(.debug_info)' $t/map2 | grep -F '/b.o:(.debug_info)'
-24
View File
@@ -1,24 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
nm mold | grep '__tsan_init' && skip
cat <<EOF | $CC -o $t/a.o -c -xc - -g -gdwarf64 || skip
void foo() {}
EOF
cat <<EOF | $CC -o $t/b.o -c -xc - -g -gdwarf32
void bar() {}
EOF
./mold -r -o $t/c.o $t/a.o $t/b.o
cat <<EOF | $CC -o $t/d.o -c -xc - -g -gdwarf64
int main() {}
EOF
MOLD_DEBUG=1 $CC -B. -o $t/exe1 $t/c.o $t/d.o -g -Wl,-Map=$t/map1
grep -A10 -F '/c.o:(.debug_info)' $t/map1 | grep -F '/d.o:(.debug_info)'
MOLD_DEBUG=1 $CC -B. -o $t/exe2 $t/d.o $t/c.o -g -Wl,-Map=$t/map2
grep -A10 -F '/c.o:(.debug_info)' $t/map2 | grep -F '/d.o:(.debug_info)'
-41
View File
@@ -1,41 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF > $t/a.c
int x = 1;
void foo() {}
EOF
$CC -o $t/a.o -c -g -gdwarf32 $t/a.c || skip
cat <<EOF > $t/b.c
int y = 1;
int main() {}
EOF
$CC -o $t/b.o -c -g -gdwarf64 $t/b.c
# Test if DWARF32 precedes DWARF64 in the output .debug_info
MOLD_DEBUG=1 $CC -B. -o $t/exe1 $t/a.o $t/b.o -g -Wl,-Map=$t/map1
grep -A10 -F '/a.o:(.debug_info)' $t/map1 | grep -F '/b.o:(.debug_info)'
readelf -p .debug_line_str $t/exe1 > $t/str1
if grep -Fw a.c $t/str1; then
grep -A10 -Fw a.c $t/str1 | grep -Fw b.c
fi
MOLD_DEBUG=1 $CC -B. -o $t/exe2 $t/b.o $t/a.o -g -Wl,-Map=$t/map2
grep -A10 -F '/a.o:(.debug_info)' $t/map2 | grep -F '/b.o:(.debug_info)'
readelf -p .debug_line_str $t/exe2 > $t/str2
if grep -Fw a.c $t/str2; then
grep -A10 -Fw a.c $t/str2 | grep -Fw b.c
fi
if [ -z "$QEMU" ] && command -v gdb; then
gdb $t/exe1 -ex 'list main' -ex 'quit' | grep 'int y = 1'
gdb $t/exe2 -ex 'list main' -ex 'quit' | grep 'int y = 1'
fi
-26
View File
@@ -1,26 +0,0 @@
#!/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
View File
@@ -1,7 +1,6 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = armeb ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -o $t/a.o -c -xc -
-1
View File
@@ -1,7 +1,6 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = armeb ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -c -o $t/a.o -xc -
-1
View File
@@ -1,7 +1,6 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = armeb ] && skip
[[ $MACHINE = loongarch* ]] && skip
mkdir -p $t/bin $t/sysroot/foo
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xassembler - || skip
cat <<EOF | $GCC -o $t/a.o -c -xassembler -
.globl foo
.tls_common foo,4,4
EOF
+2 -2
View File
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc -
static _Thread_local int foo[10000] = { 3, [9999] = 5 };
int get_foo(int idx) { return foo[idx]; }
@@ -9,7 +9,7 @@ EOF
$CC -B. -shared -o $t/b.so $t/a.o
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
cat <<EOF | $GCC -fPIC -c -o $t/c.o -xc -
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
Loaded 100 of 391 files, more files were not shown because too many files have changed in this diff. Show more