mirror of
https://github.com/rui314/mold.git
synced 2026-11-12 14:59:43 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d9b4d5c05 | ||
|
|
d06bfe0ac3 | ||
|
|
6540294a81 | ||
|
|
02a7640047 | ||
|
|
f4c5a8a42e | ||
|
|
0fdbace9b2 | ||
|
|
cab6014154 | ||
|
|
000ce0ee52 | ||
|
|
d720a2011c | ||
|
|
c91055271b | ||
|
|
1478acd551 | ||
|
|
b3404c07d7 | ||
|
|
94767dc0e7 | ||
|
|
f957acfe11 | ||
|
|
7fc43ccc3a | ||
|
|
dc5b9508d9 | ||
|
|
ef056d8ed1 | ||
|
|
6a63671ef5 | ||
|
|
d3867766af | ||
|
|
1ae4b9571b | ||
|
|
3fd1cec20e | ||
|
|
28b16eea19 | ||
|
|
37f32a9008 | ||
|
|
8415252322 | ||
|
|
b3984c9580 | ||
|
|
6616995804 | ||
|
|
de7d37e1fc | ||
|
|
3cb8a528a5 | ||
|
|
8bb01e1a0e | ||
|
|
0416d7ed80 | ||
|
|
c02c04f08a | ||
|
|
efb952ef12 | ||
|
|
04b145d795 | ||
|
|
1830fa1d84 | ||
|
|
a2b300b863 | ||
|
|
2d8297a562 | ||
|
|
deb7728b46 | ||
|
|
bbd6d86af4 | ||
|
|
b6361b8cad | ||
|
|
04fdae124f | ||
|
|
29df7093a8 | ||
|
|
18212a21ab | ||
|
|
e13d28bfe6 | ||
|
|
667204ac7c | ||
|
|
76ea83e791 | ||
|
|
55daf7385d | ||
|
|
0e66e4b7e9 | ||
|
|
fe485db9b4 | ||
|
|
b3dc4879fd | ||
|
|
25ce3738b9 | ||
|
|
22b8ac8470 | ||
|
|
6a4e128ef8 | ||
|
|
66d68c8a98 | ||
|
|
76867af090 | ||
|
|
5d02248a0b | ||
|
|
eb6c213f2a | ||
|
|
efdac9afdd | ||
|
|
c6bfff2a04 | ||
|
|
49066ea329 | ||
|
|
25ceab07f7 | ||
|
|
6e97393740 | ||
|
|
ff3d54d26f | ||
|
|
25c9cf3976 | ||
|
|
5f2fb08cfd | ||
|
|
60bc56848f | ||
|
|
600ca381fd | ||
|
|
da3f5dd4ec | ||
|
|
b59911b0c1 | ||
|
|
8131528305 | ||
|
|
6a71e7dd4b | ||
|
|
a63b2f70b2 | ||
|
|
3f88964527 | ||
|
|
e18dc70af0 | ||
|
|
0c6daad215 | ||
|
|
8c35c80bb7 | ||
|
|
d42ba3bae4 | ||
|
|
8c337b25df | ||
|
|
8d1c08bd03 | ||
|
|
5599a6f24f | ||
|
|
ef9a610dd1 | ||
|
|
31c3b5397b | ||
|
|
e11d64b5c7 | ||
|
|
9d91a2bc23 | ||
|
|
8c41f21d3c | ||
|
|
5c7a75b3e4 | ||
|
|
0804fbd095 | ||
|
|
0f78fa9c43 | ||
|
|
d30e970eb8 | ||
|
|
edc2f852a1 | ||
|
|
348e44a9c4 | ||
|
|
42f663cac0 | ||
|
|
4dd5d2f8cb | ||
|
|
4cdfc7e72c | ||
|
|
9516da1751 | ||
|
|
f3de82b2a2 | ||
|
|
c4c506c1cb | ||
|
|
0c62a91ded | ||
|
|
2fd0a4c60a | ||
|
|
d3188e39df | ||
|
|
3229b5b7e3 | ||
|
|
d9ede96aca | ||
|
|
4b4219fc33 | ||
|
|
02ead29989 | ||
|
|
37919009ab | ||
|
|
782ff4f582 | ||
|
|
3c468c71a4 | ||
|
|
a31679cf04 | ||
|
|
9cd65ed152 | ||
|
|
79ad405319 | ||
|
|
b60a164fcc | ||
|
|
2d4ba3ac74 | ||
|
|
42eabf9f71 | ||
|
|
aa4b3f7d01 | ||
|
|
2d121b8d6d | ||
|
|
c86a59a511 | ||
|
|
f7dea3a21b | ||
|
|
651ae8a2a8 | ||
|
|
5f7aca6276 | ||
|
|
39cdf61695 | ||
|
|
5c4955763b | ||
|
|
dae070283b | ||
|
|
403655904b | ||
|
|
44942b638b | ||
|
|
ceffd4679b | ||
|
|
6fa742be67 | ||
|
|
484e357971 | ||
|
|
53a5a37370 | ||
|
|
41579888b6 | ||
|
|
1e2d84038e | ||
|
|
6c7700b051 | ||
|
|
34c53777fb | ||
|
|
7be1b66791 | ||
|
|
4bd80ec21c | ||
|
|
a9ab0ff452 | ||
|
|
206bf65c50 | ||
|
|
2b3e8eec05 | ||
|
|
9eac818254 | ||
|
|
6fe0cadfa1 | ||
|
|
1a2baee096 | ||
|
|
728b265cbd | ||
|
|
9fa61b9298 | ||
|
|
6ad942cb27 | ||
|
|
eed4496af2 | ||
|
|
17c292c2b4 | ||
|
|
ab1ab78f8a | ||
|
|
8506065fd2 | ||
|
|
a291d0d448 | ||
|
|
690fefb97d | ||
|
|
c7c1779ad7 | ||
|
|
ec095e7513 | ||
|
|
69a91e8aa0 | ||
|
|
af46000645 | ||
|
|
b1658c4702 | ||
|
|
9f85267b44 | ||
|
|
f8d20cd3ce | ||
|
|
a396fa400f | ||
|
|
d8a4f14fd0 | ||
|
|
c8b5fae2d0 | ||
|
|
0ce32d36d5 | ||
|
|
01cd5d1a32 | ||
|
|
39a25e2394 | ||
|
|
bfdd75ed38 | ||
|
|
5411f4216e | ||
|
|
3cf00e30d5 | ||
|
|
0f714710f0 | ||
|
|
83ec409426 | ||
|
|
e6ae83fbf9 | ||
|
|
00702773f4 | ||
|
|
1739cfbfb4 | ||
|
|
ff689a1253 | ||
|
|
ae427d133e | ||
|
|
d92af2456d | ||
|
|
b3e1092879 | ||
|
|
46ef71eb75 | ||
|
|
871ef8cf14 | ||
|
|
c1b1caa854 | ||
|
|
dcab76697e | ||
|
|
cf11efb147 | ||
|
|
a688b73f8e | ||
|
|
9bb50ae7a5 | ||
|
|
ead6235470 | ||
|
|
967006479f | ||
|
|
f843daee9b | ||
|
|
c7129916ee | ||
|
|
e234ce25b6 | ||
|
|
02e1b9de10 | ||
|
|
effde5ffa9 | ||
|
|
1d3706285e | ||
|
|
085a1148fd | ||
|
|
5cf3e53ab9 | ||
|
|
a5aa387cb4 | ||
|
|
f99cb33c2d | ||
|
|
e55703405b | ||
|
|
4c71174d5d | ||
|
|
2ea08ac4b3 | ||
|
|
82ace062db | ||
|
|
17f0d85efb | ||
|
|
a994d71fa1 | ||
|
|
0eea8f4015 | ||
|
|
2e3b56ee3f | ||
|
|
b8bcb8a2a3 | ||
|
|
ac3ee91cbd | ||
|
|
18df821625 |
No files matched your search
@@ -0,0 +1,45 @@
|
||||
name: Build all tarballs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
build-tarballs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ x86_64, aarch64, arm, riscv64, ppc64le, s390x ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tarballs
|
||||
path: mold-*.tar.gz
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Build x86 tarball
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build-tarball:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tarball
|
||||
path: mold-*.tar.gz
|
||||
+18
-21
@@ -18,12 +18,12 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: rui314/setup-mold@staging
|
||||
- uses: rui314/setup-mold@v1
|
||||
- name: install-build-deps
|
||||
run: sudo ./install-build-deps.sh update
|
||||
run: sudo ./install-build-deps.sh
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
sudo apt-get install -y clang++-12
|
||||
@@ -31,7 +31,10 @@ jobs:
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++-12 ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: test
|
||||
run: |
|
||||
cd build
|
||||
ctest -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
@@ -47,16 +50,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: install-build-deps
|
||||
shell: bash
|
||||
run: |
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump xz-utils {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
|
||||
|
||||
for i in {i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu arm-linux-gnueabihf; do
|
||||
ln -sf /usr/bin/$i-gcc-10 /usr/bin/$i-gcc
|
||||
ln -sf /usr/bin/$i-g++-10 /usr/bin/$i-g++
|
||||
done
|
||||
./install-build-deps.sh
|
||||
./install-cross-tools.sh
|
||||
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
@@ -89,26 +87,25 @@ jobs:
|
||||
|
||||
wget -O/usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2023.08.08/qemu-loongarch64
|
||||
chmod 755 /usr/local/bin/qemu-loongarch64
|
||||
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On -DMOLD_ENABLE_QEMU_TESTS_LOONGARCH=On ..
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: test
|
||||
run: |
|
||||
cd build
|
||||
ctest -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-gcc
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
path: b/out b/Testing
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-11
|
||||
@@ -122,7 +119,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
name: linux-packages
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: build
|
||||
run: ./dist.sh x86_64
|
||||
- name: upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: mold-*-linux.tar.gz
|
||||
@@ -0,0 +1,32 @@
|
||||
# This file is generated by ChatGPT
|
||||
|
||||
name: Update manpage
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/mold.md'
|
||||
|
||||
jobs:
|
||||
update-manpage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install ronn
|
||||
run: sudo apt-get install -y ronn
|
||||
|
||||
- name: Generate mold.1 from mold.md
|
||||
run: ronn --roff docs/mold.md
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name 'Rui Ueyama'
|
||||
git config --global user.email '[email protected]'
|
||||
|
||||
- name: Commit and push if mold.1 is updated
|
||||
run: |
|
||||
git add docs/mold.1
|
||||
git diff --staged --quiet || (git commit -m "Update mold.1 (automated commit)" && git push)
|
||||
@@ -3,15 +3,7 @@
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
+9
-8
@@ -44,7 +44,7 @@
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.2.0)
|
||||
project(mold VERSION 2.4.0)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
@@ -64,9 +64,12 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function
|
||||
-Wunused-variable
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
@@ -157,12 +160,10 @@ endif()
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles(
|
||||
"static_assert(sizeof(void *) == 4); int main() {}" P32)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD" OFF)
|
||||
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT P32" OFF)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
@@ -301,8 +302,8 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/arch-loongarch.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/cmdline.cc
|
||||
elf/dwarf.cc
|
||||
elf/gc-sections.cc
|
||||
elf/gdb-index.cc
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
|
||||
@@ -71,7 +71,7 @@ necessary packages. You may need to run it as root.
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v2.2.0
|
||||
git checkout v2.4.0
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
@@ -255,13 +255,14 @@ following people and organizations who have sponsored $128/month or more:
|
||||
|
||||
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
|
||||
|
||||
- [Uber](https://uber.com)
|
||||
- [G-Research](https://www.gresearch.co.uk)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
- [GlareDB](https://github.com/GlareDB)
|
||||
|
||||
### Individual sponsors
|
||||
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Johan Andersson](https://github.com/repi)
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
- [kyle-elliott](https://github.com/kyle-elliott)
|
||||
- [Bryant Biggs](https://github.com/bryantbiggs)
|
||||
- [kraptor23](https://github.com/kraptor23)
|
||||
- [Jinkyu Yi](https://github.com/jincreator)
|
||||
@@ -1,17 +0,0 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ docker buildx create --use
|
||||
# $ docker buildx build --platform x86_64,aarch64,arm,ppc64le,s390x -t rui314/mold-builder:latest --push .
|
||||
|
||||
FROM ubuntu:18.04
|
||||
ENV TZ=Europe/London
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
\
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
|
||||
rm -rf /var/lib/apt/lists/* /cmake
|
||||
+57
-46
@@ -6,58 +6,69 @@ namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(Context &ctx, std::string_view path) {
|
||||
read_response_file(Context &ctx, std::string_view path, i64 depth) {
|
||||
if (depth > 10)
|
||||
Fatal(ctx) << path << ": response file nesting too deep";
|
||||
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mf->size && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mf->size)
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
|
||||
while (i < mf->size) {
|
||||
if (data[i] == '\\' && i + 1 < mf->size) {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[i])) {
|
||||
buf.append(1, data[i++]);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
while (!data.empty()) {
|
||||
if (isspace(data[0])) {
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
auto read_quoted = [&]() {
|
||||
char quote = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
for (i64 i = 0; i < mf->size;) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
std::string buf;
|
||||
while (!data.empty() && data[0] != quote) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
} else {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
}
|
||||
}
|
||||
if (data.empty())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
data = data.substr(1);
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
auto read_unquoted = [&] {
|
||||
std::string buf;
|
||||
while (!data.empty()) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[0])) {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
std::string_view tok;
|
||||
if (data[0] == '\'' || data[0] == '\"')
|
||||
tok = read_quoted();
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
tok = read_unquoted();
|
||||
|
||||
if (tok.starts_with('@'))
|
||||
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
|
||||
else
|
||||
vec.push_back(tok);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -68,7 +79,7 @@ std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
append(vec, read_response_file(ctx, argv[i] + 1, 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
|
||||
+192
-103
@@ -23,6 +23,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -225,8 +226,14 @@ struct Atomic : std::atomic<T> {
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val) { std::atomic<T>::store(val, relaxed); }
|
||||
T load() const { return std::atomic<T>::load(relaxed); }
|
||||
void store(T val, std::memory_order order = relaxed) {
|
||||
std::atomic<T>::store(val, order);
|
||||
}
|
||||
|
||||
T load(std::memory_order order = relaxed) const {
|
||||
return std::atomic<T>::load(order);
|
||||
}
|
||||
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
@@ -243,6 +250,85 @@ struct Atomic : std::atomic<T> {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// Bit vector
|
||||
//
|
||||
@@ -479,33 +565,29 @@ public:
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
free(entries);
|
||||
free(entries_buf);
|
||||
}
|
||||
|
||||
// In order to avoid unnecessary cache-line false sharing, we want
|
||||
// to make this object to be aligned to a reasonably large
|
||||
// power-of-two address.
|
||||
struct alignas(32) Entry {
|
||||
std::atomic<const char *> key;
|
||||
Atomic<const char *> key;
|
||||
T value;
|
||||
u32 keylen;
|
||||
};
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
free(entries_buf);
|
||||
|
||||
i64 sz = sizeof(Entry) * this->nbuckets;
|
||||
free(entries);
|
||||
|
||||
#if _WIN32
|
||||
// Even though std::aligned_alloc is defined in C++17, MSVC doesn't
|
||||
// seem to provide that function.
|
||||
entries = (Entry *)_aligned_malloc(sz, alignof(Entry));
|
||||
#else
|
||||
entries = (Entry *)std::aligned_alloc(alignof(Entry), sz);
|
||||
#endif
|
||||
|
||||
memset(entries, 0, sz);
|
||||
// seem to provide that function. C11's aligned_alloc may not be always
|
||||
// avialalbe. Therefore, we'll align the buffer ourselves.
|
||||
i64 size = sizeof(Entry) * this->nbuckets;
|
||||
entries_buf = malloc(size + alignof(Entry) - 1);
|
||||
entries = (Entry *)align_to((u64)entries_buf, alignof(Entry));
|
||||
memset(entries, 0, size);
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
@@ -554,19 +636,66 @@ public:
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
const char *get_key(i64 idx) {
|
||||
return entries[idx].key.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
i64 get_idx(T *value) const {
|
||||
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
|
||||
return (Entry *)addr - entries;
|
||||
}
|
||||
|
||||
// Return a list of map entries sorted in a deterministic order.
|
||||
std::vector<Entry *> get_sorted_entries(i64 shard_idx) {
|
||||
if (nbuckets == 0)
|
||||
return {};
|
||||
|
||||
i64 shard_size = nbuckets / NUM_SHARDS;
|
||||
i64 begin = shard_idx * shard_size;
|
||||
i64 end = begin + shard_size;
|
||||
|
||||
i64 sz = 0;
|
||||
for (i64 i = begin; i < end; i++)
|
||||
if (entries[i].key)
|
||||
sz++;
|
||||
|
||||
std::vector<Entry *> vec;
|
||||
vec.reserve(sz);
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while (entries[end - 1].key)
|
||||
vec.push_back(entries + --end);
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
i64 last = 0;
|
||||
for (i64 i = begin; i < end;) {
|
||||
while (i < end && entries[i].key)
|
||||
vec.push_back(entries + i++);
|
||||
|
||||
std::sort(vec.begin() + last, vec.end(), [](Entry *a, Entry *b) {
|
||||
if (a->keylen != b->keylen)
|
||||
return a->keylen < b->keylen;
|
||||
return memcmp(a->key, b->key, a->keylen) < 0;
|
||||
});
|
||||
|
||||
last = vec.size();
|
||||
|
||||
while (i < end && !entries[i].key)
|
||||
i++;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<Entry *> get_sorted_entries_all() {
|
||||
std::vector<std::vector<Entry *>> vec(NUM_SHARDS);
|
||||
tbb::parallel_for((i64)0, NUM_SHARDS, [&](i64 i) {
|
||||
vec[i] = get_sorted_entries(i);
|
||||
});
|
||||
return flatten(vec);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 2048;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 128;
|
||||
|
||||
void *entries_buf = nullptr;
|
||||
Entry *entries = nullptr;
|
||||
i64 nbuckets = 0;
|
||||
|
||||
@@ -594,9 +723,11 @@ public:
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::vector<u8> buf2;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
i64 fd = -1;
|
||||
i64 filesize = 0;
|
||||
bool is_mmapped = false;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
@@ -604,6 +735,43 @@ protected:
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
~MallocOutputFile() {
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
FILE *fp;
|
||||
|
||||
if (this->path == "-") {
|
||||
fp = stdout;
|
||||
} else {
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
fp = fdopen(fd, "w");
|
||||
}
|
||||
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
if (!this->buf2.empty())
|
||||
fwrite(this->buf2.data(), this->buf2.size(), 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
@@ -739,85 +907,6 @@ private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
@@ -905,9 +994,9 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
#else
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
|
||||
if (fd == -1) {
|
||||
@@ -943,7 +1032,7 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
|
||||
+5
-6
@@ -11,16 +11,15 @@
|
||||
namespace mold {
|
||||
|
||||
std::string_view demangle(std::string_view name) {
|
||||
static thread_local char *p;
|
||||
if (p)
|
||||
free(p);
|
||||
static thread_local char *buf;
|
||||
free(buf);
|
||||
|
||||
// Try to demangle as a Rust symbol. Since legacy-style Rust symbols
|
||||
// are also valid as a C++ mangled name, we need to call this before
|
||||
// cpp_demangle.
|
||||
p = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (p)
|
||||
return p;
|
||||
buf = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (buf)
|
||||
return buf;
|
||||
|
||||
// Try to demangle as a C++ symbol.
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
|
||||
+12
-37
@@ -52,14 +52,13 @@ class MemoryMappedOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, true) {
|
||||
i64 fd;
|
||||
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
|
||||
std::tie(this->fd, output_tmpfile) =
|
||||
open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
MAP_SHARED, this->fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
@@ -76,6 +75,15 @@ public:
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (this->buf2.empty()) {
|
||||
::close(this->fd);
|
||||
} else {
|
||||
FILE *out = fdopen(this->fd, "w");
|
||||
fseek(out, 0, SEEK_END);
|
||||
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// If an output file already exists, open a file and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system to immediately release disk blocks occupied by the file.
|
||||
@@ -92,39 +100,6 @@ private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
@@ -6,39 +6,6 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
@@ -1,40 +1,169 @@
|
||||
#!/bin/bash
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically-linked to
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
# This script creates a mold binary distribution. The output is written in
|
||||
# this directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
|
||||
# `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
|
||||
# these libraries almost always exist on any Linux system. We can't
|
||||
# statically link libc because doing so would disable dlopen(), which is
|
||||
# necessary to open the LTO linker plugin.
|
||||
#
|
||||
# This script aims to produce reproducible outputs. That means if you run
|
||||
# the script twice on the same git commit, it should produce bit-by-bit
|
||||
# identical binary files. This property is crucial as a countermeasure
|
||||
# against supply chain attacks. With this, you can verify that the binary
|
||||
# files distributed on the GitHub release pages are created from the
|
||||
# commit with release tags by rebuilding the binaries yourself.
|
||||
#
|
||||
# Debian provides snapshot.debian.org to host all historical binary
|
||||
# packages. We use it to construct Docker images pinned to a
|
||||
# particular timestamp.
|
||||
#
|
||||
# We aim to use a reasonably old Debian version because we'll dynamically
|
||||
# link glibc to mold, and a binary linked against a newer version of glibc
|
||||
# won't work on a system with an older version of glibc.
|
||||
#
|
||||
# We need GCC 10 or newer to build mold. If GCC 10 is not available on an
|
||||
# old Debian version, we'll build it ourselves.
|
||||
#
|
||||
# You may need to run the following command to use Docker with Qemu:
|
||||
#
|
||||
# $ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
set -e -x
|
||||
cd "$(dirname $0)"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
[[ $arch = arm* ]] && arch=arm
|
||||
if [ $arch = arm64 ]; then
|
||||
arch=aarch64
|
||||
elif [[ $arch = arm* ]]; then
|
||||
arch=arm
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
exit 1
|
||||
usage
|
||||
esac
|
||||
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || usage
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
set -e -x
|
||||
|
||||
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
|
||||
bash -c "mkdir /tmp/build &&
|
||||
cd /tmp/build &&
|
||||
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
|
||||
cmake --build . -j\$(nproc) &&
|
||||
[ $arch = arm ] || ctest -j\$(nproc) &&
|
||||
cmake --install . --prefix $dest --strip &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold mold-wrapper.so /mold &&
|
||||
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-builder-$arch
|
||||
docker_build="docker build --platform linux/$arch -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created Docker image in GitHub's Docker repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
|
||||
docker_build="docker buildx build --platform linux/$arch -t $image --push --cache-to type=inline --cache-from type=registry,ref=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch -"
|
||||
fi
|
||||
|
||||
# Create a Docker image.
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 8 (Jessie) released in April 2015
|
||||
cat <<EOF | $docker_build
|
||||
FROM debian:jessie-20210326@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends wget bzip2 file make autoconf gcc g++ libssl-dev && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
# Build CMake 3.27
|
||||
RUN mkdir -p /build/cmake && \
|
||||
cd /build/cmake && \
|
||||
wget -O- --no-check-certificate https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
./bootstrap --parallel=\$(nproc) && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GCC 10
|
||||
RUN mkdir -p /build/gcc && \
|
||||
cd /build/gcc && \
|
||||
wget -O- http://ftp.gnu.org/gnu/gcc/gcc-10.5.0/gcc-10.5.0.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir isl gmp mpc mpfr && \
|
||||
wget -O- http://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2 | tar xjf - --strip-components=1 -C isl && \
|
||||
wget -O- http://ftp.gnu.org/gnu/gmp/gmp-6.1.2.tar.bz2 | tar xjf - --strip-components=1 -C gmp && \
|
||||
wget -O- http://ftp.gnu.org/gnu/mpc/mpc-1.2.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
|
||||
wget -O- http://ftp.gnu.org/gnu/mpfr/mpfr-4.1.0.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
|
||||
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
|
||||
rm -rf /build
|
||||
EOF
|
||||
;;
|
||||
aarch64 | arm | ppc64le | s390x)
|
||||
# Debian 10 (Bullseye) released in July 2019
|
||||
#
|
||||
# We don't want to build GCC for these targets with Qemu becuase
|
||||
# that'd take extremely long time. Also I believe old build machines
|
||||
# are usually x86-64.
|
||||
[ $arch = aarch64 ] && digest=d5ed76c5265576982e6599b6f12392290d9b52b315b19b28b640aaba6e8af002
|
||||
[ $arch = arm ] && digest=bede2623dae269454c5b6dd4af15a10810a5f4ef75963d4eb6531628f98bd633
|
||||
[ $arch = ppc64le ] && digest=255f385e735469493b3465befad59a16f9d46f41d0b50e4fa6d5928c5ee7702a
|
||||
[ $arch = s390x ] && digest=96fb9ce5d3ce7f3dab7c34c18edfee093904cbc7fc19162dbcca22b2cc273b9d
|
||||
|
||||
cat <<EOF | $docker_build
|
||||
FROM debian:bullseye-20231030@sha256:$digest
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
|
||||
ln -sf /usr/bin/gcc-10 /usr/bin/cc && \
|
||||
ln -sf /usr/bin/g++-10 /usr/bin/c++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
riscv64)
|
||||
# snapshot.debian.org is not available for RISC-V binaries
|
||||
cat <<EOF | $docker_build
|
||||
FROM riscv64/debian:unstable-20231030@sha256:be1882409392c1f68f23e1e04bd965c49398f4a358b1a15f7b1820c0b39ede5b
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-12 g++-12 cmake && \
|
||||
ln -sf /usr/bin/gcc-12 /usr/bin/cc && \
|
||||
ln -sf /usr/bin/g++-12 /usr/bin/c++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
# Source tarballs available on GitHub don't contain .git history.
|
||||
# Clone the repo if missing.
|
||||
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp="$(git log -1 --format=%ci)"
|
||||
|
||||
# Build mold in a container.
|
||||
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" $image bash -c "
|
||||
set -e
|
||||
mkdir -p /build/mold
|
||||
cd /build/mold
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=On /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date='$timestamp'
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /mold/$dest.tar.gz
|
||||
chown $(id -u):$(id -g) /mold/$dest.tar.gz
|
||||
"
|
||||
+11
-5
@@ -1,6 +1,6 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "MOLD" "1" "July 2023" ""
|
||||
.TH "MOLD" "1" "November 2023" ""
|
||||
.SH "NAME"
|
||||
\fBmold\fR \- a modern linker
|
||||
.SH "SYNOPSIS"
|
||||
@@ -179,6 +179,9 @@ Omit \fB\.symtab\fR section from the output file\.
|
||||
\fB\-u\fR \fIsymbol\fR, \fB\-\-undefined\fR=\fIsymbol\fR
|
||||
If \fIsymbol\fR remains as an undefined symbol after reading all object files, and if there is a static archive that contains an object file defining \fIsymbol\fR, pull out the object file and link it so that the output file contains a definition of \fIsymbol\fR\.
|
||||
.TP
|
||||
\fB\-y\fR \fIsymbol\fR, \fB\-\-trace\-symbol\fR=\fIsymbol\fR
|
||||
Trace references to \fIsymbol\fR\.
|
||||
.TP
|
||||
\fB\-\-Bdynamic\fR
|
||||
Link against shared libraries\.
|
||||
.TP
|
||||
@@ -354,6 +357,9 @@ Create a share library\.
|
||||
\fB\-\-spare\-dynamic\-tags\fR=\fInumber\fR
|
||||
Reserve the given \fInumber\fR of tags in \fB\.dynamic\fR section\.
|
||||
.TP
|
||||
\fB\-\-spare\-program\-headers\fR=\fInumber\fR
|
||||
Reserve the given \fInumber\fR of entries in the program header\.
|
||||
.TP
|
||||
\fB\-\-start\-lib\fR, \fB\-\-end\-lib\fR
|
||||
Handle object files between \fB\-\-start\-lib\fR and \fB\-\-end\-lib\fR as if they were in an archive file\. That means object files between them are linked only when they are needed to resolve undefined symbols\. The options are useful if you want to link object files only when they are needed but want to avoid the overhead of running ar(3)\.
|
||||
.TP
|
||||
@@ -475,13 +481,13 @@ Ignored
|
||||
.SH "ENVIRONMENT VARIABLES"
|
||||
.TP
|
||||
\fBMOLD_JOBS\fR
|
||||
If this variable is set to \fB1\fR, only one process of \fBmold\fR runs actively\. A mold process invoked while another active mold process is running will wait before doing anything until the active process exits\.
|
||||
If this variable is set to \fB1\fR, only one \fBmold\fR process will run at a time\. If a new mold process is initiated while another is already active, the new process will wait until the active one completes before starting\.
|
||||
.IP
|
||||
The purpose of this environment variable is to reduce peak memory usage\. Since mold is highly parallelized, there's no point in running it simultaneously\. If you run N instances of mold in parallel, it would take N times more time and N times more memory\. If you run them serially, it would still take N times more to finish, but their peak memory usage is reduced to normal\.
|
||||
The primary reason for this environment variable is to minimize peak memory usage\. Since mold is designed to operate with high parallelism, running multiple mold instances simultaneously may not be beneficial\. If you execute N instances of mold concurrently, it could require N times the time and N times the memory\. On the other hand, running them one after the other might still take N times longer, but the peak memory usage would be the same as running just a single instance\.
|
||||
.IP
|
||||
If your build system tends to invoke multiple linker processes simultaneously, you may want to try to set this environment variable to \fB1\fR to see if it could improve overall performance\.
|
||||
If your build system invokes multiple linker processes simultaneously and some of them often get killed due to out\-of\-memory errors, you might consider setting this environment variable to \fB1\fR to see if it addresses the OOM issue\.
|
||||
.IP
|
||||
Currently, any value other than 1 is silently ignored\.
|
||||
Currently, any value other than \fB1\fR is silently ignored\.
|
||||
.TP
|
||||
\fBMOLD_DEBUG\fR
|
||||
If this variable is set to a non\-empty string, \fBmold\fR embeds its command\-line options in the output file's \fB\.comment\fR section\.
|
||||
|
||||
+21
-13
@@ -321,6 +321,9 @@ arguments.
|
||||
_symbol_, pull out the object file and link it so that the output file
|
||||
contains a definition of _symbol_.
|
||||
|
||||
* `-y` _symbol_, `--trace-symbol`=_symbol_:
|
||||
Trace references to _symbol_.
|
||||
|
||||
* `--Bdynamic`:
|
||||
Link against shared libraries.
|
||||
|
||||
@@ -581,6 +584,9 @@ arguments.
|
||||
* `--spare-dynamic-tags`=_number_:
|
||||
Reserve the given _number_ of tags in `.dynamic` section.
|
||||
|
||||
* `--spare-program-headers`=_number_:
|
||||
Reserve the given _number_ of entries in the program header.
|
||||
|
||||
* `--start-lib`, `--end-lib`:
|
||||
Handle object files between `--start-lib` and `--end-lib` as if they were in
|
||||
an archive file. That means object files between them are linked only when
|
||||
@@ -764,22 +770,24 @@ arguments.
|
||||
## ENVIRONMENT VARIABLES
|
||||
|
||||
* `MOLD_JOBS`:
|
||||
If this variable is set to `1`, only one process of `mold` runs actively. A
|
||||
mold process invoked while another active mold process is running will wait
|
||||
before doing anything until the active process exits.
|
||||
If this variable is set to `1`, only one `mold` process will run at a time.
|
||||
If a new mold process is initiated while another is already active, the new
|
||||
process will wait until the active one completes before starting.
|
||||
|
||||
The purpose of this environment variable is to reduce peak memory usage.
|
||||
Since mold is highly parallelized, there's no point in running it
|
||||
simultaneously. If you run N instances of mold in parallel, it would take N
|
||||
times more time and N times more memory. If you run them serially, it would
|
||||
still take N times more to finish, but their peak memory usage is reduced to
|
||||
normal.
|
||||
The primary reason for this environment variable is to minimize peak memory
|
||||
usage. Since mold is designed to operate with high parallelism, running
|
||||
multiple mold instances simultaneously may not be beneficial. If you execute
|
||||
N instances of mold concurrently, it could require N times the time and N
|
||||
times the memory. On the other hand, running them one after the other might
|
||||
still take N times longer, but the peak memory usage would be the same as
|
||||
running just a single instance.
|
||||
|
||||
If your build system tends to invoke multiple linker processes
|
||||
simultaneously, you may want to try to set this environment variable to
|
||||
`1` to see if it could improve overall performance.
|
||||
If your build system invokes multiple linker processes simultaneously and
|
||||
some of them often get killed due to out-of-memory errors, you might
|
||||
consider setting this environment variable to `1` to see if it addresses the
|
||||
OOM issue.
|
||||
|
||||
Currently, any value other than 1 is silently ignored.
|
||||
Currently, any value other than `1` is silently ignored.
|
||||
|
||||
* `MOLD_DEBUG`:
|
||||
If this variable is set to a non-empty string, `mold` embeds its
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ void AlphaGotSection::copy_buf(Context<E> &ctx) {
|
||||
|
||||
if (e.sym->is_imported) {
|
||||
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, e.sym->get_dynsym_idx(ctx), e.addend);
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
|
||||
} else {
|
||||
*buf = e.sym->get_addr(ctx) + e.addend;
|
||||
if (ctx.arg.pic && !e.sym->is_absolute())
|
||||
|
||||
+5
-5
@@ -200,9 +200,9 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
@@ -225,7 +225,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
@@ -647,7 +647,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
@@ -602,7 +602,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
|
||||
+49
-6
@@ -164,7 +164,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
@@ -172,7 +172,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +254,34 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x8b83; // lea 0(%ebx), %eax -> mov 0(%ebx), %eax
|
||||
case 0x8d9b: return 0x8b9b; // lea 0(%ebx), %ebx -> mov 0(%ebx), %ebx
|
||||
case 0x8d8b: return 0x8b8b; // lea 0(%ebx), %ecx -> mov 0(%ebx), %ecx
|
||||
case 0x8d93: return 0x8b93; // lea 0(%ebx), %edx -> mov 0(%ebx), %edx
|
||||
case 0x8db3: return 0x8bb3; // lea 0(%ebx), %esi -> mov 0(%ebx), %esi
|
||||
case 0x8dbb: return 0x8bbb; // lea 0(%ebx), %edi -> mov 0(%ebx), %edi
|
||||
case 0x8da3: return 0x8ba3; // lea 0(%ebx), %esp -> mov 0(%ebx), %esp
|
||||
case 0x8dab: return 0x8bab; // lea 0(%ebx), %ebp -> mov 0(%ebx), %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x90b8; // lea 0(%ebx), %eax -> mov $0, %eax
|
||||
case 0x8d9b: return 0x90bb; // lea 0(%ebx), %ebx -> mov $0, %ebx
|
||||
case 0x8d8b: return 0x90b9; // lea 0(%ebx), %ecx -> mov $0, %ecx
|
||||
case 0x8d93: return 0x90ba; // lea 0(%ebx), %edx -> mov $0, %edx
|
||||
case 0x8db3: return 0x90be; // lea 0(%ebx), %esi -> mov $0, %esi
|
||||
case 0x8dbb: return 0x90bf; // lea 0(%ebx), %edi -> mov $0, %edi
|
||||
case 0x8da3: return 0x90bc; // lea 0(%ebx), %esp -> mov $0, %esp
|
||||
case 0x8dab: return 0x90bd; // lea 0(%ebx), %ebp -> mov $0, %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -373,15 +401,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
//
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// lea 0(%ebx), %reg
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// mov %reg, %eax
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x83;
|
||||
u32 insn = relax_tlsdesc_to_ie(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
loc[-2] = 0x90;
|
||||
loc[-1] = 0xb8;
|
||||
u32 insn = relax_tlsdesc_to_le(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
|
||||
+20
-25
@@ -65,14 +65,12 @@ static u64 hi64(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.
|
||||
bool x = val & 0x800;
|
||||
bool y = (page(val + 0x800) - page(pc)) & 0x8000'0000;
|
||||
|
||||
if (x && !y)
|
||||
return val - 0x1'0000'0000;
|
||||
if (!x && y)
|
||||
return val + 0x1'0000'0000;
|
||||
return val;
|
||||
u64 x = page(val) - page(pc);
|
||||
if (val & 0x800)
|
||||
x += 0x1000 - 0x1'0000'0000;
|
||||
if (x & 0x8000'0000)
|
||||
x += 0x1'0000'0000;
|
||||
return x;
|
||||
}
|
||||
|
||||
static u64 higher20(u64 val, u64 pc) {
|
||||
@@ -174,7 +172,7 @@ void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
@@ -325,10 +323,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_k12(loc, (S + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_PCALA_LO12:
|
||||
write_k12(loc, S + A);
|
||||
// It looks like R_LARCH_PCALA_LO12 is sometimes used for JIRL even
|
||||
// though the instruction takes a 16 bit immediate rather than 12 bits.
|
||||
// It is contrary to the psABI document, but GNU ld has special
|
||||
// code to handle it, so we accept it too.
|
||||
if ((*(ul32 *)loc & 0xfc00'0000) == 0x4c00'0000)
|
||||
write_k16(loc, sign_extend(S + A, 11) >> 2);
|
||||
else
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
case R_LARCH_PCALA_HI20:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(S + A, P));
|
||||
break;
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
@@ -341,7 +345,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_k12(loc, GOT + G + A);
|
||||
break;
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
check(GOT + G + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT64_PC_LO20:
|
||||
@@ -378,7 +381,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_k12(loc, sym.get_gottp_addr(ctx) + A);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_PC_HI20:
|
||||
check(sym.get_gottp_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_PC_LO20:
|
||||
@@ -567,11 +569,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
@@ -587,6 +584,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_B26:
|
||||
case R_LARCH_PCALA_HI20:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
@@ -620,7 +618,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_LARCH_ABS_LO12:
|
||||
case R_LARCH_ABS64_LO20:
|
||||
case R_LARCH_ABS64_HI12:
|
||||
case R_LARCH_PCALA_HI20:
|
||||
case R_LARCH_PCALA_LO12:
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
case R_LARCH_PCALA64_HI12:
|
||||
@@ -656,12 +653,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x1a00'000c, // pcalau12i $t0, 0
|
||||
0x02c0'018c, // addi.d $t0, $t0, 0
|
||||
0x1e00'000c, // pcaddu18i $t0, 0
|
||||
0x4c00'0180, // jirl $zero, $t0, 0
|
||||
0x0340'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
@@ -673,8 +668,8 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_j20(buf, hi20(S, P));
|
||||
write_k12(buf + 4, S);
|
||||
write_j20(buf, (S - P + 0x20000) >> 18);
|
||||
write_k16(buf + 4, (S - P) >> 2);
|
||||
|
||||
buf += sizeof(insn);
|
||||
P += sizeof(insn);
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+2
-2
@@ -120,7 +120,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 offset = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
i64 offset = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
loc[4] |= higha(offset);
|
||||
loc[5] |= lo(offset);
|
||||
}
|
||||
@@ -408,7 +408,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ub32 local_thunk[] = {
|
||||
// Get this thunk's address
|
||||
0x7c08'02a6, // mflr r0
|
||||
|
||||
+10
-13
@@ -409,7 +409,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// If the destination is .plt.got, we save the current r2, read an
|
||||
// address of a function descriptor from .got, restore %r2 and jump
|
||||
// to the function.
|
||||
@@ -465,7 +465,6 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_got(ctx)) {
|
||||
@@ -486,7 +485,6 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,7 +519,7 @@ struct OpdSymbol {
|
||||
};
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
@@ -616,7 +614,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
if (sym.get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
|
||||
Symbol<E> *real_sym = get_opd_sym_at(opd_syms, r.r_addend);
|
||||
if (!real_sym)
|
||||
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
|
||||
<< " at offset 0x" << std::hex << (u64)r.r_addend;
|
||||
@@ -640,15 +638,14 @@ void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Functions referenced by the ELF header also have to have .opd entries.
|
||||
auto mark = [&](std::string_view name) {
|
||||
if (!name.empty())
|
||||
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
};
|
||||
if (!ctx.arg.entry->is_imported)
|
||||
ctx.arg.entry->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
mark(ctx.arg.entry);
|
||||
mark(ctx.arg.init);
|
||||
mark(ctx.arg.fini);
|
||||
if (!ctx.arg.init->is_imported)
|
||||
ctx.arg.init->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
if (!ctx.arg.fini->is_imported)
|
||||
ctx.arg.fini->flags |= NEEDS_PPC_OPD;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
|
||||
+28
-18
@@ -92,8 +92,10 @@ static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
static u64 prefix34(u64 x) {
|
||||
return bits(x, 33, 16) | (bits(x, 15, 0) << 32);
|
||||
static void write34(u8 *loc, u64 x) {
|
||||
ul32 *buf = (ul32 *)loc;
|
||||
buf[0] = (buf[0] & 0xfffc'0000) | bits(x, 33, 16);
|
||||
buf[1] = (buf[1] & 0xffff'0000) | bits(x, 15, 0);
|
||||
}
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
@@ -205,7 +207,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
@@ -279,10 +281,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(G + GOT - P);
|
||||
write34(loc, G + GOT - P);
|
||||
break;
|
||||
case R_PPC64_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - P);
|
||||
write34(loc, S + A - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
@@ -291,7 +293,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(sym.get_gottp_addr(ctx) - P);
|
||||
write34(loc, sym.get_gottp_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
@@ -300,7 +302,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(sym.get_tlsgd_addr(ctx) - P);
|
||||
write34(loc, sym.get_tlsgd_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
@@ -309,7 +311,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(ctx.got->get_tlsld_addr(ctx) - P);
|
||||
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
@@ -318,7 +320,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - ctx.dtp_addr);
|
||||
write34(loc, S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
@@ -326,6 +328,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL34:
|
||||
write34(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
@@ -443,6 +448,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL34:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
@@ -478,11 +484,12 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// If the destination is PLT, we read an address from .got.plt or .got
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -491,6 +498,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
static const ul32 plt_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
||||
0xe580'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -501,6 +509,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -509,6 +518,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
static const ul32 local_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0610'0000, // pla r12, foo@pcrel
|
||||
0x3980'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -522,6 +532,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_plt(ctx)) {
|
||||
@@ -530,22 +541,21 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, plt_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(buf + 1) |= prefix34(got - P - 4);
|
||||
write34(buf + 8, got - P - 8);
|
||||
} else {
|
||||
i64 val = got - ctx.extra.TOC->value;
|
||||
memcpy(buf, plt_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 4) |= higha(val);
|
||||
*(ul32 *)(buf + 8) |= lo(val);
|
||||
*(ul32 *)(buf + 8) |= higha(got - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(got - TOC);
|
||||
}
|
||||
} else {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, local_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(buf + 1) |= prefix34(sym->get_addr(ctx) - P - 4);
|
||||
write34(buf + 8, S - P - 8);
|
||||
} else {
|
||||
i64 val = sym->get_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, local_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 4) |= higha(val);
|
||||
*(ul32 *)(buf + 8) |= lo(val);
|
||||
*(ul32 *)(buf + 8) |= higha(S - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(S - TOC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-5
@@ -198,7 +198,7 @@ void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
@@ -259,6 +259,7 @@ static inline bool is_hi20(const ElfRel<E> &rel) {
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
u64 GP = ctx.__global_pointer ? ctx.__global_pointer->get_addr(ctx) : 0;
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
@@ -375,7 +376,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_utype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S: {
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_GPREL_LO12_I:
|
||||
case R_RISCV_GPREL_LO12_S: {
|
||||
i64 idx2 = find_paired_reloc();
|
||||
const ElfRel<E> &rel2 = rels[idx2];
|
||||
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
|
||||
@@ -399,11 +402,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_PCREL_HI20:
|
||||
val = S + A - P;
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
val = S + A - GP;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
if (rel.r_type == R_RISCV_PCREL_LO12_I)
|
||||
if (rel.r_type == R_RISCV_PCREL_LO12_I ||
|
||||
rel.r_type == R_RISCV_GPREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
@@ -560,6 +567,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
write_cjtype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
check(S + A - GP, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, S + A - GP);
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
@@ -761,6 +772,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_TPREL_ADD:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
if (ctx.arg.shared)
|
||||
Fatal(ctx) << *this << ": R_RISCV_GPREL_HI20 may not be used with -shared";
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
@@ -781,6 +796,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_ALIGN:
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
case R_RISCV_GPREL_LO12_I:
|
||||
case R_RISCV_GPREL_LO12_S:
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
@@ -821,7 +838,7 @@ u64 get_eflags(Context<E> &ctx) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
static bool is_resizable(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
|
||||
(isec->shdr().sh_flags & SHF_EXECINSTR);
|
||||
}
|
||||
@@ -1021,7 +1038,7 @@ i64 riscv_resize_sections<E>(Context<E> &ctx) {
|
||||
// This step should only shrink sections.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
if (is_resizable(isec.get()))
|
||||
shrink_section(ctx, *isec, use_rvc);
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+2
-2
@@ -169,7 +169,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
@@ -181,7 +181,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx);
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ub64 *)(buf + 24) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub64 *)(buf + 24) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+97
-27
@@ -64,28 +64,41 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
// Only a canonical PLT can be address-taken; there's no way to take
|
||||
// an address of a non-canonical PLT. Therefore, a non-canonical PLT
|
||||
// doesn't have to start with an endbr64.
|
||||
if (sym.is_canonical) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 6;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -165,6 +178,50 @@ static u32 relax_gottpoff(u8 *loc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x488b05; // lea 0(%rip), %rax -> mov 0(%rip), %rax
|
||||
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %rcx -> mov 0(%rip), %rcx
|
||||
case 0x488d15: return 0x488b15; // lea 0(%rip), %rdx -> mov 0(%rip), %rdx
|
||||
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %rbx -> mov 0(%rip), %rbx
|
||||
case 0x488d25: return 0x488b25; // lea 0(%rip), %rsp -> mov 0(%rip), %rsp
|
||||
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %rbp -> mov 0(%rip), %rbp
|
||||
case 0x488d35: return 0x488b35; // lea 0(%rip), %rsi -> mov 0(%rip), %rsi
|
||||
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %rdi -> mov 0(%rip), %rdi
|
||||
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r8 -> mov 0(%rip), %r8
|
||||
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r9 -> mov 0(%rip), %r9
|
||||
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r10 -> mov 0(%rip), %r10
|
||||
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r11 -> mov 0(%rip), %r11
|
||||
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r12 -> mov 0(%rip), %r12
|
||||
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r13 -> mov 0(%rip), %r13
|
||||
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r14 -> mov 0(%rip), %r14
|
||||
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r15 -> mov 0(%rip), %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. We can do this when we know the thread-local variable's TP-
|
||||
// relative address at link-time.
|
||||
@@ -345,7 +402,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
@@ -388,19 +445,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_PLTOFF64:
|
||||
*(ul64 *)loc = S + A - GOTPLT;
|
||||
*(ul64 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOTPLT + A - P);
|
||||
write32s(GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
*(ul64 *)loc = GOTPLT + A - P;
|
||||
*(ul64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
*(ul64 *)loc = G + GOTPLT + A - P;
|
||||
*(ul64 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
// We always want to relax GOTPCRELX relocs even if --no-relax
|
||||
@@ -416,7 +473,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
@@ -430,7 +487,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
@@ -488,19 +545,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
//
|
||||
// mov $foo@TPOFF, %rax
|
||||
// nop
|
||||
//
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// lea 0(%rip), %reg
|
||||
// R_X86_64_GOTPC32_TLSDESC foo
|
||||
// mov %reg, %rax
|
||||
// call *(%rax)
|
||||
// R_X86_64_TLSDESC_CALL foo
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// mov foo@gottpoff(%rip), %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x05;
|
||||
u32 insn = relax_tlsdesc_to_ie(loc - 3);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
// mov $foo@tpoff, %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0xc7;
|
||||
loc[-1] = 0xc0;
|
||||
u32 insn = relax_tlsdesc_to_le(loc - 3);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S - ctx.tp_addr);
|
||||
}
|
||||
break;
|
||||
|
||||
+48
-21
@@ -52,6 +52,8 @@ Options:
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
-y SYMBOL, --trace-symbol SYMBOL
|
||||
Trace references to SYMBOL
|
||||
--Bdynamic, --dy Link against shared libraries (default)
|
||||
--Bstatic, --dn, --static Do not link against shared libraries
|
||||
--Bsymbolic Bind global symbols locally
|
||||
@@ -107,6 +109,8 @@ Options:
|
||||
Allow merging non-executable sections with --icf
|
||||
--image-base ADDR Set the base address to a given value
|
||||
--init SYMBOL Call SYMBOL at load-time
|
||||
--nmagic Do not page align sections
|
||||
--no-nmagic
|
||||
--no-undefined Report undefined symbols (even with --shared)
|
||||
--noinhibit-exec Create an output file even if errors occur
|
||||
--oformat=binary Omit ELF, section and program headers
|
||||
@@ -140,7 +144,9 @@ Options:
|
||||
--shuffle-sections[=SEED] Randomize the output by shuffling input sections
|
||||
--sort-common Ignored
|
||||
--sort-section Ignored
|
||||
--spare-dynamic-tags NUMBER Reserve give number of tags in .dynamic section
|
||||
--spare-dynamic-tags NUMBER Reserve given number of tags in .dynamic section
|
||||
--spare-program-headers NUMBER
|
||||
Reserve given number of slots in the program header
|
||||
--start-lib Give following object files in-archive-file semantics
|
||||
--end-lib End the effect of --start-lib
|
||||
--stats Print input statistics
|
||||
@@ -586,7 +592,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.emit_relocs = true;
|
||||
ctx.arg.discard_locals = false;
|
||||
} else if (read_arg("e") || read_arg("entry")) {
|
||||
ctx.arg.entry = arg;
|
||||
ctx.arg.entry = get_symbol(ctx, arg);
|
||||
} else if (read_arg("Map")) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
@@ -604,6 +610,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.shared = true;
|
||||
} else if (read_arg("spare-dynamic-tags")) {
|
||||
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
|
||||
} else if (read_arg("spare-program-headers")) {
|
||||
ctx.arg.spare_program_headers
|
||||
= parse_number(ctx, "spare-program-headers", arg);
|
||||
} else if (read_flag("start-lib")) {
|
||||
remaining.push_back("--start-lib");
|
||||
} else if (read_flag("start-stop")) {
|
||||
@@ -660,13 +669,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg("undefined") || read_arg("u")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
ctx.arg.undefined.push_back(get_symbol(ctx, arg));
|
||||
} else if (read_arg("require-defined")) {
|
||||
ctx.arg.require_defined.push_back(arg);
|
||||
ctx.arg.require_defined.push_back(get_symbol(ctx, arg));
|
||||
} else if (read_arg("init")) {
|
||||
ctx.arg.init = arg;
|
||||
ctx.arg.init = get_symbol(ctx, arg);
|
||||
} else if (read_arg("fini")) {
|
||||
ctx.arg.fini = arg;
|
||||
ctx.arg.fini = get_symbol(ctx, arg);
|
||||
} else if (read_arg("hash-style")) {
|
||||
if (arg == "sysv") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
@@ -883,8 +892,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.z_sectionheader = true;
|
||||
} else if (read_z_flag("nosectionheader")) {
|
||||
ctx.arg.z_sectionheader = false;
|
||||
} else if (read_z_flag("rewrite-endbr")) {
|
||||
ctx.arg.z_rewrite_endbr = true;
|
||||
} else if (read_flag("no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag("nmagic")) {
|
||||
ctx.arg.nmagic = true;
|
||||
} else if (read_flag("no-nmagic")) {
|
||||
ctx.arg.nmagic = false;
|
||||
} else if (read_flag("fatal-warnings")) {
|
||||
ctx.arg.fatal_warnings = true;
|
||||
} else if (read_flag("no-fatal-warnings")) {
|
||||
@@ -1102,21 +1117,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("no-keep-memory")) {
|
||||
} else if (read_arg("max-cache-size")) {
|
||||
} else if (read_arg("version-script")) {
|
||||
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
|
||||
// are treated as positional arguments even though they are actually not
|
||||
// positional. This is because linker scripts (a positional argument)
|
||||
// can also specify a version script, and it's better to consolidate
|
||||
// parsing in read_input_files. In particular, version scripts can
|
||||
// modify ctx.default_version which we initialize *after* parsing
|
||||
// non-positional args, so the parsing cannot be done right here.
|
||||
// --version-script is treated as positional arguments even though
|
||||
// they are actually not positional. This is because linker scripts
|
||||
// (a positional argument) can also specify a version script, and
|
||||
// it's better to consolidate parsing in read_input_files. In
|
||||
// particular, version scripts can modify ctx.default_version which
|
||||
// we initialize *after* parsing non-positional args, so the parsing
|
||||
// cannot be done right here.
|
||||
remaining.push_back("--version-script=" + std::string(arg));
|
||||
} else if (read_arg("dynamic-list")) {
|
||||
ctx.arg.Bsymbolic = true;
|
||||
remaining.push_back("--dynamic-list=" + std::string(arg));
|
||||
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
|
||||
} else if (read_arg("export-dynamic-symbol")) {
|
||||
remaining.push_back("--export-dynamic-symbol=" + std::string(arg));
|
||||
ctx.dynamic_list_patterns.push_back({arg, "<command line>"});
|
||||
} else if (read_arg("export-dynamic-symbol-list")) {
|
||||
remaining.push_back("--export-dynamic-symbol-list=" + std::string(arg));
|
||||
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
|
||||
} else if (read_flag("as-needed")) {
|
||||
remaining.push_back("--as-needed");
|
||||
} else if (read_flag("no-as-needed")) {
|
||||
@@ -1198,6 +1213,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
else if (!ctx.arg.section_order.empty())
|
||||
ctx.arg.z_relro = false;
|
||||
|
||||
if (ctx.arg.nmagic)
|
||||
ctx.arg.z_relro = false;
|
||||
|
||||
if (!ctx.arg.shared) {
|
||||
if (!ctx.arg.filter.empty())
|
||||
Fatal(ctx) << "-filter may not be used without -shared";
|
||||
@@ -1226,11 +1244,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
|
||||
if (ctx.arg.shared || ctx.arg.export_dynamic)
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.default_version = VER_NDX_LOCAL;
|
||||
|
||||
if (ctx.arg.default_symver) {
|
||||
std::string ver = ctx.arg.soname.empty() ?
|
||||
filepath(ctx.arg.output).filename().string() : std::string(ctx.arg.soname);
|
||||
@@ -1243,6 +1256,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
|
||||
ctx.arg.undefined.push_back(ctx.arg.entry);
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
std::variant<Symbol<E> *, u64> &val = ctx.arg.defsyms[i].second;
|
||||
if (Symbol<E> **sym = std::get_if<Symbol<E> *>(&val))
|
||||
ctx.arg.undefined.push_back(*sym);
|
||||
}
|
||||
|
||||
// --oformat=binary implies --strip-all because without a section
|
||||
// header, there's no way to identify the locations of a symbol
|
||||
// table in an output file in the first place.
|
||||
@@ -1262,6 +1281,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
ctx.overwrite_output_file = false;
|
||||
|
||||
if (!ctx.arg.chroot.empty()) {
|
||||
if (!ctx.arg.Map.empty())
|
||||
ctx.arg.Map = ctx.arg.chroot + "/" + ctx.arg.Map;
|
||||
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
ctx.arg.dependency_file = ctx.arg.chroot + "/" + ctx.arg.dependency_file;
|
||||
}
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
return remaining;
|
||||
|
||||
-554
@@ -1,554 +0,0 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, which often abbreviated as compunit
|
||||
// or cu, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using `gdb-add-index`
|
||||
// post-processing tool that comes with gdb.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name (with the same definition), while there
|
||||
// should be no two functions that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contains attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -fucntion-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4/5 and
|
||||
// .debug_rnglists/.debug_addr in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Split .debug_info into so-called "compilation units". A .debug_info
|
||||
// section usually contains one compunit unless it was created by `ld -r`.
|
||||
// This is for --gdb-index.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
file.debug_info->uncompress(ctx);
|
||||
std::string_view data = file.debug_info->contents;
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(U32<E> *)data.data() == 0xffff'ffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(U32<E> *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<GdbIndexName> vec;
|
||||
|
||||
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
|
||||
i64 off = 0;
|
||||
for (i64 i = 0; i < file.compunits.size(); i++) {
|
||||
if (offset == off)
|
||||
return file.compunits_idx + i;
|
||||
off += file.compunits[i].size();
|
||||
}
|
||||
Fatal(ctx) << isec << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
auto read = [&](InputSection<E> &isec) {
|
||||
isec.uncompress(ctx);
|
||||
std::string_view contents = isec.contents;
|
||||
|
||||
while (!contents.empty()) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(U32<E> *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(U32<E> *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
|
||||
u8 type = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string_view name = data.data();
|
||||
data = data.substr(name.size() + 1);
|
||||
|
||||
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (file.debug_pubnames)
|
||||
read(*file.debug_pubnames);
|
||||
if (file.debug_pubtypes)
|
||||
read(*file.debug_pubtypes);
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} <
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} ==
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
std::sort(vec.begin(), vec.end(), less);
|
||||
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (u8 *buf = chunk->get_uncompressed_data())
|
||||
return buf;
|
||||
return ctx.buf + chunk->shdr.sh_offset;
|
||||
}
|
||||
|
||||
// Try to find a compilation unit from .debug_info and its
|
||||
// corresponding record from .debug_abbrev and returns them.
|
||||
template <typename E>
|
||||
static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(U16<E> *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(U32<E> *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(U32<E> *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
|
||||
switch (u32 unit_type = cu[6]; unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
cu += 12;
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
cu += 20;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
|
||||
<< std::hex << unit_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
|
||||
<< dwarf_version;
|
||||
}
|
||||
|
||||
u32 abbrev_code = read_uleb(&cu);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(&abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(&abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(&abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(&abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return {cu, abbrev, dwarf_version};
|
||||
}
|
||||
|
||||
// Estimate the number of address ranges contained in a given file.
|
||||
// It may over-estimate but never under-estimate.
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
// Each CU contains zero or one address area.
|
||||
i64 ret = file.compunits.size();
|
||||
|
||||
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
|
||||
// .debug_ranges contains a vector of [begin, end) address pairs.
|
||||
// The last entry must be a null terminator, so we do -1.
|
||||
if (file.debug_ranges)
|
||||
ret += file.debug_ranges->sh_size / sizeof(Word<E>) / 2 - 1;
|
||||
|
||||
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
|
||||
// contains variable-length entries. The smallest possible range entry
|
||||
// is one byte for the code and two ULEB128 values (each can be as
|
||||
// small as one byte), so 3 bytes.
|
||||
if (file.debug_rnglists)
|
||||
ret += file.debug_rnglists->sh_size / 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields. This class reads them.
|
||||
template <typename E>
|
||||
class DebugInfoReader {
|
||||
public:
|
||||
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
|
||||
: ctx(ctx), file(file), cu(cu) {}
|
||||
|
||||
u64 read(u64 form);
|
||||
|
||||
Context<E> &ctx;
|
||||
ObjectFile<E> &file;
|
||||
u8 *cu;
|
||||
};
|
||||
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *cu++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)cu;
|
||||
cu += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(&cu);
|
||||
case DW_FORM_string:
|
||||
cu += strlen((char *)cu) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0) {
|
||||
// base address selection entry
|
||||
base = range[i + 1];
|
||||
} else {
|
||||
vec.push_back(range[i] + base);
|
||||
vec.push_back(range[i + 1] + base);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
Word<E> *addrx) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (;;) {
|
||||
switch (*rnglist++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return vec;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(&rnglist)];
|
||||
break;
|
||||
case DW_RLE_startx_endx:
|
||||
vec.push_back(addrx[read_uleb(&rnglist)]);
|
||||
vec.push_back(addrx[read_uleb(&rnglist)]);
|
||||
break;
|
||||
case DW_RLE_startx_length:
|
||||
vec.push_back(addrx[read_uleb(&rnglist)]);
|
||||
vec.push_back(vec.back() + read_uleb(&rnglist));
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
vec.push_back(base + read_uleb(&rnglist));
|
||||
vec.push_back(base + read_uleb(&rnglist));
|
||||
break;
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)rnglist;
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(vec.back() + read_uleb(&rnglist));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
u8 *cu;
|
||||
u8 *abbrev;
|
||||
u32 dwarf_version;
|
||||
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
|
||||
|
||||
DebugInfoReader<E> reader{ctx, file, cu};
|
||||
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
std::optional<u64> rnglists_base;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = reader.read(form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(get_buffer(ctx, ctx.debug_addr) + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (dwarf_version <= 4) {
|
||||
Word<E> *range_begin =
|
||||
(Word<E> *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
|
||||
return read_debug_range(ctx, file, range_begin);
|
||||
}
|
||||
|
||||
assert(dwarf_version == 5);
|
||||
|
||||
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
|
||||
if (ranges.form == DW_FORM_sec_offset)
|
||||
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
|
||||
|
||||
if (!rnglists_base)
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {lo, high_pc.value};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {lo, addrx[high_pc.value]};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {lo, lo + high_pc.value};
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
|
||||
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -161,6 +161,7 @@ enum : u32 {
|
||||
VER_NDX_LOCAL = 0,
|
||||
VER_NDX_GLOBAL = 1,
|
||||
VER_NDX_LAST_RESERVED = 1,
|
||||
VER_NDX_UNSPECIFIED = 0xffff,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -291,6 +292,7 @@ enum : u32 {
|
||||
DT_VERNEEDNUM = 0x6fffffff,
|
||||
DT_PPC_GOT = 0x70000000,
|
||||
DT_PPC64_GLINK = 0x70000000,
|
||||
DT_AARCH64_VARIANT_PCS = 0x70000005,
|
||||
DT_AUXILIARY = 0x7ffffffd,
|
||||
DT_FILTER = 0x7fffffff,
|
||||
};
|
||||
@@ -774,6 +776,9 @@ enum : u32 {
|
||||
R_RISCV_RVC_BRANCH = 44,
|
||||
R_RISCV_RVC_JUMP = 45,
|
||||
R_RISCV_RVC_LUI = 46,
|
||||
R_RISCV_GPREL_LO12_I = 47,
|
||||
R_RISCV_GPREL_LO12_S = 48,
|
||||
R_RISCV_GPREL_HI20 = 49,
|
||||
R_RISCV_RELAX = 51,
|
||||
R_RISCV_SUB6 = 52,
|
||||
R_RISCV_SET6 = 53,
|
||||
@@ -1638,6 +1643,16 @@ struct ElfRel<E> {
|
||||
std::conditional_t<E::is_64, U32<E>, u8> r_type;
|
||||
};
|
||||
|
||||
// Returns true if a given relocation is of type used for direct
|
||||
// function call.
|
||||
template <typename E>
|
||||
inline bool is_func_call_rel(const ElfRel<E> &r) {
|
||||
for (u32 r_type : E::R_FUNCALL)
|
||||
if (r.r_type == r_type)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct ElfDyn {
|
||||
Word<E> d_tag;
|
||||
@@ -1705,6 +1720,35 @@ struct ElfNhdr {
|
||||
// Target-specific ELF data types
|
||||
//
|
||||
|
||||
template <>
|
||||
struct ElfSym<ARM64> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 5;
|
||||
u8 arm64_variant_pcs : 1; // ARM64-specific
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 arm64_variant_pcs : 1;
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
#endif
|
||||
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfSym<PPC64V2> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
@@ -1824,19 +1868,20 @@ struct X86_64 {
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0xcc }; // int3
|
||||
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_X86_64_64;
|
||||
static constexpr u32 R_DYNAMIC = R_X86_64_64;
|
||||
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
|
||||
};
|
||||
|
||||
struct I386 {
|
||||
@@ -1849,18 +1894,19 @@ struct I386 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0xcc }; // int3
|
||||
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_386_32;
|
||||
static constexpr u32 R_DYNAMIC = R_386_32;
|
||||
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_386_PLT32 };
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
@@ -1875,18 +1921,19 @@ struct ARM64 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 }; // brk
|
||||
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_AARCH64_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_AARCH64_ABS64;
|
||||
static constexpr u32 R_DYNAMIC = R_AARCH64_ABS64;
|
||||
static constexpr u32 R_RELATIVE = R_AARCH64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_AARCH64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_AARCH64_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
|
||||
};
|
||||
|
||||
struct ARM32 {
|
||||
@@ -1901,18 +1948,22 @@ struct ARM32 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 16;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0xff, 0xde }; // udf
|
||||
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_ARM_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ARM_ABS32;
|
||||
static constexpr u32 R_DYNAMIC = R_ARM_ABS32;
|
||||
static constexpr u32 R_RELATIVE = R_ARM_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_ARM_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_ARM_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_ARM_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_ARM_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_ARM_TLS_DESC;
|
||||
|
||||
static constexpr u32 R_FUNCALL[] = {
|
||||
R_ARM_JUMP24, R_ARM_THM_JUMP24, R_ARM_CALL, R_ARM_THM_CALL, R_ARM_PLT32,
|
||||
};
|
||||
};
|
||||
|
||||
struct RV64 {
|
||||
@@ -1923,18 +1974,19 @@ struct RV64 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 }; // ebreak
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_64;
|
||||
static constexpr u32 R_DYNAMIC = R_RISCV_64;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV64LE : RV64 {
|
||||
@@ -1955,18 +2007,19 @@ struct RV32 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 }; // ebreak
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_DYNAMIC = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV32LE : RV32 {
|
||||
@@ -1991,17 +2044,21 @@ struct PPC32 {
|
||||
static constexpr u32 pltgot_size = 36;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 36;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC_ADDR32;
|
||||
static constexpr u32 R_DYNAMIC = R_PPC_ADDR32;
|
||||
static constexpr u32 R_RELATIVE = R_PPC_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_PPC_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_PPC_DTPMOD32;
|
||||
|
||||
static constexpr u32 R_FUNCALL[] = {
|
||||
R_PPC_REL24, R_PPC_PLTREL24, R_PPC_LOCAL24PC,
|
||||
};
|
||||
};
|
||||
|
||||
struct PPC64 {
|
||||
@@ -2014,12 +2071,12 @@ struct PPC64 {
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_DYNAMIC = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_PPC64_REL24, R_PPC64_REL24_NOTOC };
|
||||
};
|
||||
|
||||
struct PPC64V1 : PPC64 {
|
||||
@@ -2029,6 +2086,7 @@ struct PPC64V1 : PPC64 {
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 28;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
|
||||
};
|
||||
|
||||
struct PPC64V2 : PPC64 {
|
||||
@@ -2038,7 +2096,8 @@ struct PPC64V2 : PPC64 {
|
||||
static constexpr u32 plt_size = 4;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
static constexpr u32 thunk_size = 24;
|
||||
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f }; // trap
|
||||
};
|
||||
|
||||
struct S390X {
|
||||
@@ -2051,17 +2110,18 @@ struct S390X {
|
||||
static constexpr u32 plt_hdr_size = 48;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x07, 0x00 }; // nop
|
||||
|
||||
static constexpr u32 R_COPY = R_390_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_390_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_390_64;
|
||||
static constexpr u32 R_DYNAMIC = R_390_64;
|
||||
static constexpr u32 R_RELATIVE = R_390_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_390_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
|
||||
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
|
||||
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
|
||||
};
|
||||
|
||||
struct SPARC64 {
|
||||
@@ -2074,17 +2134,18 @@ struct SPARC64 {
|
||||
static constexpr u32 plt_hdr_size = 128;
|
||||
static constexpr u32 plt_size = 32;
|
||||
static constexpr u32 pltgot_size = 32;
|
||||
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 }; // ta 5
|
||||
|
||||
static constexpr u32 R_COPY = R_SPARC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_SPARC_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_SPARC_64;
|
||||
static constexpr u32 R_DYNAMIC = R_SPARC_64;
|
||||
static constexpr u32 R_RELATIVE = R_SPARC_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_SPARC_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_SPARC_TLS_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_SPARC_TLS_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_SPARC_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SPARC_WPLT30, R_SPARC_WDISP30 };
|
||||
};
|
||||
|
||||
struct M68K {
|
||||
@@ -2097,16 +2158,17 @@ struct M68K {
|
||||
static constexpr u32 plt_hdr_size = 18;
|
||||
static constexpr u32 plt_size = 14;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0x4a, 0xfc }; // illegal
|
||||
|
||||
static constexpr u32 R_COPY = R_68K_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_68K_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_68K_32;
|
||||
static constexpr u32 R_DYNAMIC = R_68K_32;
|
||||
static constexpr u32 R_RELATIVE = R_68K_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_68K_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_68K_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_68K_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_68K_PLT32 };
|
||||
};
|
||||
|
||||
struct SH4 {
|
||||
@@ -2119,16 +2181,17 @@ struct SH4 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 12;
|
||||
static constexpr u8 filler[] = { 0x00, 0x90 }; // nop
|
||||
|
||||
static constexpr u32 R_COPY = R_SH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_SH_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_SH_DIR32;
|
||||
static constexpr u32 R_DYNAMIC = R_SH_DIR32;
|
||||
static constexpr u32 R_RELATIVE = R_SH_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_SH_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_SH_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_SH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SH_PLT32 };
|
||||
};
|
||||
|
||||
struct ALPHA {
|
||||
@@ -2141,16 +2204,17 @@ struct ALPHA {
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 0;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 }; // bugchk
|
||||
|
||||
static constexpr u32 R_COPY = R_ALPHA_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
|
||||
static constexpr u32 R_DYNAMIC = R_ALPHA_REFQUAD;
|
||||
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = {};
|
||||
};
|
||||
|
||||
struct LOONGARCH64 {
|
||||
@@ -2164,18 +2228,19 @@ struct LOONGARCH64 {
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_64;
|
||||
static constexpr u32 R_DYNAMIC = R_LARCH_64;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
struct LOONGARCH32 {
|
||||
@@ -2189,18 +2254,19 @@ struct LOONGARCH32 {
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_32;
|
||||
static constexpr u32 R_DYNAMIC = R_LARCH_32;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
} // namespace mold::elf
|
||||
+4
-6
@@ -114,13 +114,11 @@ static void collect_root_set(Context<E> &ctx,
|
||||
});
|
||||
|
||||
// Add sections referenced by root symbols.
|
||||
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
enqueue_symbol(sym);
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
enqueue_symbol(sym);
|
||||
|
||||
// .eh_frame consists of variable-length records called CIE and FDE
|
||||
// records, and they are a unit of inclusion or exclusion.
|
||||
|
||||
@@ -0,0 +1,793 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, often abbreviated as compunit or
|
||||
// CU, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using the
|
||||
// `gdb-add-index` post-processing tool that comes with gdb.
|
||||
//
|
||||
// Post-relocated debug section contents are needed to create a
|
||||
// .gdb_index. Therefore, we create it after relocating all the other
|
||||
// sections. The size of the section is also hard to estimate before
|
||||
// applying relocations to debug info sections, so a .gdb_index is
|
||||
// placed at the very end of the output file, even after the section
|
||||
// header.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name, while there should be no two functions
|
||||
// that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contain attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -ffunction-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4 and .debug_rnglists/.debug_addr
|
||||
// in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
enum DwarfKind { DWARF2_32, DWARF5_32, DWARF2_64, DWARF5_64 };
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U32<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U64<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> debug_info_offset;
|
||||
U32<E> debug_info_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> debug_info_offset;
|
||||
U64<E> debug_info_size;
|
||||
};
|
||||
|
||||
struct SectionHeader {
|
||||
ul32 version = 7;
|
||||
ul32 cu_list_offset = 0;
|
||||
ul32 cu_types_offset = 0;
|
||||
ul32 ranges_offset = 0;
|
||||
ul32 symtab_offset = 0;
|
||||
ul32 const_pool_offset = 0;
|
||||
};
|
||||
|
||||
struct NameType {
|
||||
bool operator==(const NameType &) const = default;
|
||||
|
||||
bool operator<(const NameType &other) const {
|
||||
return std::tuple(hash, type, name) <
|
||||
std::tuple(other.hash, other.type, other.name);
|
||||
}
|
||||
|
||||
std::string_view name;
|
||||
u32 hash;
|
||||
u8 type;
|
||||
};
|
||||
|
||||
struct MapValue {
|
||||
u32 hash = 0;
|
||||
Atomic<u32> count = 0;
|
||||
u32 name_offset = 0;
|
||||
u32 type_offset = 0;
|
||||
};
|
||||
|
||||
struct Compunit {
|
||||
DwarfKind kind;
|
||||
i64 offset;
|
||||
i64 size;
|
||||
std::vector<std::pair<u64, u64>> ranges;
|
||||
std::vector<NameType> nametypes;
|
||||
std::vector<MapValue *> entries;
|
||||
};
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static DwarfKind get_dwarf_kind(Context<E> &ctx, u8 *p) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff) {
|
||||
CuHdrDwarf2_64<E> &hdr = *(CuHdrDwarf2_64<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_64 : DWARF2_64;
|
||||
}
|
||||
|
||||
CuHdrDwarf2_32<E> &hdr = *(CuHdrDwarf2_32<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_32 : DWARF2_32;
|
||||
}
|
||||
|
||||
template <typename E, typename CuHdr>
|
||||
u8 *find_cu_abbrev(Context<E> &ctx, u8 **p, const CuHdr &hdr) {
|
||||
if (hdr.address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << "--gdb-index: unsupported address size " << hdr.address_size;
|
||||
|
||||
if constexpr (requires { hdr.unit_type; }) {
|
||||
switch (hdr.unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
*p += 8;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unknown unit type: 0x"
|
||||
<< std::hex << hdr.unit_type;
|
||||
}
|
||||
}
|
||||
|
||||
i64 abbrev_code = read_uleb(p);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = &ctx.debug_abbrev[0] + hdr.abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(&abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << "--gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(&abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << "--gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(&abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(&abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return abbrev;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields.
|
||||
// This function reads one scalar value from a given location.
|
||||
template <typename E, typename Offset>
|
||||
u64 read_scalar(Context<E> &ctx, u8 **p, u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *(*p)++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)*p;
|
||||
*p += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)*p;
|
||||
*p += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)*p;
|
||||
*p += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)*p;
|
||||
*p += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp: {
|
||||
u64 val = *(Offset *)*p;
|
||||
*p += sizeof(Offset);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)*p;
|
||||
*p += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(p);
|
||||
case DW_FORM_string:
|
||||
*p += strlen((char *)*p) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_debug_range(Word<E> *range, u64 base) {
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0)
|
||||
base = range[i + 1];
|
||||
else
|
||||
vec.emplace_back(range[i] + base, range[i + 1] + base);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static void
|
||||
read_rnglist_range(std::vector<std::pair<u64, u64>> &vec, u8 *p,
|
||||
Word<E> *addrx, u64 base) {
|
||||
for (;;) {
|
||||
switch (*p++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(&p)];
|
||||
break;
|
||||
case DW_RLE_startx_endx: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val2]);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_startx_length: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val1] + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_offset_pair: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
|
||||
// If the base is 0, this address range is for an eliminated
|
||||
// section. We only emit it if it's alive.
|
||||
if (base)
|
||||
vec.emplace_back(base + val1, base + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end: {
|
||||
u64 val1 = ((Word<E> *)p)[0];
|
||||
u64 val2 = ((Word<E> *)p)[1];
|
||||
p += sizeof(Word<E>) * 2;
|
||||
vec.emplace_back(val1, val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_start_length: {
|
||||
u64 val1 = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(val1, val1 + val2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E, typename CuHdr>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_address_ranges(Context<E> &ctx, const Compunit &cu) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *p = &ctx.debug_info[0] + cu.offset;
|
||||
CuHdr &hdr = *(CuHdr *)p;
|
||||
p += sizeof(hdr);
|
||||
|
||||
u8 *abbrev = find_cu_abbrev(ctx, &p, hdr);
|
||||
|
||||
// Now, read debug info records.
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
u64 rnglists_base = -1;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = read_scalar<E, Offset>(ctx, &p, form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(&ctx.debug_addr[0] + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (hdr.version <= 4) {
|
||||
Word<E> *p = (Word<E> *)(&ctx.debug_ranges[0] + ranges.value);
|
||||
return read_debug_range<E>(p, low_pc.value);
|
||||
}
|
||||
|
||||
assert(hdr.version == 5);
|
||||
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
u8 *buf = &ctx.debug_rnglists[0];
|
||||
|
||||
if (ranges.form == DW_FORM_sec_offset) {
|
||||
read_rnglist_range<E>(vec, buf + ranges.value, addrx, low_pc.value);
|
||||
} else {
|
||||
if (rnglists_base == -1)
|
||||
Fatal(ctx) << "--gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + rnglists_base;
|
||||
i64 num_offsets = *(U32<E> *)(base - 4);
|
||||
Offset *offsets = (Offset *)base;
|
||||
|
||||
for (i64 i = 0; i < num_offsets; i++)
|
||||
read_rnglist_range<E>(vec, base + offsets[i], addrx, low_pc.value);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {{lo, high_pc.value}};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {{lo, addrx[high_pc.value]}};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {{lo, lo + high_pc.value}};
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E, typename PubnamesHdr>
|
||||
static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
|
||||
std::vector<Compunit> &cus, ObjectFile<E> &file) {
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
auto get_cu = [&](i64 offset) {
|
||||
for (i64 i = 0; i < cus.size(); i++)
|
||||
if (cus[i].offset == offset)
|
||||
return &cus[i];
|
||||
Fatal(ctx) << file << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
Compunit *cu = get_cu(file.debug_info->offset + hdr.debug_info_offset);
|
||||
i64 size = hdr.size + offsetof(PubnamesHdr, size) + sizeof(hdr.size);
|
||||
u8 *p = (u8 *)&hdr + sizeof(hdr);
|
||||
u8 *end = (u8 *)&hdr + size;
|
||||
|
||||
while (p < end) {
|
||||
if (*(Offset *)p == 0)
|
||||
break;
|
||||
p += sizeof(Offset);
|
||||
|
||||
u8 type = *p++;
|
||||
std::string_view name = (char *)p;
|
||||
p += name.size() + 1;
|
||||
cu->nametypes.push_back({name, gdb_hash(name), type});
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
static void read_pubnames(Context<E> &ctx, std::vector<Compunit> &cus,
|
||||
ObjectFile<E> &file) {
|
||||
for (InputSection<E> *isec : { file.debug_pubnames, file.debug_pubtypes }) {
|
||||
if (!isec)
|
||||
continue;
|
||||
|
||||
isec->uncompress(ctx);
|
||||
if (isec->contents.empty())
|
||||
continue;
|
||||
|
||||
u8 *p = (u8*)&isec->contents[0];
|
||||
u8 *end = p + isec->contents.size();
|
||||
|
||||
while (p < end) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff)
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr64<E> *)p, cus, file);
|
||||
else
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr32<E> *)p, cus, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Compunit> read_compunits(Context<E> &ctx) {
|
||||
std::vector<Compunit> cus;
|
||||
|
||||
// Read compunits from the output .debug_info section.
|
||||
u8 *begin = &ctx.debug_info[0];
|
||||
u8 *end = begin + ctx.debug_info.size();
|
||||
|
||||
for (u8 *p = begin; p < end;) {
|
||||
DwarfKind kind = get_dwarf_kind(ctx, p);
|
||||
i64 size;
|
||||
if (kind == DWARF2_32 || kind == DWARF5_32)
|
||||
size = ((CuHdrDwarf2_32<E> *)p)->size + 4;
|
||||
else
|
||||
size = ((CuHdrDwarf2_64<E> *)p)->size + 12;
|
||||
|
||||
cus.push_back(Compunit{kind, p - begin, size});
|
||||
p += size;
|
||||
}
|
||||
|
||||
// Read address ranges for each compunit.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
switch (cu.kind) {
|
||||
case DWARF2_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF2_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_64<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_64<E>>(ctx, cu);
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove empty ranges
|
||||
std::erase_if(cu.ranges, [](std::pair<u64, u64> p) {
|
||||
return p.first == 0 || p.first == p.second;
|
||||
});
|
||||
});
|
||||
|
||||
// Read symbols from .debug_gnu_pubnames and .debug_gnu_pubtypes.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
read_pubnames(ctx, cus, *file);
|
||||
});
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
sort(cu.nametypes);
|
||||
remove_duplicates(cu.nametypes);
|
||||
});
|
||||
|
||||
return cus;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (chunk->is_compressed)
|
||||
return chunk->uncompressed_data;
|
||||
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_gdb_index(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_gdb_index");
|
||||
|
||||
// Find debug info sections
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
std::string_view name = chunk->name;
|
||||
if (name == ".debug_info")
|
||||
ctx.debug_info = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_abbrev")
|
||||
ctx.debug_abbrev = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_ranges")
|
||||
ctx.debug_ranges = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_addr")
|
||||
ctx.debug_addr = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_rnglists")
|
||||
ctx.debug_rnglists = get_buffer(ctx, chunk);
|
||||
}
|
||||
|
||||
if (ctx.debug_info.empty())
|
||||
return;
|
||||
|
||||
// Read debug info
|
||||
std::vector<Compunit> cus = read_compunits(ctx);
|
||||
|
||||
// Uniquify symbols
|
||||
HyperLogLog estimator;
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
HyperLogLog e;
|
||||
for (NameType &nt : cu.nametypes)
|
||||
e.insert(nt.hash);
|
||||
estimator.merge(e);
|
||||
});
|
||||
|
||||
ConcurrentMap<MapValue> map(estimator.get_cardinality() * 3 / 2);
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
cu.entries.reserve(cu.nametypes.size());
|
||||
for (NameType &nt : cu.nametypes) {
|
||||
MapValue *ent;
|
||||
bool inserted;
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash, MapValue{nt.hash});
|
||||
ent->count++;
|
||||
cu.entries.push_back(ent);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort symbols for build reproducibility
|
||||
using Entry = typename decltype(map)::Entry;
|
||||
std::vector<Entry *> entries = map.get_sorted_entries_all();
|
||||
|
||||
// Compute sizes of each components
|
||||
SectionHeader hdr;
|
||||
hdr.cu_list_offset = sizeof(hdr);
|
||||
hdr.cu_types_offset = hdr.cu_list_offset + cus.size() * 16;
|
||||
hdr.ranges_offset = hdr.cu_types_offset;
|
||||
|
||||
hdr.symtab_offset = hdr.ranges_offset;
|
||||
for (Compunit &cu : cus)
|
||||
hdr.symtab_offset += cu.ranges.size() * 20;
|
||||
|
||||
i64 ht_size = bit_ceil(entries.size() * 5 / 4 + 1);
|
||||
hdr.const_pool_offset = hdr.symtab_offset + ht_size * 8;
|
||||
|
||||
i64 offset = 0;
|
||||
for (Entry *ent : entries) {
|
||||
ent->value.type_offset = offset;
|
||||
offset += ent->value.count * 4 + 4;
|
||||
}
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
ent->value.name_offset = offset;
|
||||
offset += ent->keylen + 1;
|
||||
}
|
||||
|
||||
i64 bufsize = hdr.const_pool_offset + offset;
|
||||
|
||||
// Allocate an output buffer
|
||||
ctx.output_file->buf2.resize(bufsize);
|
||||
u8 *buf = ctx.output_file->buf2.data();
|
||||
|
||||
// Write a section header
|
||||
memcpy(buf, &hdr, sizeof(hdr));
|
||||
|
||||
// Write a CU list
|
||||
u8 *p = buf + sizeof(hdr);
|
||||
|
||||
for (Compunit &cu : cus) {
|
||||
*(ul64 *)p = cu.offset;
|
||||
*(ul64 *)(p + 8) = cu.size;
|
||||
p += 16;
|
||||
}
|
||||
|
||||
// Write address areas
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
for (std::pair<u64, u64> range : cus[i].ranges) {
|
||||
*(ul64 *)p = range.first;
|
||||
*(ul64 *)(p + 8) = range.second;
|
||||
*(ul32 *)(p + 16) = i;
|
||||
p += 20;
|
||||
}
|
||||
}
|
||||
|
||||
// Write a symbol table
|
||||
u32 mask = ht_size - 1;
|
||||
ul32 *ht = (ul32 *)(buf + hdr.symtab_offset);
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
u32 hash = ent->value.hash;
|
||||
u32 step = ((hash * 17) & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
|
||||
while (ht[j * 2] || ht[j * 2 + 1])
|
||||
j = (j + step) & mask;
|
||||
|
||||
ht[j * 2] = ent->value.name_offset;
|
||||
ht[j * 2 + 1] = ent->value.type_offset;
|
||||
}
|
||||
|
||||
// Write types
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
Compunit &cu = cus[i];
|
||||
u8 *base = buf + hdr.const_pool_offset;
|
||||
|
||||
for (i64 j = 0; j < cu.nametypes.size(); j++) {
|
||||
ul32 *p = (ul32 *)(base + cu.entries[j]->type_offset);
|
||||
i64 idx = ++p[0];
|
||||
p[idx] = (cu.nametypes[j].type << 24) | i;
|
||||
}
|
||||
}
|
||||
|
||||
// Write names
|
||||
tbb::parallel_for_each(entries, [&](Entry *ent) {
|
||||
memcpy(buf + hdr.const_pool_offset + ent->value.name_offset,
|
||||
ent->key, ent->keylen);
|
||||
});
|
||||
|
||||
// Update the section size and rewrite the section header
|
||||
if (ctx.shdr) {
|
||||
ctx.gdb_index->shdr.sh_size = bufsize;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void write_gdb_index(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+12
-14
@@ -115,21 +115,19 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
|
||||
ctx.arg.ignore_data_address_equality;
|
||||
bool is_relro = (name == ".data.rel.ro" ||
|
||||
name.starts_with(".data.rel.ro."));
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
|
||||
bool is_bss = (shdr.sh_type == SHT_NOBITS);
|
||||
bool is_empty = (shdr.sh_size == 0);
|
||||
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
|
||||
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
|
||||
bool is_enumerable = is_c_identifier(name);
|
||||
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
|
||||
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
|
||||
return false;
|
||||
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
return (ctx.arg.icf_all || !isec.address_taken) &&
|
||||
name != ".init" && name != ".fini";
|
||||
} else {
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.output_section && isec.output_section->is_relro;
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
}
|
||||
|
||||
static Digest digest_final(blake3_hasher *hasher) {
|
||||
|
||||
+81
-117
@@ -63,7 +63,7 @@ void InputFile<E>::clear_symbols() {
|
||||
sym->origin = 0;
|
||||
sym->value = -1;
|
||||
sym->sym_idx = -1;
|
||||
sym->ver_idx = 0;
|
||||
sym->ver_idx = VER_NDX_UNSPECIFIED;
|
||||
sym->is_weak = false;
|
||||
sym->is_imported = false;
|
||||
sym->is_exported = false;
|
||||
@@ -249,15 +249,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
if constexpr (E::is_rela)
|
||||
Fatal(ctx) << *this << ": REL-type relocation table is not supported"
|
||||
<< " for this target";
|
||||
break;
|
||||
case SHT_RELA:
|
||||
if constexpr (!E::is_rela)
|
||||
Fatal(ctx) << *this <<": RELA-type relocation table is not supported"
|
||||
<< " for this target";
|
||||
break;
|
||||
case SHT_SYMTAB:
|
||||
case SHT_SYMTAB_SHNDX:
|
||||
case SHT_STRTAB:
|
||||
@@ -314,7 +306,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.oformat_binary && !(shdr.sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
@@ -322,8 +314,17 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shdr.sh_type == SHT_INIT_ARRAY ||
|
||||
shdr.sh_type == SHT_FINI_ARRAY ||
|
||||
shdr.sh_type == SHT_PREINIT_ARRAY)
|
||||
ctx.has_init_array = true;
|
||||
|
||||
if (name == ".ctors" || name.starts_with(".ctors.") ||
|
||||
name == ".dtors" || name.starts_with(".dtors."))
|
||||
ctx.has_ctors = true;
|
||||
|
||||
if (name == ".eh_frame")
|
||||
eh_frame_section = this->sections[i].get();
|
||||
eh_frame_sections.push_back(this->sections[i].get());
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
@@ -335,10 +336,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = isec;
|
||||
if (name == ".debug_ranges")
|
||||
debug_ranges = isec;
|
||||
if (name == ".debug_rnglists")
|
||||
debug_rnglists = isec;
|
||||
|
||||
// If --gdb-index is given, contents of .debug_gnu_pubnames and
|
||||
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
|
||||
@@ -419,78 +416,75 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// This function parses an input .eh_frame section.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
|
||||
if (!eh_frame_section)
|
||||
return;
|
||||
for (InputSection<E> *isec : eh_frame_sections) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
|
||||
InputSection<E> &isec = *eh_frame_section;
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
// Read CIEs and FDEs until empty.
|
||||
std::string_view contents = this->get_string(ctx, isec->shdr());
|
||||
i64 rel_idx = 0;
|
||||
|
||||
// Read CIEs and FDEs until empty.
|
||||
std::string_view contents = this->get_string(ctx, isec.shdr());
|
||||
i64 rel_idx = 0;
|
||||
for (std::string_view data = contents; !data.empty();) {
|
||||
i64 size = *(U32<E> *)data.data();
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
for (std::string_view data = contents; !data.empty();) {
|
||||
i64 size = *(U32<E> *)data.data();
|
||||
if (size == 0)
|
||||
break;
|
||||
i64 begin_offset = data.data() - contents.data();
|
||||
i64 end_offset = begin_offset + size + 4;
|
||||
i64 id = *(U32<E> *)(data.data() + 4);
|
||||
data = data.substr(size + 4);
|
||||
|
||||
i64 begin_offset = data.data() - contents.data();
|
||||
i64 end_offset = begin_offset + size + 4;
|
||||
i64 id = *(U32<E> *)(data.data() + 4);
|
||||
data = data.substr(size + 4);
|
||||
i64 rel_begin = rel_idx;
|
||||
while (rel_idx < rels.size() && rels[rel_idx].r_offset < end_offset)
|
||||
rel_idx++;
|
||||
assert(rel_idx == rels.size() || begin_offset <= rels[rel_begin].r_offset);
|
||||
|
||||
i64 rel_begin = rel_idx;
|
||||
while (rel_idx < rels.size() && rels[rel_idx].r_offset < end_offset)
|
||||
rel_idx++;
|
||||
assert(rel_idx == rels.size() || begin_offset <= rels[rel_begin].r_offset);
|
||||
if (id == 0) {
|
||||
// This is CIE.
|
||||
cies.emplace_back(ctx, *this, *isec, begin_offset, rels, rel_begin);
|
||||
} else {
|
||||
// This is FDE.
|
||||
if (rel_begin == rel_idx || rels[rel_begin].r_sym == 0) {
|
||||
// FDE has no valid relocation, which means FDE is dead from
|
||||
// the beginning. Compilers usually don't create such FDE, but
|
||||
// `ld -r` tend to generate such dead FDEs.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
// This is CIE.
|
||||
cies.emplace_back(ctx, *this, isec, begin_offset, rels, rel_begin);
|
||||
} else {
|
||||
// This is FDE.
|
||||
if (rel_begin == rel_idx || rels[rel_begin].r_sym == 0) {
|
||||
// FDE has no valid relocation, which means FDE is dead from
|
||||
// the beginning. Compilers usually don't create such FDE, but
|
||||
// `ld -r` tend to generate such dead FDEs.
|
||||
continue;
|
||||
if (rels[rel_begin].r_offset - begin_offset != 8)
|
||||
Fatal(ctx) << *isec << ": FDE's first relocation should have offset 8";
|
||||
|
||||
fdes.emplace_back(begin_offset, rel_begin);
|
||||
}
|
||||
}
|
||||
|
||||
if (rels[rel_begin].r_offset - begin_offset != 8)
|
||||
Fatal(ctx) << isec << ": FDE's first relocation should have offset 8";
|
||||
// Associate CIEs to FDEs.
|
||||
auto find_cie = [&](i64 offset) {
|
||||
for (i64 i = cies_begin; i < cies.size(); i++)
|
||||
if (cies[i].input_offset == offset)
|
||||
return i;
|
||||
Fatal(ctx) << *isec << ": bad FDE pointer";
|
||||
};
|
||||
|
||||
fdes.emplace_back(begin_offset, rel_begin);
|
||||
for (i64 i = fdes_begin; i < fdes.size(); i++) {
|
||||
i64 cie_offset = *(I32<E> *)(contents.data() + fdes[i].input_offset + 4);
|
||||
fdes[i].cie_idx = find_cie(fdes[i].input_offset + 4 - cie_offset);
|
||||
}
|
||||
}
|
||||
|
||||
// Associate CIEs to FDEs.
|
||||
auto find_cie = [&](i64 offset) {
|
||||
for (i64 i = cies_begin; i < cies.size(); i++)
|
||||
if (cies[i].input_offset == offset)
|
||||
return i;
|
||||
Fatal(ctx) << isec << ": bad FDE pointer";
|
||||
};
|
||||
|
||||
for (i64 i = fdes_begin; i < fdes.size(); i++) {
|
||||
i64 cie_offset = *(I32<E> *)(contents.data() + fdes[i].input_offset + 4);
|
||||
fdes[i].cie_idx = find_cie(fdes[i].input_offset + 4 - cie_offset);
|
||||
}
|
||||
|
||||
auto get_isec = [&](const FdeRecord<E> &fde) -> InputSection<E> * {
|
||||
return get_section(this->elf_syms[rels[fde.rel_idx].r_sym]);
|
||||
auto get_isec = [&](const FdeRecord<E> &fde) {
|
||||
return get_section(this->elf_syms[fde.get_rels(*this)[0].r_sym]);
|
||||
};
|
||||
|
||||
// We assume that FDEs for the same input sections are contiguous
|
||||
// in `fdes` vector.
|
||||
std::stable_sort(fdes.begin() + fdes_begin, fdes.end(),
|
||||
[&](const FdeRecord<E> &a, const FdeRecord<E> &b) {
|
||||
sort(fdes, [&](const FdeRecord<E> &a, const FdeRecord<E> &b) {
|
||||
return get_isec(a)->get_priority() < get_isec(b)->get_priority();
|
||||
});
|
||||
|
||||
// Associate FDEs to input sections.
|
||||
for (i64 i = fdes_begin; i < fdes.size();) {
|
||||
for (i64 i = 0; i < fdes.size();) {
|
||||
InputSection<E> *isec = get_isec(fdes[i]);
|
||||
assert(isec->fde_begin == -1);
|
||||
isec->fde_begin = i++;
|
||||
@@ -649,9 +643,20 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
if (!(shdr.sh_flags & SHF_MERGE))
|
||||
return nullptr;
|
||||
|
||||
i64 entsize = shdr.sh_entsize;
|
||||
if (entsize == 0)
|
||||
entsize = (shdr.sh_flags & SHF_STRINGS) ? 1 : (int)shdr.sh_addralign;
|
||||
|
||||
if (entsize == 0)
|
||||
return nullptr;
|
||||
|
||||
i64 addralign = shdr.sh_addralign;
|
||||
if (addralign == 0)
|
||||
addralign = 1;
|
||||
|
||||
std::unique_ptr<MergeableSection<E>> rec(new MergeableSection<E>);
|
||||
rec->parent = MergedSection<E>::get_instance(ctx, sec.name(), shdr.sh_type,
|
||||
shdr.sh_flags);
|
||||
shdr.sh_flags, entsize, addralign);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
if (sec.sh_size == 0)
|
||||
@@ -662,18 +667,10 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
const char *begin = data.data();
|
||||
u64 entsize = shdr.sh_entsize;
|
||||
HyperLogLog estimator;
|
||||
|
||||
// Split sections
|
||||
if (shdr.sh_flags & SHF_STRINGS) {
|
||||
if (entsize == 0) {
|
||||
// GHC (Glasgow Haskell Compiler) sometimes creates a mergeable
|
||||
// string section with entsize of 0 instead of 1, though such
|
||||
// entsize is technically wrong. This is a workaround for the issue.
|
||||
entsize = 1;
|
||||
}
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t end = find_null(data, entsize);
|
||||
if (end == data.npos)
|
||||
@@ -690,11 +687,6 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
estimator.insert(hash);
|
||||
}
|
||||
} else {
|
||||
// OCaml compiler seems to create a mergeable non-string section with
|
||||
// entisze of 0. Such section is malformed. We do not split such section.
|
||||
if (entsize == 0)
|
||||
return nullptr;
|
||||
|
||||
if (data.size() % entsize)
|
||||
Fatal(ctx) << sec << ": section size is not multiple of sh_entsize";
|
||||
|
||||
@@ -866,33 +858,6 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
this->symbols.push_back(&sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::mark_addrsig(Context<E> &ctx) {
|
||||
// Parse a .llvm_addrsig section.
|
||||
if (llvm_addrsig) {
|
||||
u8 *cur = (u8 *)llvm_addrsig->contents.data();
|
||||
u8 *end = cur + llvm_addrsig->contents.size();
|
||||
|
||||
while (cur != end) {
|
||||
Symbol<E> &sym = *this->symbols[read_uleb(&cur)];
|
||||
if (sym.file == this)
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
isec->address_significant = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We treat a symbol's address as significant if
|
||||
//
|
||||
// 1. we have no address significance information for the symbol, or
|
||||
// 2. the symbol can be referenced from the outside in an address-
|
||||
// significant manner.
|
||||
for (Symbol<E> *sym : this->symbols)
|
||||
if (sym->file == this)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (!llvm_addrsig || sym->is_exported)
|
||||
isec->address_significant = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
sections.resize(this->elf_sections.size());
|
||||
@@ -1124,13 +1089,9 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
|
||||
std::string_view name;
|
||||
|
||||
if (sym.get_type() == STT_TLS) {
|
||||
name = ".tls_common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_TLS;
|
||||
} else {
|
||||
name = ".common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
}
|
||||
|
||||
@@ -1140,7 +1101,7 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, name, idx);
|
||||
std::make_unique<InputSection<E>>(ctx, *this, idx);
|
||||
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec.get());
|
||||
@@ -1326,17 +1287,17 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_hidden = (!vers.empty() && (vers[i] & VERSYM_HIDDEN));
|
||||
bool is_default = vers.empty() || !(vers[i] & VERSYM_HIDDEN);
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
this->versyms.push_back(ver);
|
||||
|
||||
if (is_hidden) {
|
||||
if (is_default) {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
std::string_view mangled_name = save_string(
|
||||
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
|
||||
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
|
||||
} else {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1391,6 +1352,9 @@ std::vector<std::string_view> SharedFile<E>::read_verdef(Context<E> &ctx) {
|
||||
ElfVerdef<E> *ver = (ElfVerdef<E> *)verdef.data();
|
||||
|
||||
for (;;) {
|
||||
if (ver->vd_ndx == VER_NDX_UNSPECIFIED)
|
||||
Fatal(ctx) << *this << ": symbol version too large";
|
||||
|
||||
if (ret.size() <= ver->vd_ndx)
|
||||
ret.resize(ver->vd_ndx + 1);
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
namespace mold::elf {
|
||||
|
||||
typedef enum {
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL, BASEREL, IFUNC,
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL,
|
||||
BASEREL, IFUNC_DYNREL,
|
||||
} Action;
|
||||
|
||||
template <typename E>
|
||||
@@ -37,8 +38,7 @@ static i64 to_p2align(u64 alignment) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
@@ -204,7 +204,7 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
isec.file.num_dynrel++;
|
||||
break;
|
||||
case IFUNC:
|
||||
case IFUNC_DYNREL:
|
||||
// Create an IRELATIVE relocation for a GNU ifunc symbol.
|
||||
//
|
||||
// We usually create an IRELATIVE relocation in .got for each ifunc.
|
||||
@@ -274,7 +274,7 @@ static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
template <typename E>
|
||||
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
return sym.is_pde_ifunc(ctx) ? NONE : IFUNC_DYNREL;
|
||||
|
||||
// This is a decision table for absolute relocations for the pointer
|
||||
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
|
||||
@@ -292,7 +292,7 @@ static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
template <typename E>
|
||||
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
return IFUNC_DYNREL;
|
||||
|
||||
// As a special case, we do not create copy relocations nor canonical
|
||||
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
|
||||
@@ -372,7 +372,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto emit_abs_dynrel = [&] {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, sym.get_dynsym_idx(ctx), A);
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = A;
|
||||
};
|
||||
@@ -407,7 +407,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
case DYNREL:
|
||||
emit_abs_dynrel();
|
||||
break;
|
||||
case IFUNC:
|
||||
case IFUNC_DYNREL:
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
|
||||
+17
-5
@@ -1,3 +1,14 @@
|
||||
// Many build systems attempt to invoke as many linker processes as there
|
||||
// are cores, based on the assumption that the linker is single-threaded.
|
||||
// However, since mold is multi-threaded, such build systems' behavior is
|
||||
// not beneficial and just increases the overall peak memory usage.
|
||||
// On machines with limited memory, this could lead to an out-of-memory
|
||||
// error.
|
||||
//
|
||||
// This file implements a feature that limits the number of concurrent
|
||||
// mold processes to just 1 for each user. It is intended to be used as
|
||||
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -14,14 +25,15 @@ template <typename E>
|
||||
void acquire_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || std::string(jobs) != "1")
|
||||
if (!jobs || jobs != "1"s)
|
||||
return;
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (!home)
|
||||
home = getpwuid(getuid())->pw_dir;
|
||||
std::string path;
|
||||
if (char *dir = getenv("XDG_RUNTIME_DIR"))
|
||||
path = dir + "/mold-lock"s;
|
||||
else
|
||||
path = "/tmp/mold-lock-"s + getpwuid(getuid())->pw_name;
|
||||
|
||||
std::string path = std::string(home) + "/.mold-lock";
|
||||
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
|
||||
if (fd == -1)
|
||||
return;
|
||||
|
||||
+31
-29
@@ -10,9 +10,6 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static thread_local MappedFile<Context<E>> *current_file;
|
||||
|
||||
template <typename E>
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
|
||||
|
||||
@@ -37,7 +34,7 @@ template <typename E>
|
||||
class SyntaxError {
|
||||
public:
|
||||
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
|
||||
std::string_view contents = current_file<E>->get_contents();
|
||||
std::string_view contents = ctx.script_file->get_contents();
|
||||
std::string_view line = get_line(contents, errpos.data());
|
||||
|
||||
i64 lineno = 1;
|
||||
@@ -48,7 +45,7 @@ public:
|
||||
i64 column = errpos.data() - line.data();
|
||||
|
||||
std::stringstream ss;
|
||||
ss << current_file<E>->name << ":" << lineno << ": ";
|
||||
ss << ctx.script_file->name << ":" << lineno << ": ";
|
||||
i64 indent = (i64)ss.tellp() + strlen("mold: ");
|
||||
ss << line << "\n" << std::setw(indent + column) << " " << "^ ";
|
||||
out << ss.str();
|
||||
@@ -118,7 +115,7 @@ template <typename E>
|
||||
static std::span<std::string_view>
|
||||
skip(Context<E> &ctx, std::span<std::string_view> tok, std::string_view str) {
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected '" << str
|
||||
Fatal(ctx) << ctx.script_file->name << ": expected '" << str
|
||||
<< "', but got EOF";
|
||||
if (tok[0] != str)
|
||||
SyntaxError(ctx, tok[0]) << "expected '" << str << "'";
|
||||
@@ -140,7 +137,7 @@ read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
while (!tok.empty() && tok[0] != ")")
|
||||
tok = tok.subspan(1);
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
|
||||
Fatal(ctx) << ctx.script_file->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
@@ -158,7 +155,7 @@ static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view to
|
||||
|
||||
// GNU ld prepends the sysroot if a pathname starts with '/' and the
|
||||
// script being processed is in the sysroot. We do the same.
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, current_file<E>->name))
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, ctx.script_file->name))
|
||||
return MappedFile<Context<E>>::must_open(ctx, ctx.arg.sysroot + str);
|
||||
|
||||
if (str.starts_with('=')) {
|
||||
@@ -205,13 +202,13 @@ read_group(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
}
|
||||
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
|
||||
Fatal(ctx) << ctx.script_file->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
ctx.script_file = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
@@ -241,7 +238,7 @@ void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
template <typename E>
|
||||
std::string_view
|
||||
get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
ctx.script_file = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
@@ -312,12 +309,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
|
||||
ctx.default_version_from_version_script = true;
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), ctx.script_file->name,
|
||||
ver_str, ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), ctx.script_file->name,
|
||||
ver_str, VER_NDX_LOCAL, is_cpp});
|
||||
}
|
||||
|
||||
@@ -358,7 +354,7 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
ctx.script_file = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
read_version_script(ctx, tok);
|
||||
@@ -367,7 +363,9 @@ void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
void read_dynamic_list_commands(Context<E> &ctx,
|
||||
std::vector<DynamicPattern> &result,
|
||||
std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (tok[0] == "extern") {
|
||||
@@ -376,11 +374,11 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
read_dynamic_list_commands(ctx, result, tok, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, true);
|
||||
read_dynamic_list_commands(ctx, result, tok, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
@@ -388,29 +386,32 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
"global", VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
result.push_back({unquote(tok[0]), "", is_cpp});
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path) {
|
||||
std::string_view contents =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(path))->get_contents();
|
||||
std::vector<std::string_view> vec = tokenize(ctx, contents);
|
||||
std::span<std::string_view> tok = vec;
|
||||
std::vector<DynamicPattern> result;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
read_dynamic_list_commands(ctx, result, tok, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
|
||||
if (!tok.empty())
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
|
||||
for (DynamicPattern &p : result)
|
||||
p.source = path;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
@@ -418,6 +419,7 @@ using E = MOLD_TARGET;
|
||||
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<DynamicPattern> parse_dynamic_list(Context<E> &, std::string_view);
|
||||
|
||||
|
||||
} // namespace mold::elf
|
||||
+3
-2
@@ -709,8 +709,9 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
get_symbol(ctx, y)->referenced_by_regular_obj = true;
|
||||
}
|
||||
|
||||
// Handle --entry
|
||||
get_symbol(ctx, ctx.arg.entry)->referenced_by_regular_obj = true;
|
||||
// Keep some symbols
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
sym->referenced_by_regular_obj = true;
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
|
||||
+23
-97
@@ -191,7 +191,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
if (!ctx.whole_archive)
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
@@ -298,22 +299,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--version-script: file not found: " << arg;
|
||||
parse_version_script(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
|
||||
"global", VER_NDX_GLOBAL, false});
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
@@ -338,47 +323,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
static int redo_main(int argc, char **argv, std::string_view target) {
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == LOONGARCH32::target_name)
|
||||
return elf_main<LOONGARCH32>(argc, argv);
|
||||
if (target == LOONGARCH64::target_name)
|
||||
return elf_main<LOONGARCH64>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
@@ -402,7 +346,7 @@ int elf_main(int argc, char **argv) {
|
||||
// Redo if -m is not x86-64.
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.emulation != X86_64::target_name)
|
||||
return redo_main(argc, argv, ctx.arg.emulation);
|
||||
return redo_main(ctx, argc, argv);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
@@ -496,9 +440,9 @@ int elf_main(int argc, char **argv) {
|
||||
// Set is_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(ctx);
|
||||
// Set "address-taken" bits for input sections.
|
||||
if (ctx.arg.icf || ctx.arg.z_rewrite_endbr)
|
||||
compute_address_significance(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
@@ -563,9 +507,9 @@ int elf_main(int argc, char **argv) {
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Handle --require-defined
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
if (!get_symbol(ctx, name)->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << name;
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
if (!sym->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << *sym;
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
@@ -575,6 +519,11 @@ int elf_main(int argc, char **argv) {
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// If .ctors/.dtors are to be placed to .init_array/.fini_array,
|
||||
// we need to reverse their contents.
|
||||
if (ctx.has_init_array && ctx.has_ctors)
|
||||
fixup_ctors_in_init_array(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
@@ -637,10 +586,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
@@ -686,11 +631,8 @@ int elf_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
if (ctx.arg.z_rewrite_endbr)
|
||||
rewrite_endbr(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
@@ -699,6 +641,12 @@ int elf_main(int argc, char **argv) {
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
// entire output file. Now that we wrote everything except build-id,
|
||||
// we can compute it.
|
||||
@@ -747,35 +695,13 @@ int elf_main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
|
||||
extern template int elf_main<I386>(int, char **);
|
||||
extern template int elf_main<ARM32>(int, char **);
|
||||
extern template int elf_main<ARM64>(int, char **);
|
||||
extern template int elf_main<RV32BE>(int, char **);
|
||||
extern template int elf_main<RV32LE>(int, char **);
|
||||
extern template int elf_main<RV64LE>(int, char **);
|
||||
extern template int elf_main<RV64BE>(int, char **);
|
||||
extern template int elf_main<PPC32>(int, char **);
|
||||
extern template int elf_main<PPC64V1>(int, char **);
|
||||
extern template int elf_main<PPC64V2>(int, char **);
|
||||
extern template int elf_main<S390X>(int, char **);
|
||||
extern template int elf_main<SPARC64>(int, char **);
|
||||
extern template int elf_main<M68K>(int, char **);
|
||||
extern template int elf_main<SH4>(int, char **);
|
||||
extern template int elf_main<ALPHA>(int, char **);
|
||||
extern template int elf_main<LOONGARCH32>(int, char **);
|
||||
extern template int elf_main<LOONGARCH64>(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
#else
|
||||
#endif
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -132,3 +132,14 @@ int posix_spawn(pid_t *pid, const char *path,
|
||||
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
|
||||
return real(pid, path, file_actions, attrp, argv, envp);
|
||||
}
|
||||
|
||||
int posix_spawnp(pid_t *pid, const char *file,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *attrp,
|
||||
char *const *argv, char *const *envp) {
|
||||
debug_print("posix_spawnp %s\n", file);
|
||||
if (is_ld(file))
|
||||
file = get_mold_path();
|
||||
typeof(posix_spawnp) *real = dlsym(RTLD_NEXT, "posix_spawnp");
|
||||
return real(pid, file, file_actions, attrp, argv, envp);
|
||||
}
|
||||
+96
-105
@@ -95,12 +95,12 @@ struct SymbolAux<PPC64V1> : SymbolAux<X86_64> {
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class RangeExtensionThunk {};
|
||||
class Thunk {};
|
||||
|
||||
template <needs_thunk E>
|
||||
class RangeExtensionThunk<E> {
|
||||
class Thunk<E> {
|
||||
public:
|
||||
RangeExtensionThunk(OutputSection<E> &osec, i64 offset)
|
||||
Thunk(OutputSection<E> &osec, i64 offset)
|
||||
: output_section(osec), offset(offset) {}
|
||||
|
||||
i64 size() const { return E::thunk_hdr_size + symbols.size() * E::thunk_size; }
|
||||
@@ -119,7 +119,7 @@ public:
|
||||
std::vector<Symbol<E> *> symbols;
|
||||
};
|
||||
|
||||
struct RangeExtensionRef {
|
||||
struct ThunkRef {
|
||||
i16 thunk_idx = -1;
|
||||
i16 sym_idx = -1;
|
||||
};
|
||||
@@ -212,7 +212,7 @@ struct InputSectionExtras {};
|
||||
|
||||
template <needs_thunk E>
|
||||
struct InputSectionExtras<E> {
|
||||
std::vector<RangeExtensionRef> range_extn;
|
||||
std::vector<ThunkRef> thunk_refs;
|
||||
};
|
||||
|
||||
template <is_riscv E>
|
||||
@@ -224,8 +224,7 @@ struct InputSectionExtras<E> {
|
||||
template <typename E>
|
||||
class InputSection {
|
||||
public:
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, std::string_view name,
|
||||
i64 shndx);
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx);
|
||||
|
||||
void uncompress(Context<E> &ctx);
|
||||
void uncompress_to(Context<E> &ctx, u8 *buf);
|
||||
@@ -258,17 +257,19 @@ public:
|
||||
i32 fde_begin = -1;
|
||||
i32 fde_end = -1;
|
||||
|
||||
u32 offset = -1;
|
||||
u64 offset = -1;
|
||||
u32 shndx = -1;
|
||||
u32 relsec_idx = -1;
|
||||
u32 reldyn_offset = 0;
|
||||
|
||||
bool uncompressed = false;
|
||||
|
||||
// For COMDAT de-duplication and garbage collection
|
||||
std::atomic_bool is_alive = true;
|
||||
u8 p2align = 0;
|
||||
|
||||
bool address_significant : 1 = false;
|
||||
bool uncompressed : 1 = false;
|
||||
// For ICF
|
||||
Atomic<bool> address_taken = false;
|
||||
|
||||
// For garbage collection
|
||||
Atomic<bool> is_visited = false;
|
||||
@@ -354,14 +355,15 @@ public:
|
||||
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
|
||||
// For --gdb-index
|
||||
virtual u8 *get_uncompressed_data() { return nullptr; }
|
||||
|
||||
std::string_view name;
|
||||
ElfShdr<E> shdr = { .sh_addralign = 1 };
|
||||
i64 shndx = 0;
|
||||
bool is_relro = false;
|
||||
|
||||
// For --gdb-index
|
||||
bool is_compressed = false;
|
||||
std::vector<u8> uncompressed_data;
|
||||
|
||||
// Some synethetic sections add local symbols to the output.
|
||||
// For example, range extension thunks adds function_name@thunk
|
||||
// symbol for each thunk entry. The following members are used
|
||||
@@ -460,7 +462,7 @@ public:
|
||||
void create_range_extension_thunks(Context<E> &ctx);
|
||||
|
||||
std::vector<InputSection<E> *> members;
|
||||
std::vector<std::unique_ptr<RangeExtensionThunk<E>>> thunks;
|
||||
std::vector<std::unique_ptr<Thunk<E>>> thunks;
|
||||
std::unique_ptr<RelocSection<E>> reloc_sec;
|
||||
};
|
||||
|
||||
@@ -620,6 +622,12 @@ public:
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
// Offsets in .strtab for ARM32 mapping symbols
|
||||
static constexpr i64 ARM = 1;
|
||||
static constexpr i64 THUMB = 4;
|
||||
static constexpr i64 DATA = 7;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -759,7 +767,8 @@ template <typename E>
|
||||
class MergedSection : public Chunk<E> {
|
||||
public:
|
||||
static MergedSection<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view name, u64 type, u64 flags);
|
||||
get_instance(Context<E> &ctx, std::string_view name, i64 type, i64 flags,
|
||||
i64 entsize, i64 addralign);
|
||||
|
||||
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
|
||||
u64 hash, i64 p2align);
|
||||
@@ -772,7 +781,7 @@ public:
|
||||
HyperLogLog estimator;
|
||||
|
||||
private:
|
||||
MergedSection(std::string_view name, u64 flags, u32 type);
|
||||
MergedSection(std::string_view name, i64 flags, i64 type, i64 entsize);
|
||||
|
||||
ConcurrentMap<SectionFragment<E>> map;
|
||||
std::vector<i64> shard_offsets;
|
||||
@@ -948,13 +957,6 @@ private:
|
||||
std::map<u32, u32> properties;
|
||||
};
|
||||
|
||||
struct GdbIndexName {
|
||||
std::string_view name;
|
||||
u32 hash = 0;
|
||||
u32 attr = 0;
|
||||
u32 entry_idx = 0;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class GdbIndexSection : public Chunk<E> {
|
||||
public:
|
||||
@@ -963,38 +965,6 @@ public:
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_addralign = 4;
|
||||
}
|
||||
|
||||
void construct(Context<E> &ctx);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
void write_address_areas(Context<E> &ctx);
|
||||
|
||||
private:
|
||||
struct SectionHeader {
|
||||
ul32 version = 7;
|
||||
ul32 cu_list_offset = 0;
|
||||
ul32 cu_types_offset = 0;
|
||||
ul32 areas_offset = 0;
|
||||
ul32 symtab_offset = 0;
|
||||
ul32 const_pool_offset = 0;
|
||||
};
|
||||
|
||||
struct MapEntry {
|
||||
MapEntry(ObjectFile<E> *owner, u32 hash) : owner(owner), hash(hash) {}
|
||||
|
||||
MapEntry(const MapEntry &other)
|
||||
: owner(other.owner.load()), num_attrs(other.num_attrs.load()),
|
||||
hash(other.hash), name_offset(other.name_offset),
|
||||
attr_offset(other.attr_offset) {}
|
||||
|
||||
std::atomic<ObjectFile<E> *> owner;
|
||||
std::atomic_uint32_t num_attrs = 0;
|
||||
u32 hash = 0;
|
||||
u32 name_offset = -1;
|
||||
u32 attr_offset = -1;
|
||||
};
|
||||
|
||||
SectionHeader header;
|
||||
ConcurrentMap<MapEntry> map;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -1002,12 +972,10 @@ class CompressedSection : public Chunk<E> {
|
||||
public:
|
||||
CompressedSection(Context<E> &ctx, Chunk<E> &chunk);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
u8 *get_uncompressed_data() override { return uncompressed.get(); }
|
||||
|
||||
private:
|
||||
ElfChdr<E> chdr = {};
|
||||
std::unique_ptr<Compressor> compressed;
|
||||
std::unique_ptr<u8[]> uncompressed;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -1059,22 +1027,10 @@ private:
|
||||
};
|
||||
|
||||
//
|
||||
// dwarf.cc
|
||||
// gdb-index.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset);
|
||||
template <typename E> void write_gdb_index(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// input-files.cc
|
||||
@@ -1210,7 +1166,6 @@ public:
|
||||
void mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(InputFile<E> *)> feeder) override;
|
||||
void convert_undefined_weak_symbols(Context<E> &ctx);
|
||||
void mark_addrsig(Context<E> &ctx);
|
||||
void scan_relocations(Context<E> &ctx);
|
||||
void convert_common_symbols(Context<E> &ctx);
|
||||
void compute_symtab_size(Context<E> &ctx);
|
||||
@@ -1228,7 +1183,7 @@ public:
|
||||
std::vector<FdeRecord<E>> fdes;
|
||||
BitVector has_symver;
|
||||
std::vector<ComdatGroupRef<E>> comdat_groups;
|
||||
InputSection<E> *eh_frame_section = nullptr;
|
||||
std::vector<InputSection<E> *> eh_frame_sections;
|
||||
bool exclude_libs = false;
|
||||
std::map<u32, u32> gnu_properties;
|
||||
bool is_lto_obj = false;
|
||||
@@ -1246,19 +1201,8 @@ public:
|
||||
|
||||
// For .gdb_index
|
||||
InputSection<E> *debug_info = nullptr;
|
||||
InputSection<E> *debug_ranges = nullptr;
|
||||
InputSection<E> *debug_rnglists = nullptr;
|
||||
InputSection<E> *debug_pubnames = nullptr;
|
||||
InputSection<E> *debug_pubtypes = nullptr;
|
||||
std::vector<std::string_view> compunits;
|
||||
std::vector<GdbIndexName> gdb_names;
|
||||
i64 compunits_idx = 0;
|
||||
i64 attrs_size = 0;
|
||||
i64 attrs_offset = 0;
|
||||
i64 names_size = 0;
|
||||
i64 names_offset = 0;
|
||||
i64 num_areas = 0;
|
||||
i64 area_offset = 0;
|
||||
|
||||
// For LTO
|
||||
std::vector<std::string_view> lto_symbol_versions;
|
||||
@@ -1337,8 +1281,15 @@ get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
|
||||
struct DynamicPattern {
|
||||
std::string_view pattern;
|
||||
std::string_view source;
|
||||
bool is_cpp = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path);
|
||||
|
||||
//
|
||||
// lto.cc
|
||||
@@ -1409,6 +1360,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
|
||||
// passes.cc
|
||||
//
|
||||
|
||||
template <typename E> int redo_main(Context<E> &, int argc, char **argv);
|
||||
template <typename E> void create_internal_file(Context<E> &);
|
||||
template <typename E> void apply_exclude_libs(Context<E> &);
|
||||
template <typename E> void create_synthetic_sections(Context<E> &);
|
||||
@@ -1427,6 +1379,7 @@ template <typename E> void check_duplicate_symbols(Context<E> &);
|
||||
template <typename E> void check_symbol_types(Context<E> &);
|
||||
template <typename E> void sort_init_fini(Context<E> &);
|
||||
template <typename E> void sort_ctor_dtor(Context<E> &);
|
||||
template <typename E> void fixup_ctors_in_init_array(Context<E> &);
|
||||
template <typename E> void shuffle_sections(Context<E> &);
|
||||
template <typename E> void compute_section_sizes(Context<E> &);
|
||||
template <typename E> void sort_output_sections(Context<E> &);
|
||||
@@ -1437,10 +1390,11 @@ 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> &);
|
||||
template <typename E> void copy_chunks(Context<E> &);
|
||||
template <typename E> void rewrite_endbr(Context<E> &);
|
||||
template <typename E> void apply_version_script(Context<E> &);
|
||||
template <typename E> void parse_symbol_version(Context<E> &);
|
||||
template <typename E> void compute_import_export(Context<E> &);
|
||||
template <typename E> void mark_addrsig(Context<E> &);
|
||||
template <typename E> void compute_address_significance(Context<E> &);
|
||||
template <typename E> void clear_padding(Context<E> &);
|
||||
template <typename E> void compute_section_headers(Context<E> &);
|
||||
template <typename E> i64 set_osec_offsets(Context<E> &);
|
||||
@@ -1654,7 +1608,12 @@ struct ContextExtras<ALPHA> {
|
||||
// resource management, and other miscellaneous objects.
|
||||
template <typename E>
|
||||
struct Context {
|
||||
Context() = default;
|
||||
Context() {
|
||||
arg.entry = get_symbol(*this, "_start");
|
||||
arg.fini = get_symbol(*this, "_fini");
|
||||
arg.init = get_symbol(*this, "_init");
|
||||
}
|
||||
|
||||
Context(const Context<E> &) = delete;
|
||||
|
||||
void checkpoint() {
|
||||
@@ -1671,6 +1630,9 @@ struct Context {
|
||||
CompressKind compress_debug_sections = COMPRESS_NONE;
|
||||
SeparateCodeKind z_separate_code = NOSEPARATE_CODE;
|
||||
ShuffleSectionsKind shuffle_sections = SHUFFLE_SECTIONS_NONE;
|
||||
Symbol<E> *entry = nullptr;
|
||||
Symbol<E> *fini = nullptr;
|
||||
Symbol<E> *init = nullptr;
|
||||
UnresolvedKind unresolved_symbols = UNRESOLVED_ERROR;
|
||||
bool Bsymbolic = false;
|
||||
bool Bsymbolic_functions = false;
|
||||
@@ -1697,6 +1659,7 @@ struct Context {
|
||||
bool ignore_data_address_equality = false;
|
||||
bool is_static = false;
|
||||
bool lto_pass2 = false;
|
||||
bool nmagic = false;
|
||||
bool noinhibit_exec = false;
|
||||
bool oformat_binary = false;
|
||||
bool omagic = false;
|
||||
@@ -1741,11 +1704,13 @@ struct Context {
|
||||
bool z_now = false;
|
||||
bool z_origin = false;
|
||||
bool z_relro = true;
|
||||
bool z_rewrite_endbr = false;
|
||||
bool z_sectionheader = true;
|
||||
bool z_shstk = false;
|
||||
bool z_text = false;
|
||||
i64 filler = -1;
|
||||
i64 spare_dynamic_tags = 5;
|
||||
i64 spare_program_headers = 0;
|
||||
i64 thread_count = 0;
|
||||
i64 z_stack_size = 0;
|
||||
u64 shuffle_sections_seed;
|
||||
@@ -1757,9 +1722,6 @@ struct Context {
|
||||
std::string dependency_file;
|
||||
std::string directory;
|
||||
std::string dynamic_linker;
|
||||
std::string entry = "_start";
|
||||
std::string fini = "_fini";
|
||||
std::string init = "_init";
|
||||
std::string output = "a.out";
|
||||
std::string package_metadata;
|
||||
std::string plugin;
|
||||
@@ -1772,6 +1734,8 @@ struct Context {
|
||||
std::unordered_set<std::string_view> ignore_ir_file;
|
||||
std::unordered_set<std::string_view> wrap;
|
||||
std::vector<SectionOrder> section_order;
|
||||
std::vector<Symbol<E> *> require_defined;
|
||||
std::vector<Symbol<E> *> undefined;
|
||||
std::vector<std::pair<Symbol<E> *, std::variant<Symbol<E> *, u64>>> defsyms;
|
||||
std::vector<std::string> library_paths;
|
||||
std::vector<std::string> plugin_opt;
|
||||
@@ -1779,31 +1743,30 @@ struct Context {
|
||||
std::vector<std::string_view> auxiliary;
|
||||
std::vector<std::string_view> exclude_libs;
|
||||
std::vector<std::string_view> filter;
|
||||
std::vector<std::string_view> require_defined;
|
||||
std::vector<std::string_view> trace_symbol;
|
||||
std::vector<std::string_view> undefined;
|
||||
u64 image_base = 0x200000;
|
||||
} arg;
|
||||
|
||||
std::vector<VersionPattern> version_patterns;
|
||||
u16 default_version = VER_NDX_GLOBAL;
|
||||
std::vector<DynamicPattern> dynamic_list_patterns;
|
||||
i64 default_version = VER_NDX_UNSPECIFIED;
|
||||
i64 page_size = E::page_size;
|
||||
std::optional<int> global_lock_fd;
|
||||
|
||||
// true if default_version is set by a wildcard in version script.
|
||||
bool default_version_from_version_script = false;
|
||||
|
||||
// Reader context
|
||||
bool as_needed = false;
|
||||
bool whole_archive = false;
|
||||
bool is_static;
|
||||
bool in_lib = false;
|
||||
i64 file_priority = 10000;
|
||||
MappedFile<Context<E>> *script_file = nullptr;
|
||||
std::unordered_set<std::string_view> visited;
|
||||
tbb::task_group tg;
|
||||
|
||||
bool has_error = false;
|
||||
bool has_lto_object = false;
|
||||
Atomic<bool> has_init_array = false;
|
||||
Atomic<bool> has_ctors = false;
|
||||
|
||||
// Symbol table
|
||||
tbb::concurrent_hash_map<std::string_view, Symbol<E>, HashCmp> symbol_map;
|
||||
@@ -1883,11 +1846,11 @@ struct Context {
|
||||
[[no_unique_address]] ContextExtras<E> extra;
|
||||
|
||||
// For --gdb-index
|
||||
Chunk<E> *debug_info = nullptr;
|
||||
Chunk<E> *debug_abbrev = nullptr;
|
||||
Chunk<E> *debug_ranges = nullptr;
|
||||
Chunk<E> *debug_addr = nullptr;
|
||||
Chunk<E> *debug_rnglists = nullptr;
|
||||
std::span<u8> debug_info;
|
||||
std::span<u8> debug_abbrev;
|
||||
std::span<u8> debug_ranges;
|
||||
std::span<u8> debug_addr;
|
||||
std::span<u8> debug_rnglists;
|
||||
|
||||
// For thread-local variables
|
||||
u64 tls_begin = 0;
|
||||
@@ -1995,6 +1958,7 @@ public:
|
||||
u64 get_tlsdesc_addr(Context<E> &ctx) const;
|
||||
u64 get_plt_addr(Context<E> &ctx) const;
|
||||
u64 get_opd_addr(Context<E> &ctx) const;
|
||||
u64 get_got_pltgot_addr(Context<E> &ctx) const;
|
||||
|
||||
void set_got_idx(Context<E> &ctx, i32 idx);
|
||||
void set_gottp_idx(Context<E> &ctx, i32 idx);
|
||||
@@ -2028,6 +1992,7 @@ public:
|
||||
bool is_relative() const { return !is_absolute(); }
|
||||
bool is_local(Context<E> &ctx) const;
|
||||
bool is_ifunc() const { return get_type() == STT_GNU_IFUNC; }
|
||||
bool is_pde_ifunc(Context<E> &ctx) const;
|
||||
bool is_remaining_undef_weak() const;
|
||||
|
||||
bool is_pcrel_linktime_const(Context<E> &ctx) const;
|
||||
@@ -2083,7 +2048,7 @@ public:
|
||||
i32 sym_idx = -1;
|
||||
|
||||
i32 aux_idx = -1;
|
||||
u16 ver_idx = 0;
|
||||
u16 ver_idx = VER_NDX_UNSPECIFIED;
|
||||
|
||||
// `flags` has NEEDS_ flags.
|
||||
Atomic<u8> flags = 0;
|
||||
@@ -2204,6 +2169,9 @@ public:
|
||||
// opposed to IR object).
|
||||
bool referenced_by_regular_obj : 1 = false;
|
||||
|
||||
// For `-z rewrite-endbr`
|
||||
bool address_taken : 1 = false;
|
||||
|
||||
// Target-dependent extra members.
|
||||
[[no_unique_address]] SymbolExtras<E> extra;
|
||||
};
|
||||
@@ -2351,7 +2319,7 @@ InputSection<E>::get_fragment(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
template <typename E>
|
||||
u64 InputSection<E>::get_thunk_addr(i64 idx) {
|
||||
if constexpr (needs_thunk<E>) {
|
||||
RangeExtensionRef ref = extra.range_extn[idx];
|
||||
ThunkRef ref = extra.thunk_refs[idx];
|
||||
assert(ref.thunk_idx != -1);
|
||||
return output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx);
|
||||
}
|
||||
@@ -2449,8 +2417,6 @@ InputFile<E>::get_string(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
|
||||
template <typename E>
|
||||
inline std::string_view InputFile<E>::get_string(Context<E> &ctx, i64 idx) {
|
||||
assert(idx < elf_sections.size());
|
||||
|
||||
if (elf_sections.size() <= idx)
|
||||
Fatal(ctx) << *this << ": invalid section index: " << idx;
|
||||
return this->get_string(ctx, elf_sections[idx]);
|
||||
@@ -2629,6 +2595,25 @@ inline u64 Symbol<E>::get_opd_addr(Context<E> &ctx) const {
|
||||
get_opd_idx(ctx) * PPC64OpdSection::ENTRY_SIZE;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline u64 Symbol<E>::get_got_pltgot_addr(Context<E> &ctx) const {
|
||||
// An ifunc symbol occupies two consecutive GOT slots in a
|
||||
// position-dependent executable (PDE). The first slot contains the
|
||||
// symbol's PLT address, and the second slot holds the resolved
|
||||
// address. A PDE uses the ifunc symbol's PLT entry as the address
|
||||
// for the symbol, akin to a canonical PLT.
|
||||
//
|
||||
// This function returns the address that the PLT entry should use
|
||||
// to jump to the resolved address.
|
||||
//
|
||||
// Note that we don't use this function for PPC64. In PPC64, symbols
|
||||
// are always accessed through the TOC table regardless of the
|
||||
// -fno-PIE setting. We don't need canonical PLTs on the psABIs too.
|
||||
if (is_pde_ifunc(ctx))
|
||||
return get_got_addr(ctx) + sizeof(Word<E>);
|
||||
return get_got_addr(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline void Symbol<E>::set_got_idx(Context<E> &ctx, i32 idx) {
|
||||
assert(aux_idx != -1);
|
||||
@@ -2757,6 +2742,12 @@ inline bool Symbol<E>::is_local(Context<E> &ctx) const {
|
||||
return !is_imported && !is_exported;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_pde_ifunc(Context<E> &ctx) const {
|
||||
// Returns true if this is an ifunc tha uses two GOT slots
|
||||
return is_ifunc() && !ctx.arg.pic && !is_ppc64<E>;
|
||||
}
|
||||
|
||||
// A remaining weak undefined symbol is promoted to a dynamic symbol
|
||||
// in DSO and resolved to 0 in an executable. This function returns
|
||||
// true if it's latter.
|
||||
|
||||
+288
-510
File diff suppressed because it is too large.
Load diff
+501
-191
@@ -13,6 +13,50 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
template <typename E>
|
||||
int redo_main(Context<E> &ctx, int argc, char **argv) {
|
||||
std::string_view target = ctx.arg.emulation;
|
||||
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == LOONGARCH32::target_name)
|
||||
return elf_main<LOONGARCH32>(argc, argv);
|
||||
if (target == LOONGARCH64::target_name)
|
||||
return elf_main<LOONGARCH64>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void apply_exclude_libs(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_exclude_libs");
|
||||
@@ -31,6 +75,14 @@ void apply_exclude_libs(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool has_debug_info_section(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->debug_info)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_synthetic_sections(Context<E> &ctx) {
|
||||
auto push = [&](auto *x) {
|
||||
@@ -91,7 +143,7 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.buildid = push(new BuildIdSection<E>);
|
||||
if (ctx.arg.eh_frame_hdr)
|
||||
ctx.eh_frame_hdr = push(new EhFrameHdrSection<E>);
|
||||
if (ctx.arg.gdb_index)
|
||||
if (ctx.arg.gdb_index && has_debug_info_section(ctx))
|
||||
ctx.gdb_index = push(new GdbIndexSection<E>);
|
||||
if (ctx.arg.z_relro && ctx.arg.section_order.empty() &&
|
||||
ctx.arg.z_separate_code != SEPARATE_LOADABLE_SEGMENTS)
|
||||
@@ -137,15 +189,13 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static void mark_live_objects(Context<E> &ctx) {
|
||||
auto mark_symbol = [&](std::string_view name) {
|
||||
if (InputFile<E> *file = get_symbol(ctx, name)->file)
|
||||
file->is_alive = true;
|
||||
};
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
if (sym->file)
|
||||
sym->file->is_alive = true;
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
mark_symbol(name);
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
mark_symbol(name);
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
if (sym->file)
|
||||
sym->file->is_alive = true;
|
||||
|
||||
std::vector<InputFile<E> *> roots;
|
||||
|
||||
@@ -306,8 +356,8 @@ void kill_eh_frame_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "kill_eh_frame_sections");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->eh_frame_section)
|
||||
file->eh_frame_section->is_alive = false;
|
||||
for (InputSection<E> *sec : file->eh_frame_sections)
|
||||
sec->is_alive = false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -345,7 +395,7 @@ template <typename E>
|
||||
void add_comment_string(Context<E> &ctx, std::string str) {
|
||||
MergedSection<E> *sec =
|
||||
MergedSection<E>::get_instance(ctx, ".comment", SHT_PROGBITS,
|
||||
SHF_MERGE | SHF_STRINGS);
|
||||
SHF_MERGE | SHF_STRINGS, 1, 1);
|
||||
|
||||
std::string_view buf = save_string(ctx, str);
|
||||
std::string_view data(buf.data(), buf.size() + 1);
|
||||
@@ -458,9 +508,31 @@ get_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Returns true if a given input section is a .ctors/.dtors that
|
||||
// should be put into .init_array/.fini_array.
|
||||
//
|
||||
// CRT object files contain .ctors/.dtors sections without any
|
||||
// relocations. They contain sentinel values, 0 and -1, to mark the
|
||||
// beginning and the end of the initializer/finalizer pointer arrays. We
|
||||
// do not place them into .init_array/.fini_array because such invalid
|
||||
// pointer values would simply make the program to crash.
|
||||
template <typename E>
|
||||
static bool is_ctors_in_init_array(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::string_view name = isec.name();
|
||||
return ctx.has_init_array && !isec.get_rels(ctx).empty() &&
|
||||
(name == ".ctors" || name.starts_with(".ctors.") ||
|
||||
name == ".dtors" || name.starts_with(".dtors."));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static OutputSectionKey
|
||||
get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
|
||||
if (is_ctors_in_init_array(ctx, isec)) {
|
||||
if (isec.name().starts_with(".ctors"))
|
||||
return {".init_array", SHT_INIT_ARRAY, SHF_ALLOC | SHF_WRITE};
|
||||
return {".fini_array", SHT_FINI_ARRAY, SHF_ALLOC | SHF_WRITE};
|
||||
}
|
||||
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
|
||||
u64 type = canonicalize_type<E>(name, shdr.sh_type);
|
||||
@@ -471,6 +543,7 @@ get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
|
||||
// So is .fini_array.
|
||||
if (type == SHT_INIT_ARRAY || type == SHT_FINI_ARRAY)
|
||||
flags |= SHF_WRITE;
|
||||
|
||||
return {name, type, flags};
|
||||
}
|
||||
|
||||
@@ -609,34 +682,9 @@ void create_internal_file(Context<E> &ctx) {
|
||||
ctx.internal_esyms.push_back(esym);
|
||||
};
|
||||
|
||||
auto add_undef = [&](Symbol<E> *sym) {
|
||||
obj->symbols.push_back(sym);
|
||||
sym->value = 0xdeadbeef;
|
||||
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = STV_DEFAULT;
|
||||
ctx.internal_esyms.push_back(esym);
|
||||
};
|
||||
|
||||
// Add --defsym symbols
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
std::variant<Symbol<E> *, u64> val = ctx.arg.defsyms[i].second;
|
||||
add(sym);
|
||||
|
||||
if (Symbol<E> **ref = std::get_if<Symbol<E> *>(&val)) {
|
||||
// Add an undefined symbol to keep a reference to the defsym target.
|
||||
// This prevents elimination by e.g. LTO or gc-sections.
|
||||
// The undefined symbol will never make to the final object file; we
|
||||
// double-check that the defsym target is not undefined in
|
||||
// fix_synthetic_symbols.
|
||||
add_undef(*ref);
|
||||
}
|
||||
}
|
||||
// Add --defsym'd symbols
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++)
|
||||
add(ctx.arg.defsyms[i].first);
|
||||
|
||||
// Add --section-order symbols
|
||||
for (SectionOrder &ord : ctx.arg.section_order)
|
||||
@@ -769,26 +817,27 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
|
||||
// Handle --defsym symbols.
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
Symbol<E> *sym1 = ctx.arg.defsyms[i].first;
|
||||
std::variant<Symbol<E> *, u64> val = ctx.arg.defsyms[i].second;
|
||||
|
||||
Symbol<E> *target = nullptr;
|
||||
if (Symbol<E> **ref = std::get_if<Symbol<E> *>(&val))
|
||||
target = *ref;
|
||||
if (Symbol<E> **ref = std::get_if<Symbol<E> *>(&val)) {
|
||||
Symbol<E> *sym2 = *ref;
|
||||
if (!sym2->file) {
|
||||
Error(ctx) << "--defsym: undefined symbol: " << *sym2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the alias refers another symobl, copy ELF symbol attributes.
|
||||
if (target) {
|
||||
ElfSym<E> &esym = obj.elf_syms[i + 1];
|
||||
esym.st_type = target->esym().st_type;
|
||||
esym.st_type = sym2->esym().st_type;
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
esym.ppc_local_entry = target->esym().ppc_local_entry;
|
||||
esym.ppc_local_entry = sym2->esym().ppc_local_entry;
|
||||
|
||||
if (sym2->is_absolute())
|
||||
sym1->origin = 0;
|
||||
} else {
|
||||
sym1->origin = 0;
|
||||
}
|
||||
|
||||
// Make the target absolute if necessary.
|
||||
if (!target || target->is_absolute())
|
||||
sym->origin = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -982,8 +1031,8 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
const ElfSym<E> &esym1 = sym.esym();
|
||||
const ElfSym<E> &esym2 = file->elf_syms[i];
|
||||
|
||||
u32 ty1 = (esym1.st_type == STT_GNU_IFUNC) ? STT_FUNC : esym1.st_type;
|
||||
u32 ty2 = (esym2.st_type == STT_GNU_IFUNC) ? STT_FUNC : esym2.st_type;
|
||||
u32 ty1 = (esym1.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym1.st_type;
|
||||
u32 ty2 = (esym2.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym2.st_type;
|
||||
|
||||
if (ty1 != STT_NOTYPE && ty2 != STT_NOTYPE && ty1 != ty2)
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
@@ -995,19 +1044,42 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_init_fini_priority(InputSection<E> *isec) {
|
||||
static std::regex re(R"(\.(\d+)$)", std::regex_constants::optimize);
|
||||
std::string_view name = isec->name();
|
||||
std::cmatch m;
|
||||
if (std::regex_search(name.data(), name.data() + name.size(), m, re))
|
||||
return std::stoi(m[1]);
|
||||
return 65536;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_ctor_dtor_priority(InputSection<E> *isec) {
|
||||
auto opts = std::regex_constants::optimize | std::regex_constants::ECMAScript;
|
||||
static std::regex re1(R"((?:clang_rt\.)?crtbegin)", opts);
|
||||
static std::regex re2(R"((?:clang_rt\.)?crtend)", opts);
|
||||
static std::regex re3(R"(\.(\d+)$)", opts);
|
||||
|
||||
// crtbegin.o and crtend.o contain marker symbols such as
|
||||
// __CTOR_LIST__ or __DTOR_LIST__. So they have to be at the
|
||||
// beginning or end of the section.
|
||||
std::smatch m;
|
||||
if (std::regex_search(isec->file.filename, m, re1))
|
||||
return -2;
|
||||
if (std::regex_search(isec->file.filename, m, re2))
|
||||
return 65536;
|
||||
|
||||
std::string name(isec->name());
|
||||
if (std::regex_search(name, m, re3))
|
||||
return std::stoi(m[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void sort_init_fini(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_init_fini");
|
||||
|
||||
auto get_priority = [](InputSection<E> *isec) {
|
||||
static std::regex re(R"(\.(\d+)$)", std::regex_constants::optimize);
|
||||
std::string_view name = isec->name();
|
||||
std::cmatch m;
|
||||
if (std::regex_search(name.data(), name.data() + name.size(), m, re))
|
||||
return std::stoi(m[1]);
|
||||
return 65536;
|
||||
};
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec()) {
|
||||
if (osec->name == ".init_array" || osec->name == ".preinit_array" ||
|
||||
@@ -1016,8 +1088,14 @@ void sort_init_fini(Context<E> &ctx) {
|
||||
std::reverse(osec->members.begin(), osec->members.end());
|
||||
|
||||
std::unordered_map<InputSection<E> *, i64> map;
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
map.insert({isec, get_priority(isec)});
|
||||
|
||||
for (InputSection<E> *isec : osec->members) {
|
||||
std::string_view name = isec->name();
|
||||
if (name.starts_with(".ctors") || name.starts_with(".dtors"))
|
||||
map.insert({isec, 65535 - get_ctor_dtor_priority(isec)});
|
||||
else
|
||||
map.insert({isec, get_init_fini_priority(isec)});
|
||||
}
|
||||
|
||||
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
|
||||
return map[a] < map[b];
|
||||
@@ -1031,27 +1109,6 @@ template <typename E>
|
||||
void sort_ctor_dtor(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_ctor_dtor");
|
||||
|
||||
auto get_priority = [](InputSection<E> *isec) {
|
||||
auto opts = std::regex_constants::optimize | std::regex_constants::ECMAScript;
|
||||
static std::regex re1(R"((?:clang_rt\.)?crtbegin)", opts);
|
||||
static std::regex re2(R"((?:clang_rt\.)?crtend)", opts);
|
||||
static std::regex re3(R"(\.(\d+)$)", opts);
|
||||
|
||||
// crtbegin.o and crtend.o contain marker symbols such as
|
||||
// __CTOR_LIST__ or __DTOR_LIST__. So they have to be at the
|
||||
// beginning or end of the section.
|
||||
std::smatch m;
|
||||
if (std::regex_search(isec->file.filename, m, re1))
|
||||
return -2;
|
||||
if (std::regex_search(isec->file.filename, m, re2))
|
||||
return 65536;
|
||||
|
||||
std::string name(isec->name());
|
||||
if (std::regex_search(name, m, re3))
|
||||
return std::stoi(m[1]);
|
||||
return -1;
|
||||
};
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec()) {
|
||||
if (osec->name == ".ctors" || osec->name == ".dtors") {
|
||||
@@ -1060,7 +1117,7 @@ void sort_ctor_dtor(Context<E> &ctx) {
|
||||
|
||||
std::unordered_map<InputSection<E> *, i64> map;
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
map.insert({isec, get_priority(isec)});
|
||||
map.insert({isec, get_ctor_dtor_priority(isec)});
|
||||
|
||||
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
|
||||
return map[a] < map[b];
|
||||
@@ -1070,6 +1127,45 @@ void sort_ctor_dtor(Context<E> &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
|
||||
// but in the exact opposite order to one another. Therefore, if we are to
|
||||
// place the contents of .ctors/.dtors into .init_array/.fini_array, we
|
||||
// need to reverse them.
|
||||
//
|
||||
// It's unfortunate that we have both .ctors/.dtors and
|
||||
// .init_array/.fini_array in ELF for historical reasons, but that's
|
||||
// the reality we need to deal with.
|
||||
template <typename E>
|
||||
void fixup_ctors_in_init_array(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_ctors_in_init_array");
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec()) {
|
||||
if (osec->name == ".init_array" || osec->name == ".fini_array") {
|
||||
for (InputSection<E> *isec : osec->members) {
|
||||
if (isec->name().starts_with(".ctors") ||
|
||||
isec->name().starts_with(".dtors")) {
|
||||
if (isec->sh_size % sizeof(Word<E>)) {
|
||||
Error(ctx) << *isec << ": section corrupted";
|
||||
continue;
|
||||
}
|
||||
|
||||
u8 *buf = (u8 *)isec->contents.data();
|
||||
std::reverse((Word<E> *)buf, (Word<E> *)(buf + isec->sh_size));
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
for (ElfRel<E> &r : rels)
|
||||
r.r_offset = isec->sh_size - r.r_offset - sizeof(Word<E>);
|
||||
std::reverse(rels.begin(), rels.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void shuffle(std::vector<T> &vec, u64 seed) {
|
||||
if (vec.empty())
|
||||
@@ -1198,12 +1294,15 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
// inserting thunks. This pass cannot be parallelized. That is,
|
||||
// create_range_extension_thunks is parallelized internally, but the
|
||||
// function itself is not thread-safe.
|
||||
if constexpr (needs_thunk<E>)
|
||||
if constexpr (needs_thunk<E>) {
|
||||
Timer t2(ctx, "create_range_extension_thunks");
|
||||
|
||||
if (!ctx.arg.relocatable)
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
osec->create_range_extension_thunks(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Find all unresolved symbols and attach them to the most appropriate files.
|
||||
@@ -1448,12 +1547,12 @@ void copy_chunks(Context<E> &ctx) {
|
||||
// sections first. This is because REL-type relocation sections (as
|
||||
// opposed to RELA-type) stores relocation addends to target sections.
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
if (chunk->shdr.sh_type != (E::is_rela ? SHT_RELA : SHT_REL))
|
||||
if (chunk->shdr.sh_type != SHT_REL)
|
||||
copy(*chunk);
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
if (chunk->shdr.sh_type == (E::is_rela ? SHT_RELA : SHT_REL))
|
||||
if (chunk->shdr.sh_type == SHT_REL)
|
||||
copy(*chunk);
|
||||
});
|
||||
|
||||
@@ -1467,6 +1566,54 @@ void copy_chunks(Context<E> &ctx) {
|
||||
fixup_arm_exidx_section(ctx);
|
||||
}
|
||||
|
||||
// Rewrite the leading endbr64 instruction with a nop if a function
|
||||
// symbol's address was not taken.
|
||||
template <typename E>
|
||||
void rewrite_endbr(Context<E> &ctx) {
|
||||
Timer t(ctx, "rewrite_endbr");
|
||||
assert(is_x86_64<E>);
|
||||
|
||||
// Compute address-taken bit for each symbol
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC)) {
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (!is_func_call_rel(rel) && sym.esym().st_type == STT_FUNC) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.address_taken = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Some symbols are implicitly address-taken
|
||||
ctx.arg.entry->address_taken = true;
|
||||
ctx.arg.init->address_taken = true;
|
||||
ctx.arg.fini->address_taken = true;
|
||||
|
||||
// Rewrite endbr64 with nop
|
||||
u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
|
||||
u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file && sym->esym().st_type == STT_FUNC &&
|
||||
!sym->address_taken) {
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
if (OutputSection<E> *osec = isec->output_section) {
|
||||
u8 *buf = ctx.buf + osec->shdr.sh_offset + isec->offset +
|
||||
sym->value;
|
||||
if (memcmp(buf, endbr64, 4) == 0)
|
||||
memcpy(buf, nop, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void construct_relr(Context<E> &ctx) {
|
||||
Timer t(ctx, "construct_relr");
|
||||
@@ -1480,9 +1627,11 @@ template <typename E>
|
||||
void create_output_symtab(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_symtab_size");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_symtab_size(ctx);
|
||||
});
|
||||
if (!ctx.arg.strip_all && !ctx.arg.retain_symbols_file) {
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_symtab_size(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->compute_symtab_size(ctx);
|
||||
@@ -1497,18 +1646,54 @@ template <typename E>
|
||||
void apply_version_script(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_version_script");
|
||||
|
||||
// If all patterns are simple (i.e. not containing any meta-
|
||||
// characters and is not a C++ name), we can simply look up
|
||||
// symbols.
|
||||
auto is_simple = [&] {
|
||||
for (VersionPattern &v : ctx.version_patterns)
|
||||
if (v.is_cpp || v.pattern.find_first_of("*?[") != v.pattern.npos)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
// Assign versions to symbols specified with `extern "C++"` or
|
||||
// wildcard patterns first.
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
|
||||
if (is_simple()) {
|
||||
for (VersionPattern &v : ctx.version_patterns) {
|
||||
for (i64 i = 0; i < ctx.version_patterns.size(); i++) {
|
||||
VersionPattern &v = ctx.version_patterns[i];
|
||||
if (v.is_cpp) {
|
||||
if (!cpp_matcher.add(v.pattern, i))
|
||||
Fatal(ctx) << "invalid version pattern: " << v.pattern;
|
||||
} else if (v.pattern.find_first_of("*?[") != v.pattern.npos) {
|
||||
if (!matcher.add(v.pattern, i))
|
||||
Fatal(ctx) << "invalid version pattern: " << v.pattern;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matcher.empty() || !cpp_matcher.empty()) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
|
||||
std::string_view name = sym->name();
|
||||
i64 match = INT64_MAX;
|
||||
|
||||
if (std::optional<u32> idx = matcher.find(name))
|
||||
match = std::min<i64>(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 = cpp_demangle(name))
|
||||
name = *s;
|
||||
if (std::optional<u32> idx = cpp_matcher.find(name))
|
||||
match = std::min<i64>(match, *idx);
|
||||
}
|
||||
|
||||
if (match != INT64_MAX)
|
||||
sym->ver_idx = ctx.version_patterns[match].ver_idx;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Next, assign versions to symbols specified by exact name.
|
||||
// In other words, exact matches have higher precedence over
|
||||
// wildcard or `extern "C++"` patterns.
|
||||
for (VersionPattern &v : ctx.version_patterns) {
|
||||
if (!v.is_cpp && v.pattern.find_first_of("*?[") == v.pattern.npos) {
|
||||
Symbol<E> *sym = get_symbol(ctx, v.pattern);
|
||||
|
||||
if (!sym->file && !ctx.arg.undefined_version)
|
||||
@@ -1518,48 +1703,7 @@ void apply_version_script(Context<E> &ctx) {
|
||||
if (sym->file && !sym->file->is_dso)
|
||||
sym->ver_idx = v.ver_idx;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, use glob pattern matchers.
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
|
||||
for (i64 i = 0; i < ctx.version_patterns.size(); i++) {
|
||||
VersionPattern &v = ctx.version_patterns[i];
|
||||
if (v.is_cpp) {
|
||||
if (!cpp_matcher.add(v.pattern, i))
|
||||
Fatal(ctx) << "invalid version pattern: " << v.pattern;
|
||||
} else {
|
||||
if (!matcher.add(v.pattern, i))
|
||||
Fatal(ctx) << "invalid version pattern: " << v.pattern;
|
||||
}
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
|
||||
std::string_view name = sym->name();
|
||||
i64 match = INT64_MAX;
|
||||
|
||||
if (std::optional<u32> idx = matcher.find(name))
|
||||
match = std::min<i64>(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 = cpp_demangle(name))
|
||||
name = *s;
|
||||
if (std::optional<u32> idx = cpp_matcher.find(name))
|
||||
match = std::min<i64>(match, *idx);
|
||||
}
|
||||
|
||||
if (match != INT64_MAX)
|
||||
sym->ver_idx = ctx.version_patterns[match].ver_idx;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1632,52 +1776,205 @@ void compute_import_export(Context<E> &ctx) {
|
||||
if (!ctx.arg.shared) {
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN) {
|
||||
if (sym->ver_idx != VER_NDX_LOCAL ||
|
||||
!ctx.default_version_from_version_script) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
}
|
||||
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN &&
|
||||
sym->ver_idx != VER_NDX_LOCAL) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto should_export = [&](Symbol<E> &sym) {
|
||||
if (sym.visibility == STV_HIDDEN)
|
||||
return false;
|
||||
|
||||
switch (sym.ver_idx) {
|
||||
case VER_NDX_UNSPECIFIED:
|
||||
if (ctx.arg.shared)
|
||||
return !((ObjectFile<E> *)sym.file)->exclude_libs;
|
||||
return ctx.arg.export_dynamic;
|
||||
case VER_NDX_LOCAL:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Export symbols that are not hidden or marked as local.
|
||||
// We also want to mark imported symbols as such.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (!sym->file || sym->visibility == STV_HIDDEN ||
|
||||
sym->ver_idx == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
// If we are using a symbol in a DSO, we need to import it at runtime.
|
||||
if (sym->file != file && sym->file->is_dso && !sym->is_absolute()) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_imported = true;
|
||||
// If we are using a symbol in a DSO, we need to import it.
|
||||
if (sym->file && sym->file->is_dso) {
|
||||
if (!sym->is_absolute()) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_imported = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are creating a DSO, all global symbols are exported by default.
|
||||
if (sym->file == file) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
// If we have a definition of a symbol, we may want to export it.
|
||||
if (sym->file == file && should_export(*sym)) {
|
||||
sym->is_exported = true;
|
||||
|
||||
if (ctx.arg.shared && sym->visibility != STV_PROTECTED &&
|
||||
// Exported symbols are marked as imported as well by default
|
||||
// for DSOs.
|
||||
if (ctx.arg.shared &&
|
||||
sym->visibility != STV_PROTECTED &&
|
||||
!ctx.arg.Bsymbolic &&
|
||||
!(ctx.arg.Bsymbolic_functions && sym->get_type() == STT_FUNC))
|
||||
sym->is_imported = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Apply --dynamic-list, --export-dynamic-symbol and
|
||||
// --export-dynamic-symbol-list options.
|
||||
//
|
||||
// The semantics of these options vary depending on whether we are
|
||||
// creating an executalbe or a shared object.
|
||||
//
|
||||
// For executable, matched symbols are exported.
|
||||
//
|
||||
// For shared objects, matched symbols are imported if it is already
|
||||
// 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.
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
|
||||
auto handle_match = [&](Symbol<E> *sym) {
|
||||
if (ctx.arg.shared) {
|
||||
if (sym->is_exported)
|
||||
sym->is_imported = true;
|
||||
} else {
|
||||
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN)
|
||||
sym->is_exported = true;
|
||||
}
|
||||
};
|
||||
|
||||
for (DynamicPattern &p : ctx.dynamic_list_patterns) {
|
||||
if (p.is_cpp) {
|
||||
if (!cpp_matcher.add(p.pattern, 1))
|
||||
Fatal(ctx) << p.source << ": invalid dynamic list entry: "
|
||||
<< p.pattern;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p.pattern.find_first_of("*?[") != p.pattern.npos) {
|
||||
if (!matcher.add(p.pattern, 1))
|
||||
Fatal(ctx) << p.source << ": invalid dynamic list entry: "
|
||||
<< p.pattern;
|
||||
continue;
|
||||
}
|
||||
|
||||
handle_match(get_symbol(ctx, p.pattern));
|
||||
}
|
||||
|
||||
if (!matcher.empty() || !cpp_matcher.empty()) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
if (ctx.arg.shared && !sym->is_exported)
|
||||
continue;
|
||||
|
||||
std::string_view name = sym->name();
|
||||
|
||||
if (matcher.find(name)) {
|
||||
handle_match(sym);
|
||||
} else if (!cpp_matcher.empty()) {
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
name = *s;
|
||||
if (cpp_matcher.find(name))
|
||||
handle_match(sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the "address-taken" bit for each input section.
|
||||
//
|
||||
// As a space-saving optimization, we want to merge two read-only objects
|
||||
// into a single object if their contents are equivalent. That
|
||||
// optimization is called the Identical Code Folding or ICF.
|
||||
//
|
||||
// A catch is that comparing object contents is not enough to determine if
|
||||
// two objects can be merged safely; we need to take care of pointer
|
||||
// equivalence.
|
||||
//
|
||||
// In C/C++, two pointers are equivalent if and only if they are taken for
|
||||
// the same object. Merging two objects into a single object can break
|
||||
// this assumption because two distinctive pointers would become
|
||||
// equivalent as a result of merging. We can still merge one object with
|
||||
// another if no pointer to the object was taken in code, because without
|
||||
// a pointer, comparing its address becomes moot.
|
||||
//
|
||||
// In mold, each input section has an "address-taken" bit. If there is a
|
||||
// pointer-taking reference to the object, it's set to true. At the ICF
|
||||
// stage, we merge only objects whose addresses were not taken.
|
||||
//
|
||||
// For functions, address-taking relocations are separated from
|
||||
// non-address-taking ones. For example, x86-64 uses R_X86_64_PLT32 for
|
||||
// direct function calls (e.g., "call foo" to call the function foo) while
|
||||
// R_X86_64_PC32 or R_X86_64_GOT32 are used for pointer-taking operations.
|
||||
//
|
||||
// Unfortunately, for data, we can't distinguish between address-taking
|
||||
// relocations and non-address-taking ones. LLVM generates an "address
|
||||
// significance" table in the ".llvm_addrsig" section to mark symbols
|
||||
// whose addresses are taken in code. If that table is available, we use
|
||||
// that information in this function. Otherwise, we conservatively assume
|
||||
// that all data items are address-taken.
|
||||
template <typename E>
|
||||
void mark_addrsig(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_addrsig");
|
||||
void compute_address_significance(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_address_significance");
|
||||
|
||||
// Flip address-taken bit for executable sections first.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->mark_addrsig(ctx);
|
||||
for (std::unique_ptr<InputSection<E>> &src : file->sections)
|
||||
if (src && src->is_alive && (src->shdr().sh_flags & SHF_ALLOC))
|
||||
for (const ElfRel<E> &r : src->get_rels(ctx))
|
||||
if (!is_func_call_rel(r))
|
||||
if (InputSection<E> *dst = file->symbols[r.r_sym]->get_input_section())
|
||||
if (dst->shdr().sh_flags & SHF_EXECINSTR)
|
||||
dst->address_taken = true;
|
||||
});
|
||||
|
||||
auto mark = [](Symbol<E> *sym) {
|
||||
if (sym)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
isec->address_taken = true;
|
||||
};
|
||||
|
||||
// Some symbols' pointer values are leaked to the dynamic section.
|
||||
mark(ctx.arg.entry);
|
||||
mark(ctx.arg.init);
|
||||
mark(ctx.arg.fini);
|
||||
|
||||
// Exported symbols are conservatively considered address-taken.
|
||||
if (ctx.dynsym)
|
||||
for (Symbol<E> *sym : ctx.dynsym->symbols)
|
||||
if (sym->is_exported)
|
||||
mark(sym);
|
||||
|
||||
// Handle data objects.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (InputSection<E> *sec = file->llvm_addrsig.get()) {
|
||||
u8 *p = (u8 *)sec->contents.data();
|
||||
u8 *end = p + sec->contents.size();
|
||||
while (p != end) {
|
||||
Symbol<E> *sym = file->symbols[read_uleb(&p)];
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
isec->address_taken = true;
|
||||
}
|
||||
} else {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec && !(isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
isec->address_taken = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1729,6 +2026,7 @@ void clear_padding(Context<E> &ctx) {
|
||||
// <writable non-RELRO bss>
|
||||
// <non-memory-allocated sections>
|
||||
// <section header>
|
||||
// .gdb_index
|
||||
//
|
||||
// .interp and some other linker-synthesized sections are placed at the
|
||||
// beginning of a file because they are needed by loader. Especially on
|
||||
@@ -1740,6 +2038,10 @@ void clear_padding(Context<E> &ctx) {
|
||||
// particular, if .note.gnu.build-id is in a truncated core file, you
|
||||
// can at least identify which executable has crashed.
|
||||
//
|
||||
// .gdb_index cannot be constructed before applying relocations to
|
||||
// other debug sections, so we create it after completing other part
|
||||
// of the output file and append it to the very end of the file.
|
||||
//
|
||||
// A PT_NOTE segment will contain multiple .note sections if exists,
|
||||
// but there's no way to represent a gap between .note sections.
|
||||
// Therefore, we sort .note sections by decreasing alignment
|
||||
@@ -1785,6 +2087,8 @@ void sort_output_sections_regular(Context<E> &ctx) {
|
||||
if (chunk == ctx.relplt)
|
||||
return 11;
|
||||
if (chunk == ctx.shdr)
|
||||
return INT32_MAX - 1;
|
||||
if (chunk == ctx.gdb_index)
|
||||
return INT32_MAX;
|
||||
|
||||
bool alloc = (flags & SHF_ALLOC);
|
||||
@@ -1800,23 +2104,31 @@ void sort_output_sections_regular(Context<E> &ctx) {
|
||||
|
||||
// Ties are broken by additional rules
|
||||
auto get_rank2 = [&](Chunk<E> *chunk) -> i64 {
|
||||
if (chunk->shdr.sh_type == SHT_NOTE)
|
||||
return -chunk->shdr.sh_addralign;
|
||||
ElfShdr<E> &shdr = chunk->shdr;
|
||||
if (shdr.sh_type == SHT_NOTE)
|
||||
return -shdr.sh_addralign;
|
||||
|
||||
if (chunk == ctx.got)
|
||||
return 1;
|
||||
if (chunk->name == ".toc")
|
||||
return 2;
|
||||
if (chunk->name == ".alpha_got")
|
||||
if (chunk->name == ".toc")
|
||||
return 3;
|
||||
if (chunk->name == ".alpha_got")
|
||||
return 4;
|
||||
|
||||
if (shdr.sh_flags & SHF_MERGE) {
|
||||
if (shdr.sh_flags & SHF_STRINGS)
|
||||
return (5LL << 32) | shdr.sh_entsize;
|
||||
return (6LL << 32) | shdr.sh_entsize;
|
||||
}
|
||||
|
||||
if (chunk == ctx.relro_padding)
|
||||
return INT_MAX;
|
||||
return INT64_MAX;
|
||||
return 0;
|
||||
};
|
||||
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return std::tuple{get_rank1(a), get_rank2(a)} <
|
||||
std::tuple{get_rank1(b), get_rank2(b)};
|
||||
return std::tuple{get_rank1(a), get_rank2(a), a->name} <
|
||||
std::tuple{get_rank1(b), get_rank2(b), b->name};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1988,7 +2300,7 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
|
||||
i64 flags1 = get_flags(chunks[i - 1]);
|
||||
i64 flags2 = get_flags(chunks[i]);
|
||||
|
||||
if (flags1 != flags2) {
|
||||
if (!ctx.arg.nmagic && flags1 != flags2) {
|
||||
switch (ctx.arg.z_separate_code) {
|
||||
case SEPARATE_LOADABLE_SEGMENTS:
|
||||
addr = align_to(addr, ctx.page_size);
|
||||
@@ -2188,8 +2500,9 @@ void compute_section_headers(Context<E> &ctx) {
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Remove empty chunks.
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
|
||||
std::erase_if(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk != ctx.gdb_index &&
|
||||
chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
@@ -2446,18 +2759,12 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
if (u64 *addr = std::get_if<u64>(&val)) {
|
||||
sym->origin = 0;
|
||||
sym->value = *addr;
|
||||
continue;
|
||||
} else {
|
||||
Symbol<E> *sym2 = std::get<Symbol<E> *>(val);
|
||||
sym->value = sym2->value;
|
||||
sym->origin = sym2->origin;
|
||||
sym->visibility = sym2->visibility.load();
|
||||
}
|
||||
|
||||
Symbol<E> *sym2 = std::get<Symbol<E> *>(val);
|
||||
if (!sym2->file) {
|
||||
Error(ctx) << "--defsym: undefined symbol: " << *sym2;
|
||||
continue;
|
||||
}
|
||||
|
||||
sym->value = sym2->value;
|
||||
sym->origin = sym2->origin;
|
||||
sym->visibility = sym2->visibility.load();
|
||||
}
|
||||
|
||||
|
||||
@@ -2580,7 +2887,7 @@ void show_stats(Context<E> &ctx) {
|
||||
static Counter thunk_bytes("thunk_bytes");
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
|
||||
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks)
|
||||
thunk_bytes += thunk->size();
|
||||
}
|
||||
|
||||
@@ -2592,6 +2899,7 @@ void show_stats(Context<E> &ctx) {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int redo_main(Context<E> &, int, char **);
|
||||
template void create_internal_file(Context<E> &);
|
||||
template void apply_exclude_libs(Context<E> &);
|
||||
template void create_synthetic_sections(Context<E> &);
|
||||
@@ -2609,6 +2917,7 @@ template void check_duplicate_symbols(Context<E> &);
|
||||
template void check_symbol_types(Context<E> &);
|
||||
template void sort_init_fini(Context<E> &);
|
||||
template void sort_ctor_dtor(Context<E> &);
|
||||
template void fixup_ctors_in_init_array(Context<E> &);
|
||||
template void shuffle_sections(Context<E> &);
|
||||
template void compute_section_sizes(Context<E> &);
|
||||
template void sort_output_sections(Context<E> &);
|
||||
@@ -2617,12 +2926,13 @@ template void scan_relocations(Context<E> &);
|
||||
template void report_undef_errors(Context<E> &);
|
||||
template void create_reloc_sections(Context<E> &);
|
||||
template void copy_chunks(Context<E> &);
|
||||
template void rewrite_endbr(Context<E> &);
|
||||
template void construct_relr(Context<E> &);
|
||||
template void create_output_symtab(Context<E> &);
|
||||
template void apply_version_script(Context<E> &);
|
||||
template void parse_symbol_version(Context<E> &);
|
||||
template void compute_import_export(Context<E> &);
|
||||
template void mark_addrsig(Context<E> &);
|
||||
template void compute_address_significance(Context<E> &);
|
||||
template void clear_padding(Context<E> &);
|
||||
template void compute_section_headers(Context<E> &);
|
||||
template i64 set_osec_offsets(Context<E> &);
|
||||
|
||||
+29
-50
@@ -68,27 +68,6 @@ static constexpr i64 max_thunk_size = 102400;
|
||||
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
|
||||
// Returns true if a given relocation is of type used for function calls.
|
||||
template <typename E>
|
||||
static bool needs_thunk_rel(const ElfRel<E> &r) {
|
||||
u32 ty = r.r_type;
|
||||
|
||||
if constexpr (is_arm64<E>) {
|
||||
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
|
||||
} else if constexpr (is_arm32<E>) {
|
||||
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
|
||||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL ||
|
||||
ty == R_ARM_PLT32;
|
||||
} else if constexpr (is_ppc32<E>) {
|
||||
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
|
||||
} else if constexpr (is_ppc64<E>) {
|
||||
return ty == R_PPC64_REL24 || ty == R_PPC64_REL24_NOTOC;
|
||||
} else {
|
||||
static_assert(is_loongarch<E>);
|
||||
return ty == R_LARCH_B26;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
@@ -140,7 +119,7 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
return -max_distance() <= val && val < max_distance();
|
||||
}
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
static void reset_thunk(Thunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
@@ -150,14 +129,14 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
|
||||
// Scan relocations to collect symbols that need thunks.
|
||||
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
RangeExtensionThunk<E> &thunk, i64 thunk_idx) {
|
||||
Thunk<E> &thunk, i64 thunk_idx) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
std::vector<ThunkRef> &thunk_refs = isec.extra.thunk_refs;
|
||||
thunk_refs.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
if (!is_func_call_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
@@ -172,15 +151,15 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
// This relocation needs a thunk. If the symbol is already in a
|
||||
// previous thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i].thunk_idx = sym.extra.thunk_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
thunk_refs[i].thunk_idx = sym.extra.thunk_idx;
|
||||
thunk_refs[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to the current thunk if it's not
|
||||
// added already.
|
||||
range_extn[i].thunk_idx = thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
thunk_refs[i].thunk_idx = thunk_idx;
|
||||
thunk_refs[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
@@ -195,14 +174,11 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
m[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)m.size(), [&](i64 i) {
|
||||
m[i]->offset = -1;
|
||||
});
|
||||
for (InputSection<E> *isec : m)
|
||||
isec->offset = -1;
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
@@ -211,7 +187,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// Input sections between B and C are in the current batch.
|
||||
//
|
||||
// A is the input section with the smallest address than can reach
|
||||
// anywhere from the current batch.
|
||||
// from the current batch.
|
||||
//
|
||||
// D is the input section with the largest address such that the thunk
|
||||
// is reachable from the current batch if it's inserted right before D.
|
||||
@@ -219,9 +195,9 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// ................................ <input sections> ............
|
||||
// A B C D
|
||||
// ^ We insert a thunk for the current batch just before D
|
||||
// <---> The current batch, which is smaller than batch_size
|
||||
// <--------> Smaller than max_distance
|
||||
// <--------> Smaller than max_distance
|
||||
// <---> The current batch, which is smaller than BATCH_SIZE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <-------------> Reachable from the current batch
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
@@ -233,10 +209,14 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
i64 t = 0;
|
||||
|
||||
while (b < m.size()) {
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk at D.
|
||||
// Move D foward as far as we can jump from B to a thunk at D.
|
||||
auto d_thunk_end = [&] {
|
||||
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
|
||||
return align_to(d_end, Thunk<E>::alignment) + max_thunk_size;
|
||||
};
|
||||
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance()) {
|
||||
(b == d || d_thunk_end() <= m[b]->offset + max_distance())) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
@@ -247,13 +227,12 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// to make sure that there's at least one section between B and C
|
||||
// to ensure progress.
|
||||
c = b + 1;
|
||||
while (c < m.size() &&
|
||||
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
|
||||
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < m.size() && m[a]->offset + max_distance() < c_offset)
|
||||
while (a < b && m[a]->offset + max_distance() < c_offset)
|
||||
a++;
|
||||
|
||||
// Erase references to out-of-range thunks.
|
||||
@@ -261,9 +240,9 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
|
||||
offset = align_to(offset, Thunk<E>::alignment);
|
||||
i64 thunk_idx = thunks.size();
|
||||
RangeExtensionThunk<E> *thunk = new RangeExtensionThunk<E>(*this, offset);
|
||||
Thunk<E> *thunk = new Thunk<E>(*this, offset);
|
||||
thunks.emplace_back(thunk);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
@@ -296,11 +275,11 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<Symbol<E> *> syms = isec->file.symbols;
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
|
||||
std::span<ThunkRef> thunk_refs = isec->extra.thunk_refs;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
if (thunk_refs[i].thunk_idx == thunk_idx)
|
||||
thunk_refs[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next batch.
|
||||
|
||||
@@ -20,7 +20,7 @@ ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get install -y cmake gcc g++
|
||||
apt-get install -y file
|
||||
;;
|
||||
fedora-*)
|
||||
fedora-* | amzn-*)
|
||||
dnf install -y gcc-g++ cmake
|
||||
dnf install -y glibc-static file libstdc++-static diffutils util-linux
|
||||
;;
|
||||
@@ -36,7 +36,7 @@ gentoo-*)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake
|
||||
;;
|
||||
arch-* | archarm-*)
|
||||
arch-* | archarm-* | artix-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
|
||||
+159
-62
@@ -1,84 +1,181 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" ON)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
|
||||
function(add_target TRIPLE)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
if(EXISTS "/proc/cpuinfo")
|
||||
file(READ "/proc/cpuinfo" CPUINFO)
|
||||
endif()
|
||||
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "ppc64")
|
||||
set(HOST powerpc64)
|
||||
elseif(${HOST} STREQUAL "ppc64le")
|
||||
set(HOST powerpc64le)
|
||||
endif()
|
||||
# CMAKE_HOST_SYSTEM_PROCESSOR returns "aarch64" on ARM32 userland
|
||||
# with ARM64 kernel. Here, we run `cc -dumpmachine` to determine the
|
||||
# compiler's default target.
|
||||
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
||||
RESULT_VARIABLE EXIT_CODE
|
||||
OUTPUT_VARIABLE ARCH
|
||||
ERROR_QUIET)
|
||||
if(NOT EXIT_CODE AND ARCH MATCHES "([^-]+).*")
|
||||
set(HOST ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "${HOST}-.*")
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "i386")
|
||||
set(HOST i686)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "powerpc64")
|
||||
set(HOST ppc64)
|
||||
elseif(${HOST} STREQUAL "powerpc64le")
|
||||
set(HOST ppc64le)
|
||||
endif()
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS)
|
||||
list(APPEND QEMU_ARCHS
|
||||
x86_64 i386 arm aarch64 ppc ppc64 ppc64le sparc64 sh4 s390x
|
||||
alpha riscv64 riscv32 m68k loongarch64)
|
||||
|
||||
LIST(APPEND TRIPLES
|
||||
x86_64-linux-gnu
|
||||
i686-linux-gnu
|
||||
aarch64-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
riscv64-linux-gnu
|
||||
powerpc-linux-gnu
|
||||
powerpc64-linux-gnu
|
||||
powerpc64le-linux-gnu
|
||||
sparc64-linux-gnu
|
||||
s390x-linux-gnu
|
||||
sh4-linux-gnu
|
||||
alpha-linux-gnu
|
||||
riscv32-linux-gnu
|
||||
m68k-linux-gnu
|
||||
loongarch64-linux-gnu)
|
||||
|
||||
foreach(ARCH IN LISTS QEMU_ARCHS)
|
||||
find_program(HAS_qemu-${ARCH} qemu-${ARCH})
|
||||
endforeach()
|
||||
|
||||
foreach(TRIPLE IN LISTS TRIPLES)
|
||||
find_program(HAS_${TRIPLE}-gcc ${TRIPLE}-gcc)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
function(add_target ARCH TRIPLE)
|
||||
set(CPU ${ARGV2})
|
||||
|
||||
if(${ARCH} STREQUAL ${HOST})
|
||||
set(IS_NATIVE 1)
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "([^-]+)-.")
|
||||
set(MACHINE ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
|
||||
if(IS_NATIVE OR MOLD_ENABLE_QEMU_TESTS)
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"${ARCH}_*.sh")
|
||||
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"${MACHINE}_*.sh")
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
if(CPU)
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}_${CPU}-${TEST}")
|
||||
else()
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}-${TEST}")
|
||||
endif()
|
||||
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${MACHINE}-${TEST}")
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT
|
||||
"HOST=${HOST};CPU=${CPU}")
|
||||
|
||||
if(IS_NATIVE)
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
SKIP_REGULAR_EXPRESSION "skipped")
|
||||
else()
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
ENVIRONMENT "TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
if(IS_NATIVE)
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY SKIP_REGULAR_EXPRESSION
|
||||
"skipped")
|
||||
else()
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT
|
||||
"TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
add_target(x86_64-linux-gnu)
|
||||
add_target(i686-linux-gnu)
|
||||
add_target(aarch64-linux-gnu)
|
||||
add_target(arm-linux-gnueabihf)
|
||||
add_target(riscv64-linux-gnu)
|
||||
add_target(powerpc-linux-gnu)
|
||||
add_target(powerpc64-linux-gnu)
|
||||
add_target(powerpc64le-linux-gnu)
|
||||
add_target(sparc64-linux-gnu)
|
||||
add_target(s390x-linux-gnu)
|
||||
add_target(sh4-linux-gnu)
|
||||
add_target(alpha-linux-gnu)
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_target(riscv32-linux-gnu)
|
||||
if(${HOST} STREQUAL "x86_64" OR (HAS_qemu-x86_64 AND HAS_x86_64-linux-gnu-gcc))
|
||||
add_target(x86_64 x86_64-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
|
||||
add_target(powerpc64le_power10-linux-gnu)
|
||||
if(${HOST} STREQUAL "i686" OR (HAS_qemu-i386 AND HAS_i686-linux-gnu-gcc))
|
||||
add_target(i686 i686-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_M68K "Enable tests for m68k" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_M68K)
|
||||
add_target(m68k-linux-gnu)
|
||||
if(${HOST} STREQUAL "aarch64" OR (HAS_qemu-aarch64 AND HAS_aarch64-linux-gnu-gcc))
|
||||
add_target(aarch64 aarch64-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_LOONGARCH "Enable tests for LoongArch" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH)
|
||||
add_target(loongarch64-linux-gnu)
|
||||
if(${HOST} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
|
||||
add_target(arm arm-linux-gnueabihf)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "riscv64" OR (HAS_qemu-riscv64 AND HAS_riscv64-linux-gnu-gcc))
|
||||
add_target(riscv64 riscv64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "riscv32" OR (HAS_qemu-riscv32 AND HAS_riscv32-linux-gnu-gcc))
|
||||
add_target(riscv32 riscv32-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc" OR (HAS_qemu-ppc AND HAS_powerpc-linux-gnu-gcc))
|
||||
add_target(ppc powerpc-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc64" OR (HAS_qemu-ppc64 AND HAS_powerpc64-linux-gnu-gcc))
|
||||
add_target(ppc64 powerpc64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc64le" OR (HAS_qemu-ppc64le AND HAS_powerpc64le-linux-gnu-gcc))
|
||||
add_target(ppc64le powerpc64le-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc64le" AND "${CPUINFO}" MATCHES "POWER10")
|
||||
add_target(ppc64le powerpc64le-linux-gnu power10)
|
||||
elseif(HAS_qemu-ppc64le AND HAS_powerpc64le-linux-gnu-gcc)
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/empty.c" "")
|
||||
|
||||
execute_process(
|
||||
COMMAND powerpc64le-linux-gnu-gcc -mcpu=power10 -E
|
||||
"${CMAKE_BINARY_DIR}/empty.c"
|
||||
RESULT_VARIABLE GCC_EXIT_CODE
|
||||
OUTPUT_QUIET ERROR_QUIET)
|
||||
|
||||
execute_process(
|
||||
COMMAND qemu-ppc64le -cpu help
|
||||
OUTPUT_VARIABLE QEMU_CPUS)
|
||||
|
||||
if("${GCC_EXIT_CODE}" EQUAL "0" AND "${QEMU_CPUS}" MATCHES power10_v2.0)
|
||||
add_target(ppc64le powerpc64le-linux-gnu power10)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "sparc64" OR (HAS_qemu-sparc64 AND HAS_sparc64-linux-gnu-gcc))
|
||||
add_target(sparc64 sparc64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "s390x" OR (HAS_qemu-s390x AND HAS_s390x-linux-gnu-gcc))
|
||||
add_target(s390x s390x-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "sh4" OR (HAS_qemu-sh4 AND HAS_sh4-linux-gnu-gcc))
|
||||
add_target(sh4 sh4-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "alpha" OR (HAS_qemu-alpha AND HAS_alpha-linux-gnu-gcc))
|
||||
add_target(alpha alpha-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "m68k" OR (HAS_qemu-m68k AND HAS_m68k-linux-gnu-gcc))
|
||||
add_target(m68k m68k-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "loongarch64" OR (HAS_qemu-loongarch64 AND HAS_loongarch64-linux-gnu-gcc))
|
||||
add_target(loongarch64 loongarch64-linux-gnu)
|
||||
endif()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
|
||||
.global foo
|
||||
.type foo, %function
|
||||
.variant_pcs foo
|
||||
foo:
|
||||
ret
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
readelf -W --dyn-syms $t/b.so | grep foo | grep -q '[VARIANT_PCS]'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so
|
||||
readelf -W --dynamic $t/exe | grep -q AARCH64_VARIANT_PCS
|
||||
@@ -2,6 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] || skip
|
||||
is_musl && skip
|
||||
|
||||
echo 'int main() {}' | $GCC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|
||||
|| skip
|
||||
|
||||
@@ -7,8 +7,7 @@ $CC -B. -o $t/exe $t/a.c -Wl,-build-id
|
||||
readelf -n $t/exe | grep -qv 'GNU.*0x00000010.*NT_GNU_BUILD_ID'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=uuid
|
||||
readelf -nW $t/exe |
|
||||
grep -Eq 'GNU.*0x00000010.*NT_GNU_BUILD_ID.*Build ID: ............4...[89abcdef]'
|
||||
readelf -nW $t/exe | grep -Eq 'Build ID: ............4...[89abcdef]'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=md5
|
||||
readelf -n $t/exe | grep -q 'GNU.*0x00000010.*NT_GNU_BUILD_ID'
|
||||
|
||||
+20
-7
@@ -5,7 +5,7 @@ export LC_ALL=C
|
||||
|
||||
canonical_name() {
|
||||
case $1 in
|
||||
i?86) echo i386 ;;
|
||||
i?86) echo i686 ;;
|
||||
arm*) echo arm ;;
|
||||
powerpc) echo ppc ;;
|
||||
powerpc64) echo ppc64 ;;
|
||||
@@ -14,10 +14,12 @@ canonical_name() {
|
||||
esac
|
||||
}
|
||||
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
if [ -z "$HOST" ]; then
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
fi
|
||||
|
||||
# Set tool names
|
||||
if [ "$TRIPLE" = "" ]; then
|
||||
if [ -z "$TRIPLE" ]; then
|
||||
MACHINE=$HOST
|
||||
TESTDIR=out/test/elf/$HOST
|
||||
CC="${TEST_CC:-cc}"
|
||||
@@ -28,8 +30,7 @@ if [ "$TRIPLE" = "" ]; then
|
||||
OBJCOPY=objcopy
|
||||
STRIP=strip
|
||||
QEMU=
|
||||
elif [ "$TRIPLE" = powerpc64le_power10-linux-gnu ]; then
|
||||
TRIPLE=powerpc64le-linux-gnu
|
||||
elif [ "$TRIPLE" = powerpc64le-linux-gnu -a "$CPU" = power10 ]; then
|
||||
MACHINE=ppc64le
|
||||
TESTDIR=out/test/elf/ppc64le-power10
|
||||
CC="${TEST_CC:-$TRIPLE-gcc} -mcpu=power10"
|
||||
@@ -50,10 +51,15 @@ else
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
|
||||
if [ $MACHINE = i686 ]; then
|
||||
QEMU="qemu-i386 -L /usr/$TRIPLE"
|
||||
else
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i386 -o $MACHINE = arm ]; then
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i686 -o $MACHINE = arm ]; then
|
||||
tlsdesc_opt=-mtls-dialect=gnu2
|
||||
elif [ $MACHINE = aarch64 ]; then
|
||||
tlsdesc_opt=-mtls-dialect=desc
|
||||
@@ -69,7 +75,14 @@ supports_ifunc() {
|
||||
$CC -c -o /dev/null -xc - >& /dev/null
|
||||
}
|
||||
|
||||
is_musl() {
|
||||
ldd --help 2>&1 | grep -q musl
|
||||
}
|
||||
|
||||
supports_tlsdesc() {
|
||||
# musl's tlsdesc on arm32 seems to be broken
|
||||
[ $MACHINE = arm ] && is_musl && return 1
|
||||
|
||||
[ -n "$tlsdesc_opt" ]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
@@ -32,7 +31,7 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
|
||||
cat <<EOF | $CC -B. -fPIC -shared -o $t/b.so -xc -
|
||||
__attribute__((section (".data.rel.ro"))) char readonly[100] = "abc";
|
||||
char readwrite[100] = "abc";
|
||||
EOF
|
||||
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
static void ctor1() { printf("ctor1 "); }
|
||||
static void ctor2() { printf("ctor2 "); }
|
||||
static void ctor3() { printf("ctor3 "); }
|
||||
static void ctor4() { printf("ctor4 "); }
|
||||
|
||||
static void dtor1() { printf("dtor1 "); }
|
||||
static void dtor2() { printf("dtor2 "); }
|
||||
static void dtor3() { printf("dtor3 "); }
|
||||
static void dtor4() { printf("dtor4 "); }
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors.65435")))
|
||||
void (*ctors65435[])() = { ctor1 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors.65433")))
|
||||
void (*ctors65433[])() = { ctor2 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors")))
|
||||
void (*ctors[])() = { ctor4, ctor3 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors")))
|
||||
void (*dtors[])() = { dtor1, dtor2 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors.65433")))
|
||||
void (*dtors65433[])() = { dtor3 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors.65435")))
|
||||
void (*dtors65435[])() = { dtor4 };
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((constructor(101)))
|
||||
static void init1() { printf("init1 "); }
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init2() { printf("init2 "); }
|
||||
|
||||
__attribute__((destructor(101)))
|
||||
static void fini1() { printf("fini1 "); }
|
||||
|
||||
__attribute__((destructor))
|
||||
static void fini2() { printf("fini2 "); }
|
||||
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q 'ctor1 init1 ctor2 ctor3 ctor4 init2 fini2 dtor1 dtor2 dtor3 fini1 dtor4'
|
||||
@@ -6,8 +6,8 @@ void foo() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so'
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o \
|
||||
-Wl,--soname=bar -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar'
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o -Wl,-defsym=foo=bar 2> $t/log
|
||||
grep -q 'undefined symbol: bar' $t/log
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
# musl libc does not support init/fini on ARM
|
||||
# https://github.com/rui314/mold/issues/951
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && ldd --help 2>&1 | grep -q musl && skip
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
rm -f $t/b.a
|
||||
ar rcs $t/b.a $t/a.o
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.a $t/b.a
|
||||
|
||||
! $CC -B. -o $t/exe $t/c.o -Wl,--push-state,--whole-archive \
|
||||
$t/b.a $t/b.a -Wl,--pop-state 2> $t/log || false
|
||||
|
||||
grep -q 'duplicate symbol:.* foo' $t/log
|
||||
@@ -10,8 +10,8 @@ EOF
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; bar; };
|
||||
@@ -20,17 +20,17 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
grep -q ' bar$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
grep -q ' bar' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
grep -q ' bar$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
grep -q ' bar' $t/log2
|
||||
|
||||
$CC -B. -o $t/exe3 $t/a.o -Wl,--export-dynamic-symbol=foo,--export-dynamic-symbol=bar
|
||||
|
||||
readelf --dyn-syms $t/exe3 > $t/log3
|
||||
grep -q ' foo$' $t/log3
|
||||
grep -q ' bar$' $t/log3
|
||||
grep -q ' foo' $t/log3
|
||||
grep -q ' bar' $t/log3
|
||||
@@ -14,8 +14,8 @@ EOF
|
||||
$CXX -B. -o $t/exe $t/a.o $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; extern "C++" { "baz(int)"; }; };
|
||||
@@ -24,13 +24,13 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
! grep -q ' bar$' $t/log1 || false
|
||||
grep -q ' _Z3bazi$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
! grep -q ' bar' $t/log1 || false
|
||||
grep -q ' _Z3bazi' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
! grep -q ' bar$' $t/log2 || false
|
||||
grep -q ' _Z3bazi$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
! grep -q ' bar' $t/log2 || false
|
||||
grep -q ' _Z3bazi' $t/log2
|
||||
+12
-12
@@ -21,19 +21,19 @@ EOF
|
||||
$CC -B. -Wl,--dynamic-list=$t/dyn -o $t/exe1 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' xyz$' $t/log1
|
||||
! grep -q ' foobarzx$' $t/log1 || false
|
||||
grep -q ' foobarcx$' $t/log1
|
||||
grep -q ' foo123bar456bx$' $t/log1
|
||||
! grep -q ' foo123bar456c$' $t/log1 || false
|
||||
! grep -q ' foo123bar456x$' $t/log1 || false
|
||||
grep -q ' xyz' $t/log1
|
||||
! grep -q ' foobarzx' $t/log1 || false
|
||||
grep -q ' foobarcx' $t/log1
|
||||
grep -q ' foo123bar456bx' $t/log1
|
||||
! grep -q ' foo123bar456c' $t/log1 || false
|
||||
! grep -q ' foo123bar456x' $t/log1 || false
|
||||
|
||||
$CC -B. -Wl,--export-dynamic-symbol-list=$t/dyn -o $t/exe2 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' xyz$' $t/log2
|
||||
! grep -q ' foobarzx$' $t/log2 || false
|
||||
grep -q ' foobarcx$' $t/log2
|
||||
grep -q ' foo123bar456bx$' $t/log2
|
||||
! grep -q ' foo123bar456c$' $t/log2 || false
|
||||
! grep -q ' foo123bar456x$' $t/log2 || false
|
||||
grep -q ' xyz' $t/log2
|
||||
! grep -q ' foobarzx' $t/log2 || false
|
||||
grep -q ' foobarcx' $t/log2
|
||||
grep -q ' foo123bar456bx' $t/log2
|
||||
! grep -q ' foo123bar456c' $t/log2 || false
|
||||
! grep -q ' foo123bar456x' $t/log2 || false
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
|
||||
void foo() { printf("foo1 "); }
|
||||
void bar() { printf("bar1 "); }
|
||||
void baz() { printf("baz1 "); }
|
||||
|
||||
void print() {
|
||||
foo();
|
||||
bar();
|
||||
baz();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; bar; };
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,--dynamic-list=$t/dyn
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
void foo() { printf("foo2 "); }
|
||||
void bar() { printf("bar2 "); }
|
||||
void baz() { printf("baz2 "); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/d.so $t/c.o
|
||||
|
||||
cat <<EOF | $CC -o $t/e.o -c -xc -
|
||||
#include <stdio.h>
|
||||
void print();
|
||||
int main() { print(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/e.o -Wl,-push-state,-no-as-needed $t/b.so -Wl,-pop-state
|
||||
$QEMU $t/exe1 | grep -q 'foo1 bar1 baz1'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/e.o -Wl,-push-state,-no-as-needed $t/d.so $t/b.so -Wl,-pop-state
|
||||
$QEMU $t/exe2 | grep -q 'foo2 bar2 baz1'
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -g -ffunction-sections -
|
||||
|
||||
@@ -11,5 +11,5 @@ EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar1$'
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar2$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar1'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar2'
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -xc++ -
|
||||
int foo() {
|
||||
try {
|
||||
throw 1;
|
||||
} catch (int x) {
|
||||
return x;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -o $t/b.o -c -xc++ -
|
||||
int bar() {
|
||||
try {
|
||||
throw 3;
|
||||
} catch (int x) {
|
||||
return x;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
EOF
|
||||
|
||||
$OBJCOPY --rename-section .eh_frame=.eh_frame2 $t/a.o
|
||||
./mold -r -o $t/c.o $t/a.o $t/b.o
|
||||
$OBJCOPY --rename-section .eh_frame2=.eh_frame $t/c.o
|
||||
|
||||
cat <<EOF | $CXX -o $t/d.o -c -xc++ -
|
||||
#include <stdio.h>
|
||||
|
||||
int foo();
|
||||
int bar();
|
||||
|
||||
int main() {
|
||||
printf("%d %d\n", foo(), bar());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CXX -B. -o $t/exe1 $t/d.o $t/c.o
|
||||
$QEMU $t/exe1
|
||||
$QEMU $t/exe1 | grep -q '^1 3$'
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
static=
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
test_cflags -gdwarf-5 -g -gdwarf64 || skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
void fn3();
|
||||
|
||||
static void fn2() {
|
||||
fn3();
|
||||
}
|
||||
|
||||
void fn1() {
|
||||
fn2();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/b.c
|
||||
void fn5();
|
||||
|
||||
static void fn4() {
|
||||
fn5();
|
||||
}
|
||||
|
||||
void fn3() {
|
||||
fn4();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/c.c
|
||||
void fn7();
|
||||
|
||||
static void fn6() {
|
||||
fn7();
|
||||
}
|
||||
|
||||
void fn5() {
|
||||
fn6();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/d.c
|
||||
#include <stdio.h>
|
||||
void trap() {}
|
||||
|
||||
static void fn8() {
|
||||
printf("Hello world\n");
|
||||
trap();
|
||||
}
|
||||
|
||||
void fn7() {
|
||||
fn8();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -c -o $t/a.o $t/a.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/b.o $t/b.c -fPIC -g -ggnu-pubnames -gdwarf-4 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/c.o $t/c.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64
|
||||
$CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
|
||||
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o
|
||||
|
||||
$CC -B. -shared -o $t/f.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
|
||||
readelf -WS $t/f.so 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
cat <<EOF | $CC -c -o $t/g.o -fPIC -g -ggnu-pubnames -gdwarf-5 -xc - -gz
|
||||
void fn1();
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
# Older versions of gdb are buggy that they complain DWARF64 debug sections
|
||||
# even without .gdb_index. Skip if such version.
|
||||
$CC -B. -o $t/exe1 $t/e.so $t/g.o
|
||||
DEBUGINFOD_URLS= gdb $t/exe1 -nx -batch -ex 'b main' -ex r -ex quit >& $t/log1
|
||||
grep -q 'DW_FORM_line_strp pointing outside of .debug_line_str' $t/log1 && skip
|
||||
|
||||
# We are using a recent version of gdb.
|
||||
$CC -B. -o $t/exe2 $t/f.so $t/g.o -Wl,--gdb-index
|
||||
readelf -WS $t/exe2 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
|
||||
DEBUGINFOD_URLS= gdb $t/exe2 -nx -batch -ex 'b main' -ex r -ex 'b trap' \
|
||||
-ex c -ex bt -ex quit >& $t/log2
|
||||
|
||||
grep -q 'fn8 () at .*/d.c:6' $t/log2
|
||||
grep -q 'fn7 () at .*/d.c:10' $t/log2
|
||||
grep -q 'fn6 () at .*/c.c:4' $t/log2
|
||||
grep -q 'fn5 () at .*/c.c:8' $t/log2
|
||||
grep -q 'fn4 () at .*/b.c:4' $t/log2
|
||||
grep -q 'fn3 () at .*/b.c:8' $t/log2
|
||||
grep -q 'fn2 () at .*/a.c:4' $t/log2
|
||||
grep -q 'fn1 () at .*/a.c:8' $t/log2
|
||||
@@ -20,7 +20,7 @@ GOT_ADDR=$($QEMU $t/exe)
|
||||
|
||||
# _GLOBAL_OFFSET_TABLE_ refers the end of .got only on x86.
|
||||
# We assume .got is followed by .gotplt.
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i386 ]; then
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i686 ]; then
|
||||
readelf -WS $t/exe | grep -q "\.got\.plt .*$GOT_ADDR "
|
||||
else
|
||||
readelf -WS $t/exe | grep -q "\.got .*$GOT_ADDR "
|
||||
|
||||
@@ -18,5 +18,5 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -no-pie
|
||||
$QEMU $t/exe | grep -q 'foo=5'
|
||||
@@ -16,5 +16,5 @@ int main() {
|
||||
EOF
|
||||
|
||||
# Make sure that we do not copy .gnu.warning.* sections.
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$CC -B. -o $t/exe $t/a.o -no-pie
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] || skip
|
||||
[ $MACHINE = i686 ] || skip
|
||||
|
||||
cat <<'EOF' | $CC -fPIC -o $t/a.o -c -xassembler -
|
||||
.globl get_foo
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_tlsdesc || skip
|
||||
|
||||
cat <<'EOF' | $GCC -c -o $t/a.o -xassembler -
|
||||
.globl get_foo
|
||||
.type get_foo, @function
|
||||
get_foo:
|
||||
pushl %ebx
|
||||
call __x86.get_pc_thunk.bx
|
||||
addl $_GLOBAL_OFFSET_TABLE_, %ebx
|
||||
subl $8, %esp
|
||||
leal foo@TLSDESC(%ebx), %ebx
|
||||
movl %ebx, %eax
|
||||
call *foo@TLSCALL(%eax)
|
||||
movl %gs:(%eax), %eax
|
||||
addl $8, %esp
|
||||
popl %ebx
|
||||
ret
|
||||
EOF
|
||||
|
||||
cat <<EOF | $GCC -fPIC -c -o $t/b.o -xc - $tlsdesc_opt
|
||||
#include <stdio.h>
|
||||
|
||||
_Thread_local int foo;
|
||||
|
||||
int get_foo();
|
||||
|
||||
int main() {
|
||||
foo = 42;
|
||||
printf("%d\n", get_foo());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o
|
||||
$QEMU $t/exe1 | grep -q 42
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,-no-relax
|
||||
$QEMU $t/exe2 | grep -q 42
|
||||
|
||||
$CC -B. -shared -o $t/c.so $t/a.o
|
||||
$CC -B. -o $t/exe3 $t/b.o $t/c.so
|
||||
$QEMU $t/exe3 | grep -q 42
|
||||
|
||||
$CC -B. -shared -o $t/c.so $t/a.o -Wl,-no-relax
|
||||
$CC -B. -o $t/exe4 $t/b.o $t/c.so -Wl,-no-relax
|
||||
$QEMU $t/exe4 | grep -q 42
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# GCC 11 or older uses R_390_PLT32DBL to take an address of a function
|
||||
# instead of R_390_PC32DBL
|
||||
if [ $MACHINE = s390x ]; then
|
||||
echo 'void *foo() { return foo; }' | $CC -c -o $t/a.o -xc -
|
||||
readelf -r $t/a.o | grep -q R_390_PLT32DBL && skip
|
||||
fi
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -ffunction-sections -fdata-sections -xc -
|
||||
int bar() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
int foo1(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
|
||||
int foo2(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
|
||||
int foo3(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -ffunction-sections -fdata-sections -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
int foo1();
|
||||
int foo2();
|
||||
int foo3();
|
||||
|
||||
int main() {
|
||||
printf("%d %d\n", foo1 == foo2, foo2 == foo3);
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 -Wl,-icf=safe $t/a.o $t/b.o
|
||||
$QEMU $t/exe1 | grep -q '^0 0$'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -ffunction-sections -fdata-sections -xc -
|
||||
int foo1();
|
||||
int foo2();
|
||||
int foo3();
|
||||
|
||||
int main() {
|
||||
foo1();
|
||||
foo2();
|
||||
foo3();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe2 -Wl,-icf=safe $t/a.o $t/c.o
|
||||
$QEMU $t/exe2 > $t/log2
|
||||
! grep foo2 $t/log2 || false
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
typedef void Func();
|
||||
void foo();
|
||||
Func *get_foo() { return foo; }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
__attribute__((ifunc("resolve_foo"))) void foo(void);
|
||||
void real_foo(void) { printf("foo "); }
|
||||
Func *resolve_foo() { return real_foo; }
|
||||
|
||||
Func *get_foo();
|
||||
|
||||
int main() {
|
||||
printf("%p %p\n", foo, get_foo());
|
||||
foo();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so -no-pie
|
||||
$QEMU $t/exe | grep -Eq '^(\S+) \1'
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
__attribute__((ifunc("resolve_foo"))) void foo(void);
|
||||
void real_foo(void) { printf("foo "); }
|
||||
Func *resolve_foo() { return real_foo; }
|
||||
|
||||
__attribute__((ifunc("resolve_bar"))) void bar(void);
|
||||
void real_bar(void) { printf("bar "); }
|
||||
Func *resolve_bar() { return real_bar; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
typedef void Func();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
|
||||
Func *get_foo() { return foo; }
|
||||
Func *get_bar() { return bar; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
Func *get_foo();
|
||||
Func *get_bar();
|
||||
|
||||
int main() {
|
||||
printf("%p %p %p %p\n", foo, get_foo(), bar, get_bar());
|
||||
foo();
|
||||
bar();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -no-pie
|
||||
$QEMU $t/exe1 | grep -Eq '^(\S+) \1 (\S+) \2'
|
||||
|
||||
readelf --dynamic $t/exe1 > $t/log1
|
||||
! grep -q TEXTREL $t/log1 || false
|
||||
@@ -16,5 +16,8 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$QEMU $t/exe | grep -Eq '^(\S+) \1$'
|
||||
$CC -B. -o $t/exe1 $t/a.o -pie
|
||||
$QEMU $t/exe1 | grep -Eq '^(\S+) \1$'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -no-pie
|
||||
$QEMU $t/exe2 | grep -Eq '^(\S+) \1$'
|
||||
@@ -33,5 +33,8 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o $t/c.o
|
||||
$QEMU $t/exe | grep -q '^3$'
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -pie
|
||||
$QEMU $t/exe1 | grep -q '^3$'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o $t/c.o -no-pie
|
||||
$QEMU $t/exe2 | grep -q '^3$'
|
||||
@@ -24,5 +24,8 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
$CC -B. -o $t/exe1 $t/a.o -pie
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -no-pie
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# musl does not support GNU-style init/fini priorities
|
||||
ldd --help 2>&1 | grep -q musl && skip
|
||||
is_musl && skip
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -xc++ -
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections -fPIC
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
|
||||
#include <stdio.h>
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -mcmodel=extreme
|
||||
#include <stdio.h>
|
||||
char msg[] = "Hello world\n";
|
||||
int main() { printf(msg); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
Regular → Executable
+1
-1
@@ -6,4 +6,4 @@ void _start() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe -flto $t/a.o -nostdlib
|
||||
readelf -Ws $t/exe | grep -Eq ' _start$'
|
||||
readelf -Ws $t/exe | grep -Eq ' _start'
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void _start() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -nostdlib -Wl,-nmagic
|
||||
$CC -B. -o $t/exe2 $t/a.o -nostdlib
|
||||
|
||||
end1=$(nm $t/exe1 | grep ' end$' | cut -d' ' -f1)
|
||||
end2=$(nm $t/exe2 | grep ' end$' | cut -d' ' -f1)
|
||||
|
||||
[ $((0x$end1)) -lt $((0x$end2)) ]
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = arm ] && skip
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Skip if 32 bits as we use very large addresses in this test.
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
[ $MACHINE = riscv32 ] && skip
|
||||
|
||||
# It looks like SPARC's runtime can't handle PLT if it's too far from GOT.
|
||||
[ $MACHINE = sparc64 ] && skip
|
||||
|
||||
# qemu aborts with the "Unknown exception 0x5" error, although this
|
||||
# test passes on a real POWER10 machine.
|
||||
[ -n "$QEMU" -a "$CPU" = power10 ] && skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
|
||||
#include <stdio.h>
|
||||
|
||||
void f0();
|
||||
void f1();
|
||||
void f2();
|
||||
void f3();
|
||||
void f4();
|
||||
void f5();
|
||||
void f6();
|
||||
void f7();
|
||||
void f8();
|
||||
void f9();
|
||||
void f10();
|
||||
void f11();
|
||||
void f12();
|
||||
void f13();
|
||||
void f14();
|
||||
void f15();
|
||||
void f16();
|
||||
void f17();
|
||||
void f18();
|
||||
void f19();
|
||||
|
||||
void f0(int x) { printf("0 "); if (!x) f9(); }
|
||||
void space0() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f1(int x) { printf("1 "); f8(x); }
|
||||
void space1() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f2(int x) { printf("2 "); f7(x); }
|
||||
void space2() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f3(int x) { printf("3 "); f6(x); }
|
||||
void space3() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f4(int x) { printf("4 "); f5(x); }
|
||||
void space4() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f5(int x) { printf("5 "); f10(x); }
|
||||
void space5() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f6(int x) { printf("6 "); f4(x); }
|
||||
void space6() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f7(int x) { printf("7 "); f3(x); }
|
||||
void space7() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f8(int x) { printf("8 "); f2(x); }
|
||||
void space8() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f9(int x) { printf("9 "); f1(x); }
|
||||
void space9() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f10(int x) { printf("10 "); f19(x); }
|
||||
void space10() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f11(int x) { printf("11 "); f18(x); }
|
||||
void space11() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f12(int x) { printf("12 "); f17(x); }
|
||||
void space12() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f13(int x) { printf("13 "); f16(x); }
|
||||
void space13() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f14(int x) { printf("14 "); f15(x); }
|
||||
void space14() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f15(int x) { printf("15 "); f0(x + 1); }
|
||||
void space15() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f16(int x) { printf("16 "); f14(x); }
|
||||
void space16() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f17(int x) { printf("17 "); f13(x); }
|
||||
void space17() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f18(int x) { printf("18 "); f12(x); }
|
||||
void space18() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f19(int x) { printf("19 "); f11(x); }
|
||||
void space19() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
int main() {
|
||||
f0(0);
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$QEMU $t/exe | grep -Eq '^0 9 1 8 2 7 3 6 4 5 10 19 11 18 12 17 13 16 14 15 0 $'
|
||||
@@ -4,7 +4,8 @@
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
# Ubuntu 22.04 SH4 GCC is broken
|
||||
# Ubuntu 22.04 GCC is broken
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -xc++ -
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xassembler -
|
||||
.section .rodata.str1.1,"aMS",@progbits,1
|
||||
val1:
|
||||
.ascii "Hello \0"
|
||||
|
||||
.section .rodata.str1.1,"aMS",@progbits,1
|
||||
val5:
|
||||
.ascii "World \0"
|
||||
EOF
|
||||
|
||||
./mold --relocatable -o $t/b.o $t/a.o
|
||||
|
||||
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b0\b.*Hello'
|
||||
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b7\b.*World'
|
||||
@@ -17,4 +17,4 @@ EOF
|
||||
|
||||
echo "'$t/b.o' '$t/c.o'" > $t/rsp
|
||||
|
||||
$CC -o $t/exe $t/a.o -Wl,@$t/rsp
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
echo "'$t/b.o'" > $t/rsp1
|
||||
echo "@$t/rsp1" > $t/rsp2
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp2
|
||||
+11
-11
@@ -12,23 +12,23 @@ cat <<'EOF' | $CC -c -o $t/a.o -x assembler -
|
||||
val1:
|
||||
.ascii "Hello \0"
|
||||
|
||||
.section .rodata.str4.4,"aMS",@progbits,4
|
||||
.align 4
|
||||
.section .rodata.foo,"aMS",@progbits,4
|
||||
.p2align 2
|
||||
val2:
|
||||
.ascii "world \0\0\0\0"
|
||||
|
||||
.section .rodata.foo,"aMS",@progbits,1
|
||||
.section .rodata.x,"aMS",@progbits,1
|
||||
val3:
|
||||
.ascii "foobar\0"
|
||||
|
||||
.section .rodata.cst8,"aM",@progbits,8
|
||||
.align 8
|
||||
.p2align 3
|
||||
val4:
|
||||
.ascii "abcdefgh"
|
||||
|
||||
.section .rodatabar,"aMS",@progbits,1
|
||||
.section .rodatabaz,"aMS",@progbits,1
|
||||
val5:
|
||||
.ascii "bar\0"
|
||||
.ascii "baz\0"
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/b.o -xc -
|
||||
@@ -43,8 +43,8 @@ EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
|
||||
readelf -p .rodata.str $t/exe | grep -q Hello
|
||||
readelf -p .rodata.str $t/exe | grep -q world
|
||||
readelf -p .rodata.str $t/exe | grep -q foobar
|
||||
readelf -p .rodata.cst $t/exe | grep -q abcdefgh
|
||||
readelf -p .rodatabar $t/exe | grep -q bar
|
||||
readelf -p .rodata.str1.1 $t/exe | grep -q Hello
|
||||
readelf -p .rodata.str4.4 $t/exe | grep -q world
|
||||
readelf -p .rodata.str1.1 $t/exe | grep -q foobar
|
||||
readelf -p .rodata.cst8 $t/exe | grep -q abcdefgh
|
||||
readelf -p .rodatabaz $t/exe | grep -q baz
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[ "$CC" = cc ] || skip
|
||||
|
||||
# ASAN doesn't work with LD_PRELOAD
|
||||
nm mold-wrapper.so | grep -q '__[at]san_init' && skip
|
||||
nm mold | grep -q '__[at]san_init' && skip
|
||||
|
||||
cat <<'EOF' | $CC -xc -c -o $t/a.o -
|
||||
#include <stdio.h>
|
||||
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
[ "$(readelf -Wl $t/exe1 | grep NULL | wc -l)" -eq 0 ]
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,--spare-program-headers=0
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
[ "$(readelf -Wl $t/exe2 | grep NULL | wc -l)" -eq 0 ]
|
||||
|
||||
$CC -B. -o $t/exe3 $t/a.o -Wl,--spare-program-headers=1
|
||||
$QEMU $t/exe3 | grep -q 'Hello world'
|
||||
[ "$(readelf -Wl $t/exe3 | grep NULL | wc -l)" -eq 1 ]
|
||||
|
||||
$CC -B. -o $t/exe4 $t/a.o -Wl,--spare-program-headers=5
|
||||
$QEMU $t/exe4 | grep -q 'Hello world'
|
||||
[ "$(readelf -Wl $t/exe4 | grep NULL | wc -l)" -eq 5 ]
|
||||
@@ -12,4 +12,4 @@ $CC -B. -o $t/exe $t/a.o -Wl,--strip-debug
|
||||
readelf -W --sections $t/exe > $t/log
|
||||
! grep -Fq .debug_info $t/log || false
|
||||
|
||||
readelf -W --symbols $t/exe | grep -q ' bar$'
|
||||
readelf -W --symbols $t/exe | grep -q ' bar'
|
||||
@@ -16,7 +16,7 @@ EOF
|
||||
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
|
||||
readelf -W --dyn-syms $t/c.so > $t/log
|
||||
|
||||
grep -q ' foo@TEST$' $t/log
|
||||
grep -q ' bar@TEST$' $t/log
|
||||
grep -q ' bar1$' $t/log
|
||||
! grep -q ' foo@@TEST$' $t/log || false
|
||||
grep -q ' foo@TEST' $t/log
|
||||
grep -q ' bar@TEST' $t/log
|
||||
grep -q ' bar1' $t/log
|
||||
! grep -q ' foo@@TEST' $t/log || false
|
||||
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user