mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 13:01:26 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de991b519a | ||
|
|
a066fe25c6 | ||
|
|
6e8a9dfec1 | ||
|
|
31eeb44d4f | ||
|
|
44b15cbb35 | ||
|
|
b5b3ffa6fe | ||
|
|
83a924ddac | ||
|
|
2e8bd0b7a6 | ||
|
|
b685f3cab7 | ||
|
|
2d25a93126 | ||
|
|
a8eb52f61a | ||
|
|
761c341c19 | ||
|
|
000e395c07 | ||
|
|
4b06f1e9f6 | ||
|
|
29dc0cf53f | ||
|
|
f8b55680f8 | ||
|
|
cb08466693 | ||
|
|
72671df483 | ||
|
|
83524e3690 | ||
|
|
0125d9c254 | ||
|
|
1ac5fe7c1e | ||
|
|
4b42f38257 | ||
|
|
d2b153c522 | ||
|
|
ab126ad1d9 | ||
|
|
b04aba89d3 | ||
|
|
2cb41d8a2b | ||
|
|
1b1a142a89 | ||
|
|
dcfe76bc80 | ||
|
|
d4d93d7fb7 | ||
|
|
ebd780e8ba | ||
|
|
65bd36ba40 | ||
|
|
bc86ab514f | ||
|
|
8ee7773260 | ||
|
|
e011889436 | ||
|
|
16e22ec067 | ||
|
|
d2ee47a4ac | ||
|
|
dce8ebe526 | ||
|
|
23de4be59f | ||
|
|
76e3f87775 | ||
|
|
fca6fad720 | ||
|
|
ceffcfcadd | ||
|
|
1ad19a4ce1 | ||
|
|
67a58c01d0 | ||
|
|
186ac191c4 | ||
|
|
34bf04bc41 | ||
|
|
e6db53fadb | ||
|
|
461001328b | ||
|
|
4bffe26bc0 | ||
|
|
290b4eaa46 | ||
|
|
0f1b828e6a | ||
|
|
46288758a0 | ||
|
|
e8789c71a4 | ||
|
|
f54203d76c | ||
|
|
a5634794b3 | ||
|
|
b403b811cd | ||
|
|
36fc065548 | ||
|
|
39b4548a2a | ||
|
|
52f0c4a06f | ||
|
|
c74fe12dd5 | ||
|
|
fe11565110 | ||
|
|
cca255e6be | ||
|
|
325d12d169 | ||
|
|
2a26a0db64 | ||
|
|
72c9425ae3 | ||
|
|
ff46c33d3f | ||
|
|
c59b3f5fa5 | ||
|
|
84a5509da2 | ||
|
|
305153b9c7 | ||
|
|
1adde7aa51 | ||
|
|
4fa2bc17b8 | ||
|
|
294a467116 | ||
|
|
67eb3069b3 | ||
|
|
ad2694445c | ||
|
|
6a1713e440 | ||
|
|
62d65371d7 | ||
|
|
c4722fe5ae | ||
|
|
13472be059 | ||
|
|
6d2dbd42a6 | ||
|
|
d2970e096d | ||
|
|
77c4795139 | ||
|
|
5434babdf3 | ||
|
|
530b3ebd88 | ||
|
|
33172a7b61 | ||
|
|
5065547c4e | ||
|
|
0870078ed2 | ||
|
|
992ad0fc08 | ||
|
|
5ae8ec9669 | ||
|
|
0bd5adde7e | ||
|
|
7e8a9f230c | ||
|
|
f7bc901c2d | ||
|
|
e1a7590db3 | ||
|
|
f592b6e167 | ||
|
|
8c0f455b97 | ||
|
|
0d08c52258 | ||
|
|
a42626b975 | ||
|
|
ec728d0cef | ||
|
|
20bddc75cf | ||
|
|
45bece4bd9 | ||
|
|
445a23fbc0 | ||
|
|
6952a2efa5 | ||
|
|
8c2ccd2f55 | ||
|
|
9ce963b480 | ||
|
|
35794cfc66 | ||
|
|
e300c6d9b2 | ||
|
|
8660db16a8 | ||
|
|
25b4f0b054 | ||
|
|
fc809c6c73 | ||
|
|
51845ac5fe | ||
|
|
8e50fad6b9 | ||
|
|
3e818cd350 | ||
|
|
4327d4547e | ||
|
|
abba8b5864 | ||
|
|
8ede25fa42 | ||
|
|
50d4bc04ab | ||
|
|
128f8b3b8d | ||
|
|
5140010a6e | ||
|
|
24c21d7ed2 | ||
|
|
e50590043c | ||
|
|
fdc592b169 | ||
|
|
82731d62f9 | ||
|
|
912a863b13 | ||
|
|
627bf7c8ef | ||
|
|
beddabe64d | ||
|
|
339b1b4704 | ||
|
|
8dff18db0e | ||
|
|
ebb9b6aff8 | ||
|
|
22af2ff328 | ||
|
|
d755793345 | ||
|
|
ec756333cb | ||
|
|
ce3aa9a752 | ||
|
|
e48b90f941 | ||
|
|
7f407fb1ae | ||
|
|
ac212c3d00 | ||
|
|
040180f933 | ||
|
|
7e6554677f | ||
|
|
dbeb10c0d2 | ||
|
|
c935d6387a | ||
|
|
acdd92e741 | ||
|
|
62660bc02a | ||
|
|
90d9fd06ae | ||
|
|
82d412cbff | ||
|
|
85efa3796a | ||
|
|
3f672919c2 | ||
|
|
a2e8f13ddd | ||
|
|
821fb07083 | ||
|
|
b9bd71ecfc | ||
|
|
9df4d6e479 | ||
|
|
29eb97fda5 | ||
|
|
67c6255c9a | ||
|
|
2d68cbbfd2 | ||
|
|
f98e0ef92a | ||
|
|
83303995c7 | ||
|
|
7d369dd9e8 | ||
|
|
9f66ea7026 | ||
|
|
67a96ae8e1 | ||
|
|
8936194ba7 | ||
|
|
00af084416 | ||
|
|
3829372a42 | ||
|
|
54ab1ec4b4 | ||
|
|
f4b0596b3a | ||
|
|
71f6b0bc32 | ||
|
|
fb8e63e498 | ||
|
|
1a133a4df5 | ||
|
|
60fc036bd6 | ||
|
|
3d96596485 | ||
|
|
0a8e90bd84 | ||
|
|
1e961000e6 | ||
|
|
3539487471 | ||
|
|
6caf0867b0 | ||
|
|
28a6c0e4f0 | ||
|
|
a4349df0a5 | ||
|
|
d2335707de | ||
|
|
1d2b08ade7 | ||
|
|
dd93988dc6 | ||
|
|
06ec355e88 | ||
|
|
c10f36c71c | ||
|
|
f32ce333dd | ||
|
|
d48c52cc2f | ||
|
|
25d02bb261 | ||
|
|
fda7657eab | ||
|
|
983fe0f10f | ||
|
|
8437097305 | ||
|
|
5a9341659c | ||
|
|
125af4c855 | ||
|
|
fa1db07ec8 | ||
|
|
f09bf0615b | ||
|
|
534cd392db | ||
|
|
5dee5ea49d | ||
|
|
957eaf0f18 | ||
|
|
12fad60eb4 | ||
|
|
404077e330 | ||
|
|
73947c56e1 | ||
|
|
63fae64e77 | ||
|
|
7f27b180ac | ||
|
|
6f4ba44946 | ||
|
|
dcc58e4f7a | ||
|
|
a68dfdb2d3 | ||
|
|
277dd7710a | ||
|
|
d03c9a1654 | ||
|
|
19bb456721 | ||
|
|
4e26d7b237 | ||
|
|
6a797a4fbc | ||
|
|
14784bbc4e | ||
|
|
6fd47dbab4 | ||
|
|
e31801f22f | ||
|
|
58e81bb0a0 | ||
|
|
d2cdce4410 | ||
|
|
7132822cc7 | ||
|
|
ca98fe843d | ||
|
|
bbfed9af3f | ||
|
|
707ddaa630 | ||
|
|
d127583c86 | ||
|
|
7128ee371d | ||
|
|
321158b99f | ||
|
|
d71a672089 | ||
|
|
e17d7daee8 | ||
|
|
d4e40a31c1 | ||
|
|
915cd23b2f | ||
|
|
994f800f8f | ||
|
|
2b3a684869 | ||
|
|
68b2b35148 | ||
|
|
da6dd7c0ac | ||
|
|
f2311b1efa | ||
|
|
b8bb78fde7 | ||
|
|
e4a1a57318 | ||
|
|
1ebaa27fc4 | ||
|
|
d6db850121 | ||
|
|
ebf732fca9 | ||
|
|
eba86d5312 | ||
|
|
648fcc60ca | ||
|
|
9f5b7e278f | ||
|
|
14fe1ba070 | ||
|
|
9d3cfd5c7d | ||
|
|
c2d5f602e7 | ||
|
|
ed42ce0d36 | ||
|
|
8201d0ed2a | ||
|
|
779a404950 | ||
|
|
52a0638dd2 | ||
|
|
73034f94fe | ||
|
|
ad0b6d0ac6 | ||
|
|
6e918eda31 | ||
|
|
3c968289f8 | ||
|
|
cb204decf4 | ||
|
|
f9fe3d621f | ||
|
|
b51d5baae6 | ||
|
|
747ba1d41e | ||
|
|
5c4ed9259e |
No files matched your search
@@ -26,11 +26,20 @@ jobs:
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
sudo apt-get install -y clang++-12
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ matrix.target }} ..
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++-12 ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -42,7 +51,7 @@ jobs:
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha,mips,mipsel}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf {gcc,g++}-10-{mips64,mips64el}-linux-gnuabi64
|
||||
|
||||
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
|
||||
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
|
||||
@@ -75,9 +84,9 @@ jobs:
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results
|
||||
name: test-results-gcc
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
+45
-26
@@ -44,7 +44,7 @@
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(mold VERSION 1.8.0)
|
||||
project(mold VERSION 2.0.0)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
@@ -52,7 +52,7 @@ include(GNUInstallDirs)
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
add_link_options(-fuse-ld=mold)
|
||||
add_link_options(-fuse-ld=mold -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
add_executable(mold)
|
||||
@@ -65,7 +65,16 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function)
|
||||
-Wno-unused-function
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
@@ -142,7 +151,7 @@ check_cxx_source_compiles(
|
||||
"static_assert(sizeof(void *) == 4); int main() {}" P32)
|
||||
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
|
||||
"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)
|
||||
@@ -198,12 +207,16 @@ endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR
|
||||
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
if(MINGW)
|
||||
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
|
||||
target_link_libraries(mold PRIVATE bcrypt)
|
||||
endif()
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
@@ -245,7 +258,7 @@ if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
endif()
|
||||
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
if(NOT APPLE AND NOT MSVC)
|
||||
target_compile_options(mold PRIVATE -pthread)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
@@ -255,15 +268,15 @@ add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
|
||||
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
|
||||
DEPENDS update-git-hash.cmake
|
||||
-P ${CMAKE_SOURCE_DIR}/common/update-git-hash.cmake
|
||||
DEPENDS common/update-git-hash.cmake
|
||||
BYPRODUCTS git-hash.cc
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
# Create config.h file
|
||||
configure_file(config.h.in config.h)
|
||||
configure_file(common/config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
@@ -275,8 +288,8 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE
|
||||
PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4 ALPHA)
|
||||
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
|
||||
S390X SPARC64 M68K SH4 ALPHA MIPS64LE MIPS64BE)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/cmdline.cc
|
||||
@@ -285,6 +298,7 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
elf/jobs.cc
|
||||
elf/linker-script.cc
|
||||
elf/lto.cc
|
||||
elf/main.cc
|
||||
@@ -294,6 +308,7 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/relocatable.cc
|
||||
elf/subprocess.cc
|
||||
elf/thunks.cc
|
||||
elf/tls.cc
|
||||
)
|
||||
|
||||
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
|
||||
@@ -335,13 +350,22 @@ endif()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
compress.cc
|
||||
demangle.cc
|
||||
common/compress.cc
|
||||
common/demangle.cc
|
||||
common/filepath.cc
|
||||
common/glob.cc
|
||||
common/hyperloglog.cc
|
||||
common/main.cc
|
||||
common/multi-glob.cc
|
||||
common/perf.cc
|
||||
common/tar.cc
|
||||
common/uuid.cc
|
||||
elf/arch-alpha.cc
|
||||
elf/arch-arm32.cc
|
||||
elf/arch-arm64.cc
|
||||
elf/arch-i386.cc
|
||||
elf/arch-m68k.cc
|
||||
elf/arch-mips64.cc
|
||||
elf/arch-ppc32.cc
|
||||
elf/arch-ppc64v1.cc
|
||||
elf/arch-ppc64v2.cc
|
||||
@@ -351,16 +375,8 @@ target_sources(mold PRIVATE
|
||||
elf/arch-sparc64.cc
|
||||
elf/arch-x86-64.cc
|
||||
elf/elf.cc
|
||||
filepath.cc
|
||||
git-hash.cc
|
||||
glob.cc
|
||||
hyperloglog.cc
|
||||
main.cc
|
||||
multi-glob.cc
|
||||
perf.cc
|
||||
tar.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
uuid.cc
|
||||
)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
@@ -421,9 +437,12 @@ endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE")
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
endif()
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
|
||||
@@ -436,10 +455,10 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
|
||||
endfunction()
|
||||
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "clang_cl_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# Contributing to mold
|
||||
|
||||
We request all patches to mold to be released under the dual license
|
||||
of the GNU AGPLv3 and the MIT license. Please read the following and
|
||||
sign-off your patch as an indication of agreement.
|
||||
|
||||
> ## Developer's Certificate of Origin
|
||||
>
|
||||
> By making a contribution to this project, I certify that:
|
||||
>
|
||||
> 1. The contribution was created in whole or in part by me and I
|
||||
> have the right to submit it under the Dual License of the GNU
|
||||
> AGPLv3 and the MIT license; or
|
||||
>
|
||||
> 2. The contribution is based upon previous work that, to the best
|
||||
> of my knowledge, is covered under the the Dual License and I
|
||||
> have the right under the Dual License to submit that work with
|
||||
> modifications, whether created in whole or in part by me, under
|
||||
> the Dual License (unless I am permitted to submit under
|
||||
> different licenses), as indicated in the file; or
|
||||
>
|
||||
> 3. The contribution was provided directly to me by some other
|
||||
> person who certified (1), (2) or (3) and I have not modified it.
|
||||
>
|
||||
> 4. I understand and agree that this project and the contribution
|
||||
> are public and that a record of the contribution (including all
|
||||
> personal information I submit with it, including my sign-off) is
|
||||
> maintained indefinitely and may be redistributed consistent with
|
||||
> this project or the open source license(s) involved.
|
||||
|
||||
If you agree, you just add a line saying:
|
||||
|
||||
```
|
||||
Signed-off-by: Random J Developer <[email protected]>
|
||||
```
|
||||
|
||||
using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
This will be done for you automatically if you use ``git commit -s``.
|
||||
Reverts should also include "Signed-off-by". ``git revert -s`` does that
|
||||
for you.
|
||||
|
||||
We assume that any contribution that's 10 lines or less does not meet
|
||||
the threshold of originality and therefore copyright does not apply.
|
||||
You don't need to add a "Signed-off-by" line to such small patches.
|
||||
@@ -0,0 +1,348 @@
|
||||
Mold includes a number of subcomponents with separate copyright notices
|
||||
and license terms. Your use of the source code for the these subcomponents
|
||||
is subject to the terms and conditions of the following licenses.
|
||||
|
||||
|
||||
=== For the third-party/mimalloc component:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
=== For the third-party/rust-demangle component:
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
=== For the third-party/tbb component:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
=== For the third-party/xxhash component:
|
||||
|
||||
xxHash Library
|
||||
Copyright (c) 2012-2020 Yann Collet
|
||||
All rights reserved.
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
=== For the third-party/zlib component:
|
||||
|
||||
(C) 1995-2022 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
[email protected] [email protected]
|
||||
|
||||
|
||||
=== For the third-party/zstd component:
|
||||
|
||||
BSD License
|
||||
|
||||
For Zstandard software
|
||||
|
||||
Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -3,20 +3,20 @@
|
||||
[](https://github.com/rui314/mold/actions/workflows/ci.yml)
|
||||
[](https://build.opensuse.org/package/show/home:marxin:mold/mold)
|
||||
|
||||
<i>This is a repo of a free, AGPL-licensed version of the linker.
|
||||
If you are looking for a commercial, non-AGPL version of the same linker,
|
||||
<i>This repository contains a free version of the mold linker.
|
||||
If you are looking for a commercial version that supports macOS
|
||||
please visit the
|
||||
[repo of the sold linker](https://github.com/bluewhalesystems/sold).</i>
|
||||
[repository of the sold linker](https://github.com/bluewhalesystems/sold).</i>
|
||||
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is designed to increase developer productivity by reducing
|
||||
build time, especially in rapid debug-edit-rebuild cycles.
|
||||
mold is a faster drop-in replacement for existing Unix linkers. It is several
|
||||
times quicker than the LLVM lld linker, the second-fastest open-source linker,
|
||||
which I initially developed a few years ago. mold aims to enhance developer
|
||||
productivity by minimizing build time, particularly in rapid
|
||||
debug-edit-rebuild cycles.
|
||||
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold for
|
||||
linking final debuginfo-enabled executables of major large programs
|
||||
on a simulated 8-core 16-threads machine.
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold when linking
|
||||
final debuginfo-enabled executables for major large programs on a simulated
|
||||
8-core, 16-thread machine.
|
||||
|
||||

|
||||
|
||||
@@ -26,48 +26,46 @@ on a simulated 8-core 16-threads machine.
|
||||
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
|
||||
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
|
||||
|
||||
mold is so fast that it is only 2x _slower_ than `cp` on the same
|
||||
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
|
||||
if you find mold is not faster than other linkers.
|
||||
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
|
||||
machine. If you find that mold is not faster than other linkers, please feel
|
||||
free to [file a bug report](https://github.com/rui314/mold/issues).
|
||||
|
||||
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
|
||||
RISC-V, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian PowerPC ELFv2,
|
||||
s390x, SPARC64 and m68k.
|
||||
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
|
||||
PowerPC ELFv2, s390x, SPARC64, m68k, SH-4, and DEC Alpha.
|
||||
|
||||
mold/macOS is commercial software. For mold/macOS, please visit
|
||||
https://github.com/bluewhalesystems/sold.
|
||||
|
||||
## Why does the speed of linking matter?
|
||||
## Why does linking speed matter?
|
||||
|
||||
If you are using a compiled language such as C, C++ or Rust, a build
|
||||
consists of two phases. In the first phase, a compiler compiles
|
||||
source files into object files (`.o` files). In the second phase,
|
||||
a linker takes all object files to combine them into a single executable
|
||||
or a shared library file.
|
||||
If you are using a compiled language such as C, C++, or Rust, a build consists
|
||||
of two phases. In the first phase, a compiler compiles source files into
|
||||
object files (`.o` files). In the second phase, a linker takes all object
|
||||
files and combines them into a single executable or shared library file.
|
||||
|
||||
The second phase takes a long time if your build output is large.
|
||||
mold can make it faster, saving your time and keeping you from being
|
||||
distracted while waiting for a long build to finish. The difference is
|
||||
most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
The second phase can be time-consuming if your build output is large. mold can
|
||||
speed up this process, saving you time and preventing distractions while
|
||||
waiting for a lengthy build to finish. The difference is most noticeable
|
||||
during rapid debug-edit-rebuild cycles.
|
||||
|
||||
## Install
|
||||
## Installation
|
||||
|
||||
Binary packages for the following systems are currently available.
|
||||
Binary packages for the following systems are currently available:
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
## How to build
|
||||
## How to Build
|
||||
|
||||
mold is written in C++20, so if you build mold yourself, you need a
|
||||
recent version of a C++ compiler and a C++ standard library. GCC 10.2
|
||||
or Clang 12.0.0 (or later) as well as libstdc++ 10 or libc++ 7 (or
|
||||
later) are recommended.
|
||||
mold is written in C++20, so if you build mold yourself, you will need a
|
||||
recent version of a C++ compiler and a C++ standard library. We recommend GCC
|
||||
10.2 or Clang 12.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
|
||||
|
||||
### Install dependencies
|
||||
### Install Dependencies
|
||||
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It recognizes your Linux distribution and tries to install
|
||||
necessary packages. You may want to run it as root.
|
||||
directory. It will detect your Linux distribution and attempt to install the
|
||||
necessary packages. You may need to run it as root.
|
||||
|
||||
### Compile mold
|
||||
|
||||
@@ -75,59 +73,58 @@ necessary packages. You may want to run it as root.
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v1.8.0
|
||||
git checkout v2.0.0
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
sudo cmake --install .
|
||||
```
|
||||
|
||||
You may need to pass a C++20 compiler command name to `cmake`.
|
||||
In the above case, `c++` is passed. If it doesn't work for you,
|
||||
try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
example above, `c++` is passed. If that doesn't work for you, try a specific
|
||||
version of a compiler, such as `g++-10` or `clang++-12`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change
|
||||
that by passing `-DCMAKE_INSTALL_PREFIX=<directory>`. For other cmake
|
||||
options, see the comments in `CMakeLists.txt`.
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change the
|
||||
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
|
||||
For other cmake options, see the comments in `CMakeLists.txt`.
|
||||
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`cmake` in the above commands doesn't work for you, you can use Docker to
|
||||
build it in a Docker environment. To do so, just run `./dist.sh` in this
|
||||
directory instead of `cmake`. The shell script pulls a Docker image,
|
||||
builds mold and auxiliary files inside it, and packs them into a
|
||||
single tar file `mold-$version-$arch-linux.tar.gz`. You can extract
|
||||
the tar file anywhere and use `mold` executable in it.
|
||||
If you are not using a recent enough Linux distribution, or if `cmake` does
|
||||
not work for you for any reason, you can use Docker to build mold in a Docker
|
||||
environment. To do so, run `./dist.sh` in this directory instead of using
|
||||
`cmake`. The shell script will pull a Docker image, build mold and auxiliary
|
||||
files inside it, and package them into a single tar file named
|
||||
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere and
|
||||
use the mold executable within it.
|
||||
|
||||
## How to use
|
||||
|
||||
<details><summary>A classic way to use mold</summary>
|
||||
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by the compiler driver (which is usually `cc`,
|
||||
`gcc` or `clang`), which is typically in turn indirectly invoked by
|
||||
`make` or some other build system command.
|
||||
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
|
||||
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
|
||||
indirectly invoked by `make` or another build system command.
|
||||
|
||||
If you can specify an additional command line option to your compiler
|
||||
driver by modifying build system's config files, add one of the
|
||||
following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
If you can specify an additional command line option for your compiler driver
|
||||
by modifying the build system's config files, add one of the following flags
|
||||
to use mold instead of `/usr/bin/ld`:
|
||||
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
- For Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
|
||||
argument, so you need to use `-B` option instead. `-B` is an option
|
||||
to tell GCC where to look for external commands such as `ld`.
|
||||
- For GCC before 12.1.0: the `-fuse-ld` option does not accept `mold` as a
|
||||
valid argument, so you need to use the `-B` option instead. The `-B` option
|
||||
tells GCC where to look for external commands like `ld`.
|
||||
|
||||
If you have installed mold with `make install`, there should be a
|
||||
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
|
||||
depending on your `$PREFIX`), and `ld` command should be there. The
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
If you have installed mold with `make install`, there should be a directory
|
||||
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
|
||||
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
|
||||
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
|
||||
`-B/usr/local/libexec/mold`) to GCC.
|
||||
|
||||
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
|
||||
take an absolute path as an argument for `-fuse-ld` though.
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
|
||||
accept an absolute path as an argument for `-fuse-ld`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -141,32 +138,19 @@ linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable. In the
|
||||
above example, we use `clang` as a linker driver as it can always take
|
||||
the `-fuse-ld` option. If your GCC is recent enough to recognize the
|
||||
option, you may be able to remove the `linker = "clang"` line.
|
||||
where `/path/to/mold` is an absolute path to the mold executable. In the
|
||||
example above, we use `clang` as a linker driver since it always accepts the
|
||||
`-fuse-ld` option. If your GCC is recent enough to recognize the option, you
|
||||
may be able to remove the `linker = "clang"` line.
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
`~/.cargo/config.toml`.
|
||||
|
||||
If you are using macOS, you can modify `config.toml` in a similar manner.
|
||||
Here is an example with `mold` installed via [Homebrew](https://brew.sh).
|
||||
|
||||
```toml
|
||||
[target.x86_64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Nim</summary>
|
||||
@@ -178,67 +162,46 @@ when findExe("mold").len > 0 and defined(linux):
|
||||
switch("passL", "-fuse-ld=mold")
|
||||
```
|
||||
|
||||
where `mold` must be included in the PATH environment variable. In this example
|
||||
The above example uses `gcc` as the linker driver.
|
||||
Use the `fuse-ld` option. If your GCC is recent enough to recognize this option.
|
||||
where `mold` must be included in the `PATH` environment variable. In this
|
||||
example, `gcc` is used as the linker driver. Use the `-fuse-ld` option if your
|
||||
GCC is recent enough to recognize this option.
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to `~/.config/config.nims`.
|
||||
|
||||
If you are using macOS, you can modify config.nims in a similar manner. Here is an example with mold installed via Homebrew.
|
||||
|
||||
```nim
|
||||
when findExe("ld64.mold").len > 0 and defined(macosx):
|
||||
switch("passL", "-fuse-ld=ld64.mold")
|
||||
```
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
`~/.config/config.nims`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>mold -run</summary>
|
||||
|
||||
It is sometimes very hard to pass an appropriate command line option
|
||||
to `cc` to specify an alternative linker. To deal with the situation,
|
||||
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
|
||||
`ld.gold` and redirect it to itself. To use the feature, run `make`
|
||||
(or another build command) as a subcommand of mold as follows:
|
||||
It is sometimes very hard to pass an appropriate command line option to `cc`
|
||||
to specify an alternative linker. To address this situation, mold has a
|
||||
feature to intercept all invocations of `ld`, `ld.lld`, or `ld.gold` and
|
||||
redirect them to itself. To use this feature, run `make` (or another build
|
||||
command) as a subcommand of mold as follows:
|
||||
|
||||
```shell
|
||||
mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
Internally, mold invokes a given command with `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object
|
||||
file intercepts all function calls to `exec(3)`-family functions to
|
||||
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>On macOS</summary>
|
||||
|
||||
mold/macOS is available as an alpha version. It can be used to build not
|
||||
only macOS apps but also iOS apps because their binary formats are the same.
|
||||
|
||||
The command name of mold/macOS is `ld64.mold`. If you build mold on macOS,
|
||||
it still produces `mold` and `ld.mold`, but these executables are useful
|
||||
only for cross compilation (i.e. building Linux apps on macOS.)
|
||||
|
||||
If you find any issue with mold/macOS, please file it to
|
||||
<a href=https://github.com/rui314/mold/issues>our GitHub Issues</a>.
|
||||
Internally, mold invokes the given command with the `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object file
|
||||
intercepts all function calls to `exec(3)`-family functions to replace
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Actions</summary>
|
||||
|
||||
You can use our <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action to speed up GitHub-hosted continuous build. GitHub Actions
|
||||
runs on a two-core machine, but mold is still significantly faster than
|
||||
the default GNU linker there especially when a program being linked is
|
||||
large.
|
||||
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
|
||||
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
|
||||
run on a two-core machine, mold is still significantly faster than the default
|
||||
GNU linker, especially when linking large programs.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
|
||||
mold leaves its identification string in `.comment` section in an output
|
||||
mold leaves its identification string in the `.comment` section of an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
```shell
|
||||
@@ -249,70 +212,50 @@ String dump of section '.comment':
|
||||
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
|
||||
```
|
||||
|
||||
If `mold` is in `.comment`, the file is created by mold.
|
||||
If `mold` is present in the `.comment` section, the file was created by mold.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Online manual</summary>
|
||||
|
||||
Since mold is a drop-in replacement, you should be able to use it
|
||||
without reading its manual. But just in case you need it, it's available
|
||||
online at <a href=https://rui314.github.io/mold.html>here</a>.
|
||||
You can also read the same manual by `man mold`.
|
||||
Since mold is a drop-in replacement, you should be able to use it without
|
||||
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
|
||||
is available. You can read the same manual by running `man mold`.
|
||||
|
||||
</details>
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
One reason is because it simply uses faster algorithms and efficient
|
||||
data structures than other linkers do. The other reason is that the
|
||||
new linker is highly parallelized.
|
||||
One reason is that it utilizes faster algorithms and more efficient data
|
||||
structures compared to other linkers. Another reason is that mold is highly
|
||||
parallelized.
|
||||
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
|
||||
mold (right), linking the same program, a Chromium executable.
|
||||
|
||||

|
||||
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. In this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
As you can see, mold uses all available cores throughout its execution and
|
||||
finishes quickly. In contrast, lld fails to utilize available cores most of
|
||||
the time. In this demo, the maximum parallelism is artificially capped at 16,
|
||||
so that the bars fit in the GIF.
|
||||
|
||||
For details, please read [design notes](docs/design.md).
|
||||
|
||||
## License
|
||||
|
||||
mold is available under AGPL. Note that that does not mean that you
|
||||
have to license your program under AGPL if you use mold to link your
|
||||
program. An output of the mold linker is a derived work of the object
|
||||
files and libraries you pass to the linker but not a derived work of
|
||||
the mold linker itself.
|
||||
|
||||
Besides that, you can also buy a commercial, non-AGPL license with
|
||||
technical support from our company, Blue Whale Systems PTE LTD. If you
|
||||
are a big company, please consider obtaining it before making hundreds
|
||||
or thousands of developers of your company to depend on mold. mold is
|
||||
mostly a single-person open-source project, and just like other
|
||||
open-source projects, we are not legally obligated to keep maintaining
|
||||
it. A legally-binding commercial license contract addresses the
|
||||
concern. By purchasing a license, you are guaranteed that mold will be
|
||||
maintained for you. Please [contact us](mailto:[email protected])
|
||||
for a commercial license inquiry.
|
||||
For details, please see the [design notes](docs/design.md).
|
||||
|
||||
## Sponsors
|
||||
|
||||
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
|
||||
and [OpenCollective](https://opencollective.com/mold-linker).
|
||||
We thank you to everybody who sponsors our project. In particular,
|
||||
we'd like to acknowledge the following people and organizations who
|
||||
have sponsored $128/month or more:
|
||||
and [OpenCollective](https://opencollective.com/mold-linker). We thank
|
||||
everyone who sponsors our project. In particular, we'd like to acknowledge the
|
||||
following people and organizations who have sponsored $128/month or more:
|
||||
|
||||
### Corporate sponsors
|
||||
|
||||
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
<a href="https://cybozu-global.com/"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
|
||||
|
||||
- [Uber](https://uber.com)
|
||||
- [G-Research](https://www.gresearch.co.uk)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
|
||||
### Individual sponsors
|
||||
|
||||
File renamed without changes.
@@ -25,7 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
#include "filetype.h"
|
||||
|
||||
namespace mold {
|
||||
@@ -76,12 +76,12 @@ struct ArHdr {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::vector<MappedFile *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mf->size) {
|
||||
@@ -117,19 +117,18 @@ read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile<C>::must_open(ctx, path));
|
||||
vec.push_back(MappedFile::must_open(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::vector<MappedFile *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mf->size - data >= 2) {
|
||||
@@ -163,10 +162,9 @@ read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mf);
|
||||
case FileType::THIN_AR:
|
||||
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(C &ctx, std::string_view path) {
|
||||
read_response_file(Context &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
|
||||
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
@@ -63,8 +63,8 @@ read_response_file(C &ctx, std::string_view path) {
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
template <typename C>
|
||||
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
template <typename Context>
|
||||
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] == '@')
|
||||
+138
-70
@@ -32,7 +32,7 @@
|
||||
#endif
|
||||
|
||||
#define XXH_INLINE_ALL 1
|
||||
#include "third-party/xxhash/xxhash.h"
|
||||
#include "../third-party/xxhash/xxhash.h"
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define unreachable() __builtin_unreachable()
|
||||
@@ -40,6 +40,23 @@
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
// __builtin_assume() is supported only by clang, and [[assume]] is
|
||||
// available only in C++23, so we use this macro when giving a hint to
|
||||
// the compiler's optimizer what's true.
|
||||
#define ASSUME(x) do { if (!(x)) __builtin_unreachable(); } while (0)
|
||||
|
||||
// This is an assert() that is enabled even in the release build.
|
||||
#define ASSERT(x) \
|
||||
do { \
|
||||
if (!(x)) { \
|
||||
std::cerr << "Assertion failed: (" << #x \
|
||||
<< "), function " << __FUNCTION__ \
|
||||
<< ", file " << __FILE__ \
|
||||
<< ", line " << __LINE__ << ".\n"; \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
inline uint64_t hash_string(std::string_view str) {
|
||||
return XXH3_64bits(str.data(), str.size());
|
||||
}
|
||||
@@ -60,7 +77,7 @@ namespace mold {
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
template <typename C> class OutputFile;
|
||||
template <typename Context> class OutputFile;
|
||||
|
||||
inline char *output_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
@@ -86,41 +103,44 @@ static u64 combine_hash(u64 a, u64 b) {
|
||||
// Error output
|
||||
//
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class SyncOut {
|
||||
public:
|
||||
SyncOut(C &ctx, std::ostream &out = std::cout) : out(out) {
|
||||
SyncOut(Context &ctx, std::ostream *out = &std::cout) : out(out) {
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
}
|
||||
|
||||
~SyncOut() {
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << "\n";
|
||||
if (out) {
|
||||
std::scoped_lock lock(mu);
|
||||
*out << ss.str() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> SyncOut &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
if (out)
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static inline std::mutex mu;
|
||||
|
||||
private:
|
||||
std::ostream &out;
|
||||
std::ostream *out;
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
static std::string add_color(C &ctx, std::string msg) {
|
||||
template <typename Context>
|
||||
static std::string add_color(Context &ctx, std::string msg) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
|
||||
return "mold: " + msg + ": ";
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(C &ctx) : out(ctx, std::cerr) {
|
||||
Fatal(Context &ctx) : out(ctx, &std::cerr) {
|
||||
out << add_color(ctx, "fatal");
|
||||
}
|
||||
|
||||
@@ -136,13 +156,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
SyncOut<Context> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class Error {
|
||||
public:
|
||||
Error(C &ctx) : out(ctx, std::cerr) {
|
||||
Error(Context &ctx) : out(ctx, &std::cerr) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << add_color(ctx, "warning");
|
||||
} else {
|
||||
@@ -157,13 +177,14 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
SyncOut<Context> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(C &ctx) : out(ctx, std::cerr) {
|
||||
Warn(Context &ctx)
|
||||
: out(ctx, ctx.arg.suppress_warnings ? nullptr : &std::cerr) {
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
@@ -178,7 +199,64 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
SyncOut<Context> out;
|
||||
};
|
||||
|
||||
//
|
||||
// Atomics
|
||||
//
|
||||
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
template <typename T>
|
||||
struct Atomic : std::atomic<T> {
|
||||
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
|
||||
|
||||
using std::atomic<T>::atomic;
|
||||
|
||||
Atomic(const Atomic<T> &other) { store(other.load()); }
|
||||
|
||||
Atomic<T> &operator=(const Atomic<T> &other) {
|
||||
store(other.load());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val) { std::atomic<T>::store(val, relaxed); }
|
||||
T load() const { return std::atomic<T>::load(relaxed); }
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
|
||||
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
|
||||
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
|
||||
|
||||
bool test_and_set() {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return load() || exchange(true);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Bit vector
|
||||
//
|
||||
|
||||
class BitVector {
|
||||
public:
|
||||
BitVector() = default;
|
||||
BitVector(u32 size) : vec((size + 7) / 8) {}
|
||||
|
||||
void resize(u32 size) { vec.resize((size + 7) / 8); }
|
||||
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
|
||||
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
|
||||
|
||||
private:
|
||||
std::vector<u8> vec;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -238,17 +316,6 @@ void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
// An optimized "mark" operation for parallel mark-and-sweep algorithms.
|
||||
// Returns true if `visited` was false and updated to true.
|
||||
inline bool fast_mark(std::atomic<bool> &visited) {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return !visited.load(std::memory_order_relaxed) &&
|
||||
!visited.exchange(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
|
||||
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
|
||||
@@ -256,7 +323,12 @@ inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
|
||||
|
||||
template <typename T>
|
||||
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
|
||||
i64 size = 0;
|
||||
for (std::vector<T> &v : vec)
|
||||
size += v.size();
|
||||
|
||||
std::vector<T> ret;
|
||||
ret.reserve(size);
|
||||
for (std::vector<T> &v : vec)
|
||||
append(ret, v);
|
||||
return ret;
|
||||
@@ -356,8 +428,8 @@ inline i64 uleb_size(u64 val) {
|
||||
return 9;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::string_view save_string(C &ctx, const std::string &str) {
|
||||
template <typename Context>
|
||||
std::string_view save_string(Context &ctx, const std::string &str) {
|
||||
u8 *buf = new u8[str.size() + 1];
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
@@ -405,9 +477,9 @@ public:
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
|
||||
key_sizes = (u32 *)calloc(nbuckets, sizeof(key_sizes[0]));
|
||||
values = (T *)calloc(nbuckets, sizeof(values[0]));
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(char *));
|
||||
key_sizes = (u32 *)malloc(nbuckets * sizeof(u32));
|
||||
values = (T *)malloc(nbuckets * sizeof(T));
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
@@ -427,7 +499,7 @@ public:
|
||||
|
||||
if (ptr == nullptr) {
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker,
|
||||
std::memory_order_acq_rel))
|
||||
std::memory_order_acquire))
|
||||
continue;
|
||||
new (values + idx) T(val);
|
||||
key_sizes[idx] = key.size();
|
||||
@@ -448,7 +520,7 @@ public:
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
bool has_key(i64 idx) {
|
||||
const char *get_key(i64 idx) {
|
||||
return keys[idx].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
@@ -457,7 +529,6 @@ public:
|
||||
static constexpr i64 MAX_RETRY = 128;
|
||||
|
||||
i64 nbuckets = 0;
|
||||
std::atomic<const char *> *keys = nullptr;
|
||||
u32 *key_sizes = nullptr;
|
||||
T *values = nullptr;
|
||||
|
||||
@@ -470,6 +541,8 @@ private:
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<const char *> *keys = nullptr;
|
||||
static constexpr const char *marker = "marker";
|
||||
};
|
||||
|
||||
@@ -477,13 +550,13 @@ private:
|
||||
// output-file.h
|
||||
//
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile<C>>
|
||||
open(C &ctx, std::string path, i64 filesize, i64 perm);
|
||||
static std::unique_ptr<OutputFile<Context>>
|
||||
open(Context &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(C &ctx) = 0;
|
||||
virtual void close(Context &ctx) = 0;
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
@@ -646,13 +719,13 @@ public:
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled)
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled)
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
@@ -689,10 +762,10 @@ struct TimerRecord {
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(C &ctx, std::string name, Timer *parent = nullptr) {
|
||||
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));
|
||||
}
|
||||
@@ -743,16 +816,16 @@ private:
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class MappedFile {
|
||||
public:
|
||||
static MappedFile *open(C &ctx, std::string path);
|
||||
static MappedFile *must_open(C &ctx, std::string path);
|
||||
static MappedFile *open(Context &ctx, std::string path);
|
||||
static MappedFile *must_open(Context &ctx, std::string path);
|
||||
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
|
||||
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
|
||||
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size);
|
||||
|
||||
std::string_view get_contents() {
|
||||
return std::string_view((char *)data, size);
|
||||
@@ -782,7 +855,6 @@ public:
|
||||
std::string name;
|
||||
u8 *data = nullptr;
|
||||
i64 size = 0;
|
||||
i64 mtime = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
@@ -792,8 +864,8 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
template <typename Context>
|
||||
MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
@@ -804,8 +876,11 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
|
||||
if (fd == -1)
|
||||
if (fd == -1) {
|
||||
if (errno != ENOENT)
|
||||
Fatal(ctx) << "opening " << path << " failed: " << errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1)
|
||||
@@ -817,14 +892,6 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
#ifdef _WIN32
|
||||
mf->mtime = st.st_mtime;
|
||||
#elif defined(__APPLE__)
|
||||
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
|
||||
#else
|
||||
mf->mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
|
||||
#endif
|
||||
|
||||
if (st.st_size > 0) {
|
||||
#ifdef _WIN32
|
||||
HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
|
||||
@@ -848,17 +915,18 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
|
||||
template <typename Context>
|
||||
MappedFile<Context> *
|
||||
MappedFile<Context>::must_open(Context &ctx, std::string path) {
|
||||
if (MappedFile *mf = MappedFile::open(ctx, path))
|
||||
return mf;
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *
|
||||
MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile<C>;
|
||||
template <typename Context>
|
||||
MappedFile<Context> *
|
||||
MappedFile<Context>::slice(Context &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = name;
|
||||
mf->data = data + start;
|
||||
mf->size = size;
|
||||
@@ -868,8 +936,8 @@ MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
void MappedFile<C>::unmap() {
|
||||
template <typename Context>
|
||||
void MappedFile<Context>::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "third-party/rust-demangle/rust-demangle.h"
|
||||
#include "../third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
@@ -7,8 +7,10 @@ namespace mold {
|
||||
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::string ret = std::filesystem::canonical(path, ec).string();
|
||||
return ec ? std::string(path) : ret;
|
||||
std::filesystem::path link = std::filesystem::read_symlink(path, ec);
|
||||
if (ec)
|
||||
return std::string(path);
|
||||
return (filepath(path) / ".." / link).lexically_normal().string();
|
||||
}
|
||||
|
||||
// Removes redundant '/..' or '/.' from a given path.
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "elf/elf.h"
|
||||
#include "common.h"
|
||||
#include "../elf/elf.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -23,15 +23,15 @@ enum class FileType {
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
bool is_text_file(MappedFile<C> *mf) {
|
||||
template <typename MappedFile>
|
||||
bool is_text_file(MappedFile *mf) {
|
||||
u8 *data = mf->data;
|
||||
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
|
||||
template <typename E, typename Context, typename MappedFile>
|
||||
inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
|
||||
using namespace mold::elf;
|
||||
|
||||
const char *data = mf->get_contents().data();
|
||||
@@ -45,21 +45,23 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
|
||||
for (ElfShdr<E> &sec : shdrs) {
|
||||
// GCC LTO object contains only sections symbols followed by a common
|
||||
// symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1` for older
|
||||
// GCC releases).
|
||||
//
|
||||
// However, FAT LTO objects don't have any of the above mentioned symbols
|
||||
// and can identify LTO by `.gnu.lto_.symtab.` section, similarly
|
||||
// to what lto-plugin does. However, if LTO linker plug-in is not available,
|
||||
// use the emitted assembly instead.
|
||||
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
|
||||
if (opt_plugin && name.starts_with (".gnu.lto_.symtab."))
|
||||
// GCC FAT LTO objects contain both regular ELF sections and GCC-
|
||||
// specific LTO sections, so that they can be linked as LTO objects if
|
||||
// the LTO linker plugin is available and falls back as regular
|
||||
// objects otherwise. GCC FAT LTO object can be identified by the
|
||||
// presence of `.gcc.lto_.symtab` section.
|
||||
if (!ctx.arg.plugin.empty()) {
|
||||
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
|
||||
if (name.starts_with(".gnu.lto_.symtab."))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
continue;
|
||||
|
||||
// GCC non-FAT LTO object contains only sections symbols followed by
|
||||
// a common symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1`
|
||||
// for older GCC releases).
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
@@ -83,8 +85,8 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
template <typename Context, typename MappedFile>
|
||||
FileType get_file_type(Context &ctx, MappedFile *mf) {
|
||||
using namespace elf;
|
||||
|
||||
std::string_view data = mf->get_contents();
|
||||
@@ -100,10 +102,10 @@ FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<I386>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<I386>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<X86_64>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<X86_64>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
@@ -116,10 +118,10 @@ FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<M68K>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<M68K>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<SPARC64>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<SPARC64>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace mold {
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += pow(2, -val);
|
||||
z += std::ldexp(1.0, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
@@ -110,14 +110,26 @@ static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
if ((signo == SIGSEGV || signo == SIGBUS) &&
|
||||
output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
switch (signo) {
|
||||
case SIGSEGV:
|
||||
case SIGBUS:
|
||||
if (output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
}
|
||||
break;
|
||||
case SIGABRT: {
|
||||
const char msg[] =
|
||||
"mold: aborted\n"
|
||||
"mold: If mold failed due to a spurious failure of pthread_create, "
|
||||
"it's likely because of https://github.com/oneapi-src/oneTBB/pull/824. "
|
||||
"You should ensure that you are using 2021.9.0 or newer version of libtbb.\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
@@ -127,6 +139,7 @@ void install_signal_handler() {
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGABRT, &action, NULL);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
@@ -148,7 +161,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
#if MOLD_IS_SOLD
|
||||
std::string cmd = mold::filepath(argv[0]).filename().string();
|
||||
if (cmd == "ld64" || cmd == "ld64.mold")
|
||||
if (cmd == "ld64" || cmd.starts_with("ld64."))
|
||||
return mold::macho::main(argc, argv);
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// slow, but complex patterns are rare in practice, so it should be
|
||||
// OK.
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
@@ -14,9 +14,9 @@ inline u32 get_umask() {
|
||||
return orig_umask;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
static std::pair<i64, char *>
|
||||
open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
open_or_create_file(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
|
||||
char *path2 = (char *)save_string(ctx, tmpl).data();
|
||||
|
||||
@@ -47,11 +47,11 @@ open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
return {fd, path2};
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
class MemoryMappedOutputFile : public OutputFile<C> {
|
||||
template <typename Context>
|
||||
class MemoryMappedOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MemoryMappedOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, true) {
|
||||
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);
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
::close(fd2);
|
||||
}
|
||||
|
||||
void close(C &ctx) override {
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
@@ -92,18 +92,18 @@ private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
class MallocOutputFile : public OutputFile<C> {
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, false), perm(perm) {
|
||||
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(C &ctx) override {
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
@@ -125,9 +125,9 @@ private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::unique_ptr<OutputFile<C>>
|
||||
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
@@ -142,11 +142,11 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
OutputFile<C> *file;
|
||||
OutputFile<Context> *file;
|
||||
if (is_special)
|
||||
file = new MallocOutputFile<C>(ctx, path, filesize, perm);
|
||||
file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
else
|
||||
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
|
||||
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
// Enable transparent huge page for an output memory-mapped file.
|
||||
@@ -163,7 +163,7 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
return std::unique_ptr<OutputFile>(file);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
@@ -6,17 +6,17 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
class MallocOutputFile : public OutputFile<C> {
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, false), perm(perm) {
|
||||
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(C &ctx) override {
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
@@ -39,19 +39,19 @@ private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::unique_ptr<OutputFile<C>>
|
||||
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
OutputFile<C> *file = new MallocOutputFile<C>(ctx, path, filesize, perm);
|
||||
OutputFile<Context> *file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
return std::unique_ptr<OutputFile<Context>>(file);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
@@ -31,7 +31,7 @@ static i64 now_nsec() {
|
||||
#else
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (i64)t.tv_sec * 1000000000 + t.tv_nsec;
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ static std::pair<i64, i64> get_usage() {
|
||||
return {to_nsec(user), to_nsec(kernel)};
|
||||
#else
|
||||
auto to_nsec = [](struct timeval t) -> i64 {
|
||||
return (i64)t.tv_sec * 1000000000 + t.tv_usec * 1000;
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
|
||||
};
|
||||
|
||||
struct rusage ru;
|
||||
@@ -79,9 +79,9 @@ void TimerRecord::stop() {
|
||||
|
||||
static void print_rec(TimerRecord &rec, i64 indent) {
|
||||
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
|
||||
((double)rec.user / 1000000000),
|
||||
((double)rec.sys / 1000000000),
|
||||
(((double)rec.end - rec.start) / 1000000000),
|
||||
((double)rec.user / 1'000'000'000),
|
||||
((double)rec.sys / 1'000'000'000),
|
||||
(((double)rec.end - rec.start) / 1'000'000'000),
|
||||
std::string(indent * 2, ' ').c_str(),
|
||||
rec.name.c_str());
|
||||
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -24,7 +24,10 @@ struct UstarHeader {
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(*this); i++)
|
||||
sum += ((u8 *)this)[i];
|
||||
assert(sum < 01000000);
|
||||
|
||||
// We need to convince the compiler that sum isn't too big to silence
|
||||
// -Werror=format-truncation.
|
||||
ASSUME(sum < 01'000'000);
|
||||
snprintf(checksum, sizeof(checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
@@ -7,20 +7,27 @@
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
[[ $arch = arm* ]] && arch=arm
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
exit 1
|
||||
fi
|
||||
esac
|
||||
|
||||
arch=$1
|
||||
echo $arch | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
set -e -x
|
||||
|
||||
docker run --platform linux/$arch -it --rm -v "$(pwd):/mold" \
|
||||
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 &&
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
+492
-1239
File diff suppressed because it is too large.
Load diff
+798
@@ -0,0 +1,798 @@
|
||||
mold(1) -- a modern linker
|
||||
==========================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`mold` [_option_...] _file_...
|
||||
|
||||
## DESCRIPTION
|
||||
`mold` is a faster drop-in replacement for the default GNU ld(1).
|
||||
|
||||
### How to use
|
||||
See <https://github.com/rui314/mold#how-to-use>.
|
||||
|
||||
### Compatibility
|
||||
|
||||
**Mold** is designed to be a drop-in replacement for the GNU linkers for
|
||||
linking user-land programs. If your user-land program cannot be built due to
|
||||
missing command-line options, please file a bug at
|
||||
<https://github.com/rui314/mold/issues>.
|
||||
|
||||
Mold supports a very limited set of linker script features, which is just
|
||||
sufficient to read `/usr/lib/x86_64-linux-gnu/libc.so` on Linux systems (on
|
||||
Linux, that file is contrary to its name not a shared library but an ASCII
|
||||
linker script that loads a real `libc.so` file.)
|
||||
|
||||
Beyond that, we have no plan to support any additional linker script features.
|
||||
The linker script is an ad-hoc, over-designed, complex language which we
|
||||
believe needs to be replaced by a simpler mechanism. We have a plan to add a
|
||||
replacement for the linker script to `mold` instead.
|
||||
|
||||
### Archive symbol resolution
|
||||
|
||||
Traditionally, Unix linkers are sensitive to the order in which input files
|
||||
appear on the command line. They process input files from the first (leftmost)
|
||||
file to the last (rightmost) file one-by-one. While reading input files, they
|
||||
maintain sets of defined and undefined symbols. When visiting an archive file
|
||||
(`.a` files), they pull out object files to resolve as many undefined symbols
|
||||
as possible and move on to the next input file. Object files that weren't
|
||||
pulled out will never have a chance for a second look.
|
||||
|
||||
Due to this behavior, you usually have to add archive files at the end of a
|
||||
command line, so that when a linker reaches archive files, it knows what
|
||||
symbols remain as undefined.
|
||||
|
||||
If you put archive files at the beginning of a command line, a linker doesn't
|
||||
have any undefined symbols, and thus no object files will be pulled out from
|
||||
archives. You can change the processing order by using the `--start-group` and
|
||||
`--end-group` options, though they make a linker slower.
|
||||
|
||||
`mold`, as well as the LLVM lld(1) linker, takes a different approach. They
|
||||
remember which symbols can be resolved from archive files instead of
|
||||
forgetting them after processing each archive. Therefore, `mold` and lld(1)
|
||||
can "go back" in a command line to pull out object files from archives if they
|
||||
are needed to resolve remaining undefined symbols. They are not sensitive to
|
||||
the input file order.
|
||||
|
||||
`--start-group` and `--end-group` are still accepted by `mold` and lld(1)
|
||||
for compatibility with traditional linkers, but they are silently ignored.
|
||||
|
||||
### Dynamic symbol resolution
|
||||
|
||||
Some Unix linker features are difficult to understand without comprehending
|
||||
the semantics of dynamic symbol resolution. Therefore, even though it's not
|
||||
specific to `mold`, we'll explain it here.
|
||||
|
||||
We use "ELF module" or just "module" as a collective term to refer to an
|
||||
executable or a shared library file in the ELF format.
|
||||
|
||||
An ELF module may have lists of imported symbols and exported symbols, as well
|
||||
as a list of shared library names from which imported symbols should be
|
||||
imported. The point is that imported symbols are not bound to any specific
|
||||
shared library until runtime.
|
||||
|
||||
Here is how the Unix dynamic linker resolves dynamic symbols. Upon the start
|
||||
of an ELF program, the dynamic linker constructs a list of ELF modules which,
|
||||
as a whole, consist of a complete program. The executable file is always at
|
||||
the beginning of the list followed by its dependent shared libraries. An
|
||||
imported symbol is searched from the beginning of the list to the end. If two
|
||||
or more modules define the same symbol, the one that appears first in the list
|
||||
takes precedence over the others.
|
||||
|
||||
This Unix semantics are contrary to systems such as Windows that have a
|
||||
two-level namespace for dynamic symbols. On Windows, for example, dynamic
|
||||
symbols are represented as a tuple of (`symbol-name`, `shared-library-name`),
|
||||
so that each dynamic symbol is guaranteed to be resolved from some specific
|
||||
library.
|
||||
|
||||
Typically, an ELF module that exports a symbol also imports the same symbol.
|
||||
Such a symbol is usually resolved to itself, but that's not the case if a
|
||||
module that appears before it in the symbol search list provides another
|
||||
definition of the same symbol.
|
||||
|
||||
Let's take `malloc` as an example. Assume that you define your version of
|
||||
`malloc` in your main executable file. Then, all `malloc` calls from any
|
||||
module are resolved to your function instead of the one in libc, because the
|
||||
executable is always at the beginning of the dynamic symbol search list. Note
|
||||
that even `malloc` calls within libc are resolved to your definition since
|
||||
libc exports and imports `malloc`. Therefore, by defining `malloc` yourself,
|
||||
you can overwrite a library function, and the `malloc` in libc becomes dead
|
||||
code.
|
||||
|
||||
These Unix semantics are tricky and sometimes considered harmful. For example,
|
||||
assume that you accidentally define `atoi` as a global function in your
|
||||
executable that behaves completely differently from the one in the C standard.
|
||||
Then, all `atoi` function calls from any modules (even function calls within
|
||||
libc) are redirected to your function instead of the one in libc, which will
|
||||
very likely cause a problem.
|
||||
|
||||
That is a somewhat surprising consequence for an accidental name conflict. On
|
||||
the other hand, this semantic is sometimes useful because it allows users to
|
||||
override library functions without rebuilding modules containing them.
|
||||
|
||||
Whether good or bad, you should keep these semantics in mind to understand
|
||||
Unix linkers' behaviors.
|
||||
|
||||
### Build reproducibility
|
||||
|
||||
`mold`'s output is deterministic. That is, if you pass the same object files
|
||||
and the same command-line options to the same version of `mold`, it is
|
||||
guaranteed that `mold` produces the bit-by-bit identical output. The linker's
|
||||
internal randomness, such as the timing of thread scheduling or iteration
|
||||
orders of hash tables, doesn't affect the output.
|
||||
|
||||
`mold` does not have any host-specific default settings. This is contrary to the
|
||||
GNU linkers, for which some configurable values, such as system-dependent
|
||||
library search paths, are hard-coded. `mold` depends only on its command-line
|
||||
arguments.
|
||||
|
||||
## MOLD-SPECIFIC OPTIONS
|
||||
|
||||
* `--chroot`=_dir_:
|
||||
Set _dir_ as the root directory.
|
||||
|
||||
* `--color-diagnostics`=[ _auto_ | _always_ | _never_ ]:
|
||||
Show diagnostic messages in color using ANSI escape sequences. `auto` means
|
||||
that `mold` prints out messages in color only if the standard output is
|
||||
connected to a TTY. Default is `auto`.
|
||||
|
||||
* `--color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=auto`.
|
||||
|
||||
* `--no-color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=never`.
|
||||
|
||||
* `--fork`, `--no-fork`:
|
||||
Spawn a child process and let it do the actual linking. When linking a large
|
||||
program, the OS kernel can take a few hundred milliseconds to terminate a
|
||||
`mold` process. `--fork` hides that latency. By default, it does fork.
|
||||
|
||||
* `--perf`:
|
||||
Print performance statistics.
|
||||
|
||||
* `--print-dependencies`:
|
||||
Print out dependency information for input files.
|
||||
|
||||
Each line of the output for this option shows which file depends on which
|
||||
file to use a specific symbol. This option is useful for debugging why some
|
||||
object file in a static archive got linked or why some shared library is
|
||||
kept in an output file's dependency list even with `--as-needed`.
|
||||
|
||||
* `--repro`:
|
||||
Archive input files, as well as a text file containing command line options,
|
||||
in a tar file so that you can run `mold` with the exact same inputs again.
|
||||
This is useful for reporting a bug with a reproducer. The output filename is
|
||||
`path/to/output.tar`, where `path/to/output` is an output filename specified
|
||||
by `-o`.
|
||||
|
||||
* `--reverse-sections`:
|
||||
Reverse the order of input sections before assigning them the offsets in the
|
||||
output file.
|
||||
|
||||
This option is useful for finding bugs that depend on the initialization
|
||||
order of global objects. In C++, constructors of global objects in a single
|
||||
source file are guaranteed to be executed in the source order, but there's
|
||||
no such guarantee across compilation units. Usually, constructors are
|
||||
executed in the order given to the linker, but depending on it is a mistake.
|
||||
|
||||
By reversing the order of input sections using `--reverse-sections`, you can
|
||||
easily test that your program works in the reversed initialization order.
|
||||
|
||||
* `--run` _command_ _arg_...:
|
||||
Run _command_ with `mold` `/usr/bin/ld`. Specifically, `mold` runs a given
|
||||
command with the `LD_PRELOAD` environment set to intercept exec(3) family
|
||||
functions and replaces `argv[0]` with itself if it is `ld`, `ld.gold`, or
|
||||
`ld.lld`.
|
||||
|
||||
* `--shuffle-sections`, `--shuffle-sections`=_number_:
|
||||
Randomize the output by shuffling the order of input sections before
|
||||
assigning them the offsets in the output file. If a _number_ is given, it's
|
||||
used as a seed for the random number generator, so that the linker produces
|
||||
the same output for the same seed. If no seed is given, a random number is
|
||||
used as a seed.
|
||||
|
||||
This option is useful for benchmarking. Modern CPUs are sensitive to a
|
||||
program's memory layout. A seemingly benign change in program layout, such
|
||||
as a small size increase of a function in the middle of a program, can
|
||||
affect the program's performance. Therefore, even if you write new code and
|
||||
get a good benchmark result, it is hard to say whether the new code improves
|
||||
the program's performance; it is possible that the new memory layout happens
|
||||
to perform better.
|
||||
|
||||
By running a benchmark multiple times with randomized memory layouts using
|
||||
`--shuffle-sections`, you can isolate your program's real performance number
|
||||
from the randomness caused by memory layout changes.
|
||||
|
||||
* `--stats`:
|
||||
Print input statistics.
|
||||
|
||||
* `--thread-count`=_count_:
|
||||
Use _count_ number of threads.
|
||||
|
||||
* `--threads`, `--no-threads`:
|
||||
Use multiple threads. By default, `mold` uses as many threads as the number of
|
||||
cores or 32, whichever is smaller. The reason it is capped at 32 is because
|
||||
`mold` doesn't scale well beyond that point. To use only one thread, pass
|
||||
`--no-threads` or `--thread-count=1`.
|
||||
|
||||
* `--quick-exit`, `--no-quick-exit`:
|
||||
Use or do not use `quick_exit` to exit.
|
||||
|
||||
## GNU-COMPATIBLE OPTIONS
|
||||
|
||||
* `--help`:
|
||||
Report usage information to stdout and exit.
|
||||
|
||||
* `-v`, `--version`:
|
||||
Report version information to stdout.
|
||||
|
||||
* `-V`:
|
||||
Report version and target information to stdout.
|
||||
|
||||
* `-E`, `--export-dynamic`, `--no-export-dynamic`:
|
||||
When creating an executable, using the `-E` option causes all global symbols
|
||||
to be put into the dynamic symbol table, so that the symbols are visible
|
||||
from other ELF modules at runtime.
|
||||
|
||||
By default, or if `--no-export-dynamic` is given, only symbols that are
|
||||
referenced by DSOs at link-time are exported from an executable.
|
||||
|
||||
* `-F` _libname_, `--filter`=_libname_:
|
||||
Set the `DT_FILTER` dynamic section field to _libname_.
|
||||
|
||||
* `-I` _file_, `--dynamic-linker`=_file_, `--no-dynamic-linker`:
|
||||
Set the dynamic linker path to _file_. If no `-I` option is given, or if
|
||||
`--no-dynamic-linker` is given, no dynamic linker path is set to an output
|
||||
file. This is contrary to the GNU linkers which set a default dynamic linker
|
||||
path in that case. This difference doesn't usually make any difference
|
||||
because the compiler driver always passes `-I` to the linker.
|
||||
|
||||
* `-L` _dir_, `--library-path`=_dir_:
|
||||
Add _dir_ to the list of library search paths from which `mold` searches
|
||||
libraries for the `-l` option.
|
||||
|
||||
Unlike the GNU linkers, `mold` does not have default search paths. This
|
||||
difference doesn't usually make any difference because the compiler driver
|
||||
always passes all necessary search paths to the linker.
|
||||
|
||||
* `-M`, `--print-map`:
|
||||
Write a map file to stdout.
|
||||
|
||||
* `-N`, `--omagic`, `--no-omagic`:
|
||||
Force `mold` to emit an output file with an old-fashioned memory layout.
|
||||
First, it makes the first data segment not aligned to a page boundary.
|
||||
Second, text segments are marked as writable if the option is given.
|
||||
|
||||
* `-S`, `--strip-debug`:
|
||||
Omit `.debug_*` sections from the output file.
|
||||
|
||||
* `-T` _file_, `--script`=_file_:
|
||||
Read linker script from _file_.
|
||||
|
||||
* `-X`, `--discard-locals`:
|
||||
Discard temporary local symbols to reduce the sizes of the symbol table and
|
||||
the string table. Temporary local symbols are local symbols starting with
|
||||
`.L`. Compilers usually generate such symbols for unnamed program elements
|
||||
such as string literals or floating-point literals.
|
||||
|
||||
* `-e` _symbol_, `--entry`=_symbol_:
|
||||
|
||||
Use _symbol_ as the entry point symbol instead of the default entry
|
||||
point symbol _start.
|
||||
|
||||
* `-f` _shlib_, `--auxiliary`=_shlib_:
|
||||
Set the `DT_AUXILIARY` dynamic section field to _shlib_.
|
||||
|
||||
* `-h` _libname_, `--soname`=_libname_:
|
||||
Set the `DT_SONAME` dynamic section field to _libname_. This option is used
|
||||
when creating a shared object file. Typically, when you create `libfoo.so`,
|
||||
you want to pass `--soname=foo` to a linker.
|
||||
|
||||
* `-l` _libname_:
|
||||
Search for `lib`_libname_`.so` or `lib`_libname_`.a` from library search
|
||||
paths.
|
||||
|
||||
* `-m` _target_:
|
||||
Choose a _target_.
|
||||
|
||||
* `-o` _file_, `--output`=_file_:
|
||||
Use _file_ as the output file name instead of the default name `a.out`.
|
||||
|
||||
* `-r`, `--relocatable`:
|
||||
Instead of generating an executable or a shared object file, combine input
|
||||
object files to generate another object file that can be used as an input to
|
||||
a linker.
|
||||
|
||||
* `--relocatable-merge-sections`:
|
||||
By default, `mold` doesn't merge input sections by name when merging input
|
||||
object files into a single output object file for `-r`. For example,
|
||||
`.text.foo` and `.text.bar` aren't merged for `-r` even though they are
|
||||
merged into `.text` according to the default section merging rules.
|
||||
|
||||
This option changes the behavior so that `mold` merges input sections by
|
||||
name by the default section merging rules.
|
||||
|
||||
* `-s`, `--strip-all`:
|
||||
Omit `.symtab` section from the output file.
|
||||
|
||||
* `-u` _symbol_, `--undefined`=_symbol_:
|
||||
If _symbol_ remains as an undefined symbol after reading all object files,
|
||||
and if there is a static archive that contains an object file defining
|
||||
_symbol_, pull out the object file and link it so that the output file
|
||||
contains a definition of _symbol_.
|
||||
|
||||
* `--Bdynamic`:
|
||||
Link against shared libraries.
|
||||
|
||||
* `--Bstatic`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--Bsymbolic`:
|
||||
When creating a shared library, make global symbols export-only (i.e. do not
|
||||
import the same symbol). As a result, references within a shared library are
|
||||
always resolved locally, negating symbol override at runtime. See "Dynamic
|
||||
symbol resolution" for more information about symbol imports and exports.
|
||||
|
||||
* `--Bsymbolic-functions`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for function
|
||||
symbols. Data symbols remain being both imported and exported.
|
||||
|
||||
* `--Bno-symbolic`:
|
||||
Cancel `--Bsymbolic` and `--Bsymbolic-functions`.
|
||||
|
||||
* `--Map`=_file_:
|
||||
Write map file to _file_.
|
||||
|
||||
* `--Tbss`=_address_:
|
||||
Alias for `--section-start=.bss=`_address_.
|
||||
|
||||
* `--Tdata`=_address_:
|
||||
Alias for `--section-start=.data=`_address_.
|
||||
|
||||
* `--Ttext`=_address_:
|
||||
Alias for `--section-start=.text=`_address_.
|
||||
|
||||
* `--allow-multiple-definition`:
|
||||
Normally, the linker reports an error if there are more than one definition
|
||||
of a symbol. This option changes the default behavior so that it doesn't
|
||||
report an error for duplicate definitions and instead use the first
|
||||
definition.
|
||||
|
||||
* `--as-needed`, `--no-as-needed`:
|
||||
By default, shared libraries given to the linker are unconditionally added
|
||||
to the list of required libraries in an output file. However, shared
|
||||
libraries after `--as-needed` are added to the list only when at least one
|
||||
symbol is actually used by the output file. In other words, shared libraries
|
||||
after `--as-needed` are not added to the list of needed libraries if they
|
||||
are not needed by a program.
|
||||
|
||||
The `--no-as-needed` option restores the default behavior for subsequent
|
||||
files.
|
||||
|
||||
* `--build-id`=[ `md5` | `sha1` | `sha256` | `uuid` | `0x`_hexstring_ | `none` ]:
|
||||
Create a `.note.gnu.build-id` section containing a byte string to uniquely
|
||||
identify an output file. `sha256` compute a 256-bit cryptographic hash of an
|
||||
output file and set it to build-id. `md5` and `sha1` compute the same hash
|
||||
but truncate it to 128 and 160 bits, respectively, before setting it to
|
||||
build-id. `uuid` sets a random 128-bit UUID. `0x`_hexstring_ sets
|
||||
_hexstring_.
|
||||
|
||||
* `--build-id`:
|
||||
Synonym for `--build-id=sha256`.
|
||||
|
||||
* `--no-build-id`:
|
||||
Synonym for `--build-id=none`.
|
||||
|
||||
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zstd` | `none` ]:
|
||||
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
|
||||
compression algorithm. `zlib-gabi` is an alias for `zlib`.
|
||||
|
||||
* `--defsym`=_symbol_=_value_:
|
||||
Define _symbol_ as an alias for _value_.
|
||||
|
||||
_value_ is either an integer (in decimal or hexadecimal with `0x` prefix) or
|
||||
a symbol name. If an integer is given as a value, _symbol_ is defined as an
|
||||
absolute symbol with the given value.
|
||||
|
||||
* `--default-symver`:
|
||||
Use soname as a symbol version and append that version to all symbols.
|
||||
|
||||
* `--demangle`, `--no-demangle`:
|
||||
Demangle C++ and Rust symbols in log messages.
|
||||
|
||||
* `--dependency-file`=_file_:
|
||||
Write a dependency file to _file_. The contents of the written file is
|
||||
readable by make(1), which defines only one rule with the linker's output
|
||||
file as a target and all input files as its prerequisites. Users are
|
||||
expected to include the generated dependency file into a Makefile to
|
||||
automate the dependency management. This option is analogous to the
|
||||
compiler's `-MM -MF` options.
|
||||
|
||||
* `--dynamic-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_. Same as
|
||||
`--export-dynamic-symbol-list`, except that it implies `--Bsymbolic`. If
|
||||
_file_ does not exist in the current directory, it is searched from library
|
||||
search paths for the sake of compatibility with GNU ld.
|
||||
|
||||
* `--eh-frame-hdr`, `--no-eh-frame-hdr`:
|
||||
Create `.eh_frame_hdr` section.
|
||||
|
||||
* `--emit-relocs`:
|
||||
The linker usually "consumes" relocation sections. That is, the linker
|
||||
applies relocations to other sections, and relocation sections themselves
|
||||
are discarded.
|
||||
|
||||
The `--emit-relocs` instructs the linker to leave relocation sections in the
|
||||
output file. Some post-link binary analysis or optimization tools such as
|
||||
LLVM Bolt need them.
|
||||
|
||||
* `--enable-new-dtags`, `--disable-new-dtags`:
|
||||
By default, `mold` emits `DT_RUNPATH` for `--rpath`. If you pass
|
||||
`--disable-new-dtags`, `mold` emits `DT_RPATH` for `--rpath` instead.
|
||||
|
||||
* `--execute-only`:
|
||||
Traditionally, most processors require both executable and readable bits to
|
||||
1 to make the page executable, which allows machine code to be read as data
|
||||
at runtime. This is actually what an attacker often does after gaining a
|
||||
limited control of a process to find pieces of machine code they can use to
|
||||
gain the full control of the process. As a mitigation, some recent
|
||||
processors allows "execute-only" pages. If a page is execute-only, you can
|
||||
call a function there as long as you know its address but can't read it as
|
||||
data.
|
||||
|
||||
This option marks text segments execute-only. This option currently works
|
||||
only on some ARM64 processors.
|
||||
|
||||
* `--exclude-libs`=_libraries_ ...:
|
||||
Mark all symbols in the given _libraries_ hidden.
|
||||
|
||||
* `--export-dynamic-symbol`=_symbol_:
|
||||
Put symbols matching _symbol_ in the dynamic symbol table. _symbol_ may be a
|
||||
glob pattern in the same syntax as for the `--export-dynamic-symbol-list` or
|
||||
`--version-script` options.
|
||||
|
||||
* `--export-dynamic-symbol-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_.
|
||||
|
||||
* `--fatal-warnings`, `--no-fatal-warnings`:
|
||||
Treat warnings as errors.
|
||||
|
||||
* `--fini`=_symbol_:
|
||||
Call _symbol_ at unload-time.
|
||||
|
||||
* `--gc-sections`, `--no-gc-sections`:
|
||||
Remove unreferenced sections.
|
||||
|
||||
* `--gdb-index`:
|
||||
Create a `.gdb_index` section to speed up GNU debugger. To use this, you
|
||||
need to compile source files with the `-ggnu-pubnames` compiler flag.
|
||||
|
||||
* `--hash-style`=[ `sysv` | `gnu` | `both` | `none` ]:
|
||||
Set hash style.
|
||||
|
||||
* `--icf`=[ `safe` | `all` | `none` ], `--no-icf`:
|
||||
It is not uncommon for a program to contain many identical functions that
|
||||
differ only in name. For example, a C++ template `std::vector` is very
|
||||
likely to be instantiated to the identical code for `std::vector<int>` and
|
||||
`std::vector<unsigned>` because the container cares only about the size of
|
||||
the parameter type. Identical Code Folding (ICF) is a size optimization to
|
||||
identify and merge such identical functions.
|
||||
|
||||
If `--icf=all` is given, `mold` tries to merge all identical functions. This
|
||||
reduces the size of the output most, but it is not a "safe" optimization. It
|
||||
is guaranteed in C and C++ that two pointers pointing two different
|
||||
functions will never be equal, but `--icf=all` breaks that assumption as two
|
||||
identical functions have the same address after merging. So a care must be
|
||||
taken when you use this flag that your program does not depend on the
|
||||
function pointer uniqueness.
|
||||
|
||||
`--icf=safe` is a flag to merge functions only when it is safe to do so.
|
||||
That is, if a program does not take an address of a function, it is safe to
|
||||
merge that function with other function, as you cannot compare a function
|
||||
pointer with something else without taking an address of a function.
|
||||
|
||||
`--icf=safe` needs to be used with a compiler that supports `.llvm_addrsig`
|
||||
section which contains the information as to what symbols are address-taken.
|
||||
LLVM/Clang supports that section by default. Since GCC does not support it
|
||||
yet, you cannot use `--icf=safe` with GCC (it doesn't do any harm but can't
|
||||
optimize at all.)
|
||||
|
||||
`--icf=none` and `--no-icf` disables ICF.
|
||||
|
||||
* `--ignore-data-address-equality`:
|
||||
Make ICF to merge not only functions but also data. This option should be
|
||||
used in combination with `--icf=all`.
|
||||
|
||||
* `--image-base`=_addr_:
|
||||
Set the base address to _addr_.
|
||||
|
||||
* `--init`=_symbol_:
|
||||
Call _symbol_ at load-time.
|
||||
|
||||
* `--no-undefined`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `--noinhibit-exec`:
|
||||
Create an output file even if errors occur.
|
||||
|
||||
* `--pack-dyn-relocs`=[ `relr` | `none` ]:
|
||||
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
|
||||
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
|
||||
`.relr.dyn` section uses a space-efficient encoding scheme, specifying this
|
||||
flag can reduce the size of the output. This is typically most effective for
|
||||
position-independent executable.
|
||||
|
||||
Note that a runtime loader has to support `.relr.dyn` to run executables or
|
||||
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2022, only
|
||||
ChromeOS, Android and Fuchsia support it.
|
||||
|
||||
* `--package-metadata`=_string_:
|
||||
Embed _string_ to a `.note.package` section. This option is intended to be
|
||||
used by a package management command such as rpm(8) to embed metadata
|
||||
regarding a package to each executable file.
|
||||
|
||||
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
|
||||
Create a position-independent executable.
|
||||
|
||||
* `--print-gc-sections`, `--no-print-gc-sections`:
|
||||
Print removed unreferenced sections.
|
||||
|
||||
* `--print-icf-sections`, `--no-print-icf-sections`:
|
||||
Print folded identical sections.
|
||||
|
||||
* `--push-state`, `--pop-state`:
|
||||
`--push-state` saves the current values of `--as-needed`, `--whole-archive`,
|
||||
`--static`, and `--start-lib`. The saved values can be restored by
|
||||
pop-state.
|
||||
|
||||
`--push-state` and `--pop-state` pairs can nest.
|
||||
|
||||
These options are useful when you want to construct linker command line
|
||||
options programmatically. For example, if you want to link `libfoo.so` by
|
||||
as-needed basis but don't want to change the global state of `--as-needed`,
|
||||
you can append `--push-state --as-needed -lfoo --pop-state` to the linker
|
||||
command line options.
|
||||
|
||||
* `--relax, --no-relax`:
|
||||
Rewrite machine instructions with more efficient ones for some relocations.
|
||||
The feature is enabled by default.
|
||||
|
||||
* `--require-defined`=_symbol_:
|
||||
Like `--undefined`, except the new symbol must be defined by the end of the
|
||||
link.
|
||||
|
||||
* `--retain-symbols-file`=_file_:
|
||||
Keep only symbols listed in _file_. _file_ is a text file containing a
|
||||
symbol name on each line. `mold` discards all local symbols as well as
|
||||
global symbol that are not in _file_. Note that this option removes symbols
|
||||
only from `.symtab` section and does not affect `.dynsym` section, which is
|
||||
used for dynamic linking.
|
||||
|
||||
* `--rpath`=_dir_:
|
||||
Add _dir_ to runtime search paths.
|
||||
|
||||
* `--section-start`=_section_=_address_:
|
||||
Set _address_ to section. _address_ is a hexadecimal number that may start
|
||||
with an optional `0x`.
|
||||
|
||||
* `--shared`, `--Bshareable`:
|
||||
Create a share library.
|
||||
|
||||
* `--spare-dynamic-tags`=_number_:
|
||||
Reserve the given _number_ of tags in `.dynamic` section.
|
||||
|
||||
* `--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
|
||||
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).
|
||||
|
||||
* `--static`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--sysroot`=_dir_:
|
||||
Set target system root directory to _dir_.
|
||||
|
||||
* `--trace`:
|
||||
Print name of each input file.
|
||||
|
||||
* `--undefined-version`, `--no-undefined-version`:
|
||||
By default, `mold` warns on a symbol specified by a version script or by
|
||||
`--export-dynamic-symbol` if it is not defined. You can silence the warning
|
||||
by `--undefined-version`.
|
||||
|
||||
* `--unique`=_pattern_:
|
||||
Don't merge input sections that match the given glob pattern _pattern_.
|
||||
|
||||
* `--unresolved-symbols`=[ `report-all` | `ignore-all` | `ignore-in-object-files` | `ignore-in-shared-libs` ]:
|
||||
How to handle undefined symbols.
|
||||
|
||||
* `--version-script`=_file_:
|
||||
Read version script from _file_. If _file_ does not exist in the current
|
||||
directory, it is searched from library search paths for the sake of
|
||||
compatibility with GNU ld.
|
||||
|
||||
* `--warn-common`, `--no-warn-common`:
|
||||
Warn about common symbols.
|
||||
|
||||
* `--warn-once`:
|
||||
Only warn once for each undefined symbol instead of warn for each relocation
|
||||
referring an undefined symbol.
|
||||
|
||||
* `--warn-unresolved-symbols`, `--error-unresolved-symbols`:
|
||||
Normally, the linker reports an error for unresolved symbols.
|
||||
`--warn-unresolved-symbols` option turns it into a warning.
|
||||
`--error-unresolved-symbols` option restores the default behavior.
|
||||
|
||||
* `--whole-archive`, `--no-whole-archive`:
|
||||
When archive files (`.a` files) are given to the linker, only object files
|
||||
that are needed to resolve undefined symbols are extracted from them and
|
||||
linked to an output file. `--whole-archive` changes that behavior for
|
||||
subsequent archives so that the linker extracts all object files and links
|
||||
them to an output. For example, if you are creating a shared object file and
|
||||
you want to include all archive members to the output, you should pass
|
||||
`--whole-archive`. `--no-whole-archive` restores the default behavior for
|
||||
subsequent archives.
|
||||
|
||||
* `--wrap`=_symbol_:
|
||||
Make _symbol_ be resolved to `__wrap_`_symbol_. The original symbol can be
|
||||
resolved as `__real_`_symbol_. This option is typically used for wrapping an
|
||||
existing function.
|
||||
|
||||
* `-z cet-report`=[ `warning` | `error` | `none` ]:
|
||||
Intel Control-flow Enforcement Technology (CET) is a new x86 feature
|
||||
available since Tiger Lake which is released in 2020. It defines new
|
||||
instructions to harden security to protect programs from control hijacking
|
||||
attacks. You can tell the compiler to use the feature by specifying the
|
||||
`-fcf-protection` flag.
|
||||
|
||||
`-z cet-report` flag is used to make sure that all object files were
|
||||
compiled with a correct `-fcf-protection` flag. If `warning` or `error` are
|
||||
given, `mold` prints out a warning or an error message if an object file was
|
||||
not compiled with the compiler flag.
|
||||
|
||||
`mold` looks for `GNU_PROPERTY_X86_FEATURE_1_IBT` bit and
|
||||
`GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in `.note.gnu.property` section to
|
||||
determine whether or not an object file was compiled with `-fcf-protection`.
|
||||
|
||||
* `-z now`, `-z lazy`:
|
||||
By default, functions referring to other ELF modules are resolved by the
|
||||
dynamic linker when they are called for the first time. `-z now` marks an
|
||||
executable or a shared library file so that all dynamic symbols are resolved
|
||||
when a file is loaded to memory. `-z lazy` restores the default behavior.
|
||||
|
||||
* `-z origin`:
|
||||
Mark object requiring immediate `$ORIGIN` processing at runtime.
|
||||
|
||||
* `-z ibt`:
|
||||
Turn on `GNU_PROPERTY_X86_FEATURE_1_IBT` bit in `.note.gnu.property` section
|
||||
to indicate that the output uses IBT-enabled PLT. This option implies `-z
|
||||
ibtplt`.
|
||||
|
||||
* `-z ibtplt`:
|
||||
Generate Intel Branch Tracking (IBT)-enabled PLT which is the default on
|
||||
x86-64. This is the default.
|
||||
|
||||
* `-z execstack`, `-z noexecstack`:
|
||||
By default, the pages for the stack area (i.e. the pages where local
|
||||
variables reside) are not executable for security reasons. `-z execstack`
|
||||
makes it executable. `-z noexecstack` restores the default behavior.
|
||||
|
||||
* `-z keep-text-section-prefix`, `-z nokeep-text-section-prefix`:
|
||||
Keep `.text.hot`, `.text.unknown`, `.text.unlikely`, `.text.startup`, and
|
||||
`.text.exit` as separate sections in the final binary instead of merging
|
||||
them as `.text`.
|
||||
|
||||
* `-z relro`, `-z norelro`:
|
||||
Some sections such as `.dynamic` have to be writable only during an
|
||||
executable or a shared library file is being loaded to memory. Once the
|
||||
dynamic linker finishes its job, such sections won't be mutated by anyone.
|
||||
As a security mitigation, it is preferred to make such segments read-only
|
||||
during program execution.
|
||||
|
||||
`-z relro` puts such sections into a special segment called `relro`. The
|
||||
dynamic linker makes a relro segment read-only after it finishes its job.
|
||||
|
||||
By default, `mold` generates a relro segment. `-z norelro` disables the
|
||||
feature.
|
||||
|
||||
* `-z separate-loadable-segments`, `-z separate-code`, `-z noseparate-code`:
|
||||
If one memory page contains multiple segments, the page protection bits are
|
||||
set in such a way that the needed attributes (writable or executable) are
|
||||
satisfied for all segments. This usually happens at a boundary of two
|
||||
segments with two different attributes.
|
||||
|
||||
`separate-loadable-segments` adds paddings between segments with different
|
||||
attributes so that they do not share the same page. This is the default.
|
||||
|
||||
`separate-code` adds paddings only between executable and non-executable
|
||||
segments.
|
||||
|
||||
`noseparate-code` does not add any paddings between segments.
|
||||
|
||||
* `-z defs`, `-z nodefs`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `-z shstk`:
|
||||
Enforce shadow stack by turning `GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in
|
||||
`.note.gnu.property` output section. Shadow stack is part of Intel
|
||||
Control-flow Enforcement Technology (CET), which is available since Tiger
|
||||
Lake (2020).
|
||||
|
||||
* `-z text`, `-z notext`, `-z textoff`:
|
||||
`mold` by default reports an error if dynamic relocations are created in
|
||||
read-only sections. If `-z notext` or `-z textoff` are given, `mold` creates
|
||||
such dynamic relocations without reporting an error. `-z text` restores the
|
||||
default behavior.
|
||||
|
||||
* `-z max-page-size`=_number_:
|
||||
Some CPU ISAs support multiple different memory page sizes. This option
|
||||
specifies the maximum page size that an output binary can run on. The
|
||||
default value is 4 KiB for i386, x86-64, and RISC-V, and 64 KiB for ARM64.
|
||||
|
||||
* `-z nodefaultlib`:
|
||||
Make the dynamic loader ignore default search paths.
|
||||
|
||||
* `-z nodelete`:
|
||||
Mark DSO non-deletable at runtime.
|
||||
|
||||
* `-z nodlopen`:
|
||||
Mark DSO not available to dlopen(3). This option makes it possible for the
|
||||
linker to optimize thread-local variable accesses by rewriting instructions
|
||||
for some targets.
|
||||
|
||||
* `-z nodump`:
|
||||
Mark DSO not available to dldump(3).
|
||||
|
||||
* `-z nocopyreloc`:
|
||||
Do not create copy relocations.
|
||||
|
||||
* `-z initfirst`:
|
||||
Mark DSO to be initialized first at runtime.
|
||||
|
||||
* `-z interpose`:
|
||||
Mark object to interpose all DSOs but executable.
|
||||
|
||||
* `-(`, `-)`, `-EL`, `-O`_number_, `--allow-shlib-undefined`, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-allow-shlib-undefined`, `--no-copy-dt-needed-entries`, `--no-fatal-warnings`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
|
||||
Ignored
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Currently, any value other than 1 is silently ignored.
|
||||
|
||||
* `MOLD_DEBUG`:
|
||||
If this variable is set to a non-empty string, `mold` embeds its
|
||||
command-line options in the output file's `.comment` section.
|
||||
|
||||
* `MOLD_REPRO`:
|
||||
Setting this variable to a non-empty string has the same effect as passing
|
||||
the `--repro` option.
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
gold(1), ld(1), elf(5), ld.so(8)
|
||||
|
||||
## AUTHOR
|
||||
|
||||
Rui Ueyama <ruiu@cs.stanford.edu>
|
||||
|
||||
## BUGS
|
||||
|
||||
Report bugs to <https://github.com/rui314/mold/issues>.
|
||||
+21
-33
@@ -166,17 +166,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -213,16 +208,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on Alpha";
|
||||
|
||||
@@ -231,29 +221,30 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_LITERAL:
|
||||
if (rel.r_addend) {
|
||||
sym.flags.fetch_or(NEEDS_ALPHA_GOT, std::memory_order_relaxed);
|
||||
if (rel.r_addend)
|
||||
ctx.extra.got->add_symbol(sym, rel.r_addend);
|
||||
} else {
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_BRSGP:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ALPHA_TLSGD:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ALPHA_TLSLDM:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ALPHA_GOTTPREL:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ALPHA_TPRELHI:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
case R_ALPHA_TPRELLO:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
case R_ALPHA_LITUSE:
|
||||
@@ -263,7 +254,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ALPHA_GPRELLOW:
|
||||
case R_ALPHA_DTPRELHI:
|
||||
case R_ALPHA_DTPRELLO:
|
||||
case R_ALPHA_TPRELLO:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -273,16 +263,15 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
// An R_ALPHA_LITERAL relocation may request the linker to create a GOT
|
||||
// entry for an external symbol with a non-zero addend. This is an unusual
|
||||
// request which we cannot find in any other targets.
|
||||
// request which is not found in any other targets.
|
||||
//
|
||||
// Referring an external symbol with a non-zero addend is a bad practice.
|
||||
// To see why, assume that we have two R_ALPHA_LITERAL relocs to symbol X
|
||||
// with addend 8 and 16. To satisfy that request, we need to create not
|
||||
// only one but two GOT entries for symbol X with different addends.
|
||||
// Referring an external symbol with a non-zero addend is a bad practice
|
||||
// because we need to create as many dynamic relocations as the number of
|
||||
// distinctive addends for the same symbol.
|
||||
//
|
||||
// We don't want to mess up the implementation of the common GOT section
|
||||
// for Alpha. So we create another GOT-like section, .alpha_got. Any GOT
|
||||
// entry for an R_ALPHA_LITERAL reloc with a non-zero addends is created
|
||||
// entry for an R_ALPHA_LITERAL reloc with a non-zero addend is created
|
||||
// not in .got but in .alpha_got.
|
||||
//
|
||||
// Since .alpha_got entries are accessed relative to GP, .alpha_got
|
||||
@@ -304,9 +293,9 @@ u64 AlphaGotSection::get_addr(Symbol<E> &sym, i64 addend) {
|
||||
return this->shdr.sh_addr + (it - entries.begin()) * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) {
|
||||
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) const {
|
||||
i64 n = 0;
|
||||
for (Entry &e : entries)
|
||||
for (const Entry &e : entries)
|
||||
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
|
||||
n++;
|
||||
return n;
|
||||
@@ -319,9 +308,8 @@ void AlphaGotSection::finalize() {
|
||||
}
|
||||
|
||||
void AlphaGotSection::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
|
||||
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < entries.size(); i++) {
|
||||
Entry &e = entries[i];
|
||||
|
||||
+99
-116
@@ -2,7 +2,7 @@
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// thumb is a subset of ARM, so not all ARM instructions are
|
||||
// Thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
@@ -12,14 +12,16 @@
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch mode.
|
||||
// linker even has to emit interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
// byte boundaries. So the least significant bit of a function address
|
||||
// is always 0.
|
||||
//
|
||||
// You can distinguish Thumb functions from ARM functions by looking
|
||||
// at the least significant bit (LSB) of its "address". If LSB is 0,
|
||||
// it's ARM; otherwise, Thumb.
|
||||
// To distinguish Thumb functions from ARM fucntions, the LSB of a
|
||||
// function address is repurposed as a boolean flag. If the LSB is 0,
|
||||
// the function referred to by the address is encoded in ARM;
|
||||
// otherwise, Thumb.
|
||||
//
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, `foo` is a function using Thumb instructions whose address
|
||||
@@ -42,8 +44,6 @@ using E = ARM32;
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_NONE:
|
||||
return 0;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_TARGET1:
|
||||
@@ -76,8 +76,9 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
}
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_TLS_CALL:
|
||||
return sign_extend(*(ul32 *)loc & 0x00ff'ffff, 23) << 2;
|
||||
return sign_extend(*(ul32 *)loc, 23) << 2;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
@@ -100,7 +101,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
return sign_extend(val, 15);
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +168,8 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
write_thm_b_imm(loc, val);
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
@@ -247,17 +248,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks =
|
||||
output_section->thunks;
|
||||
|
||||
auto get_trampoline_addr = [&](u64 addr) {
|
||||
for (;;) {
|
||||
assert(!thunks.empty());
|
||||
i64 disp = output_section->shdr.sh_addr + thunks[0]->offset - addr;
|
||||
auto get_tls_trampoline_addr = [&, i = 0](u64 addr) mutable {
|
||||
for (; i < output_section->thunks.size(); i++) {
|
||||
i64 disp = output_section->shdr.sh_addr + output_section->thunks[i]->offset -
|
||||
addr;
|
||||
if (is_jump_reachable(disp))
|
||||
return disp;
|
||||
thunks = thunks.subspan(1);
|
||||
}
|
||||
unreachable();
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -294,9 +292,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
@@ -304,6 +299,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
i64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
@@ -332,8 +330,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32: {
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like THM_CALL, ARM_CALL relocation refers either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
@@ -341,45 +343,53 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
|
||||
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
*(ul32 *)loc = 0xfa00'0000; // BLX
|
||||
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// These relocs refers a B (unconditional branch) instruction.
|
||||
// Unlike BL or BLX, we can't rewrite B to BX in place when the
|
||||
// processor mode switch is required because BX doesn't takes an
|
||||
// immediate; it takes only a register. So if mode switch is
|
||||
// required, we jump to a linker-synthesized thunk which does the
|
||||
// job with a longer code sequence.
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T)
|
||||
*(ul32 *)loc = 0xfa00'0000 | (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
else
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
if (!is_jump_reachable(val) || T)
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
|
||||
// takes an immediate; it takes only a register. So if mode switch
|
||||
// is required, we jump to a linker-synthesized thunk which constructs
|
||||
// a branch destination in a register and branches to that address.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || T)
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP11: {
|
||||
case R_ARM_THM_JUMP11:
|
||||
assert(T);
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc &= 0xf800;
|
||||
*(ul16 *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 19), 1 << 19);
|
||||
@@ -398,18 +408,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
} else {
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || !T)
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || !T)
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
@@ -419,12 +431,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_PREL31: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc &= 0x8000'0000;
|
||||
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
break;
|
||||
@@ -469,7 +480,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_tls_trampoline_addr(P + 8), 25, 2);
|
||||
} else {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0xe320'f000;
|
||||
@@ -477,7 +488,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_trampoline_addr(P + 4), 4);
|
||||
u64 val = align_to(get_tls_trampoline_addr(P + 4), 4);
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else {
|
||||
@@ -497,17 +508,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -546,18 +552,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
@@ -568,16 +569,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
@@ -585,17 +586,20 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
@@ -607,7 +611,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
case R_ARM_V4BX:
|
||||
@@ -668,7 +671,13 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function sorts .ARM.exidx records.
|
||||
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_arm_exidx_section");
|
||||
|
||||
OutputSection<E> *osec = find_section(ctx, SHT_ARM_EXIDX);
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
@@ -688,11 +697,11 @@ static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
ul32 val;
|
||||
};
|
||||
|
||||
if (osec.shdr.sh_size % sizeof(Entry))
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
|
||||
Entry *ent = (Entry *)(ctx.buf + osec.shdr.sh_offset);
|
||||
i64 num_entries = osec.shdr.sh_size / sizeof(Entry);
|
||||
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
|
||||
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by addresses, we first translate them so that the addresses
|
||||
@@ -719,38 +728,12 @@ static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
}
|
||||
|
||||
void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_arm_exidx_section");
|
||||
|
||||
auto find_exidx = [&]() -> OutputSection<E> * {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
OutputSection<E> *exidx = find_exidx();
|
||||
if (!exidx)
|
||||
return;
|
||||
|
||||
// Sort .ARM.exidx contents
|
||||
sort_exidx(ctx, *exidx);
|
||||
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (ctx.shdr) {
|
||||
auto find_text = [&]() -> Chunk<E> * {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->name == ".text")
|
||||
return chunk;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (Chunk<E> *text = find_text()) {
|
||||
exidx->shdr.sh_link = text->shndx;
|
||||
if (Chunk<E> *text = find_section(ctx, ".text")) {
|
||||
osec->shdr.sh_link = text->shndx;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
+134
-69
@@ -123,6 +123,24 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_adrp(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
|
||||
}
|
||||
|
||||
static bool is_ldr(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
|
||||
}
|
||||
|
||||
static bool is_add(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -158,6 +176,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
@@ -172,57 +191,82 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(val, 31, 16) << 5;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(val, 47, 32) << 5;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
check(S + A, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
if (sym.has_got(ctx)) {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
|
||||
u32 reg = bits(*(ul32 *)loc, 4, 0);
|
||||
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
|
||||
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
|
||||
// :lo12: foo` instruction pair to materialize a PC-relative address
|
||||
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
|
||||
// if foo is in PC ± 1 MiB.
|
||||
if (ctx.arg.relax && i + 1 < rels.size() &&
|
||||
sign_extend(S + A - P - 4, 20) == S + A - P - 4) {
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_addend == rel.r_addend &&
|
||||
is_adrp(loc) &&
|
||||
is_add(loc + 4)) {
|
||||
u32 reg1 = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 reg2 = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (reg1 == reg2) {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
|
||||
write_adr(loc + 4, S + A - P - 4);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, val);
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
@@ -238,25 +282,23 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(val, 20, 2) << 5;
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_AARCH64_PREL16:
|
||||
check(S + A - P, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
case R_AARCH64_PREL32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -309,6 +351,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
check(S + A - ctx.tp_addr, 0, 1 << 12);
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
@@ -368,17 +413,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -424,48 +464,81 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
|
||||
// symbol's address from GOT. If the GOT value is a link-time
|
||||
// constant, we may be able to rewrite the ADRP+LDR instruction pair
|
||||
// with an ADRP+ADD, eliminating a GOT memory load.
|
||||
if (ctx.arg.relax && sym.is_relative() && !sym.is_imported &&
|
||||
!sym.is_ifunc() && i + 1 < rels.size()) {
|
||||
// ADRP+LDR must be consecutive and use the same register to relax.
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel.r_addend == 0 &&
|
||||
rel2.r_addend == 0 &&
|
||||
is_adrp(loc) &&
|
||||
is_ldr(loc + 4)) {
|
||||
u32 rd = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
|
||||
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (rd == rn && rn == rt) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
case R_AARCH64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
@@ -486,14 +559,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
break;
|
||||
|
||||
+107
-125
@@ -31,7 +31,7 @@
|
||||
// assume that %ebx is set to .got. For PDE, we need to create position-
|
||||
// dependent PLT entries which don't use %ebx.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -42,8 +42,6 @@ using E = I386;
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return 0;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
return *loc;
|
||||
@@ -66,8 +64,9 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
return *(ul32 *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -141,6 +140,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
@@ -150,10 +150,9 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -203,6 +202,58 @@ static u32 relax_got32x(u8 *loc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Relax GD to LE
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32:
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Relax LD to LE
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -234,33 +285,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
case R_386_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
@@ -298,32 +341,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
@@ -331,34 +349,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
@@ -398,17 +389,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -425,36 +411,28 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
case R_386_PC8:
|
||||
check(S + A, -(1 << 7), 1 << 7);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_PC16:
|
||||
check(S + A, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
@@ -489,19 +467,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
@@ -518,23 +491,24 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOTPC:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_GOT32X: {
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_got32x(loc - 2);
|
||||
// We always want to relax GOT32X because static PIE doesn't
|
||||
// work without it.
|
||||
bool do_relax = !sym.is_imported && sym.is_relative() &&
|
||||
relax_got32x(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
}
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (i + 1 == rels.size())
|
||||
@@ -545,10 +519,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && !ctx.arg.shared && !sym.is_imported))
|
||||
i++;
|
||||
else
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (i + 1 == rels.size())
|
||||
@@ -559,14 +536,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsld(ctx))
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
|
||||
+13
-21
@@ -12,7 +12,7 @@
|
||||
// access instructions and therefore can support position-independent
|
||||
// code without too much hassle.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/m68k.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -221,17 +221,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -262,16 +257,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
@@ -294,35 +284,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_68K_GOTOFF32:
|
||||
case R_68K_GOTOFF16:
|
||||
case R_68K_GOTOFF8:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_68K_PLT32:
|
||||
case R_68K_PLT16:
|
||||
case R_68K_PLT8:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
case R_68K_TLS_GD16:
|
||||
case R_68K_TLS_GD8:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
case R_68K_TLS_LDM16:
|
||||
case R_68K_TLS_LDM8:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
case R_68K_TLS_IE16:
|
||||
case R_68K_TLS_IE8:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static constexpr i64 BIAS = 0x8000;
|
||||
|
||||
template <typename E>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {}
|
||||
|
||||
template <typename E>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const U32<E> insn[] = {
|
||||
0xdf99'0000, // ld t9, 0(gp)
|
||||
0x03e0'7825, // move t3, ra
|
||||
0x0320'f809, // jalr t9
|
||||
0x6418'0000, // daddiu t8, zero, 0
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(U32<E> *)buf |= sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx);
|
||||
*(U32<E> *)(buf + 12) |= sym.get_dynsym_idx(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <typename E>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_MIPS_64:
|
||||
*(U64<E> *)loc = val;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 GP = ctx._gp->get_addr(ctx);
|
||||
u64 GP0 = file.mips_gp0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write_combined = [&](u64 val) {
|
||||
if (rel.r_type2 == R_MIPS_64 && rel.r_type3 == R_NONE) {
|
||||
*(U64<E> *)loc = val;
|
||||
} else if (rel.r_type2 == R_MIPS_SUB && rel.r_type3 == R_MIPS_HI16) {
|
||||
*(U32<E> *)loc |= ((-val + BIAS) >> 16) & 0xffff;
|
||||
} else if (rel.r_type2 == R_MIPS_SUB && rel.r_type3 == R_MIPS_LO16) {
|
||||
*(U32<E> *)loc |= -val & 0xffff;
|
||||
} else {
|
||||
Error(ctx) << *this << ": unsupported relocation combination: "
|
||||
<< rel_to_string<E>(rel.r_type) << " "
|
||||
<< rel_to_string<E>(rel.r_type2) << " "
|
||||
<< rel_to_string<E>(rel.r_type3);
|
||||
}
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
|
||||
*(U32<E> *)loc = val;
|
||||
else
|
||||
write_combined(val);
|
||||
};
|
||||
|
||||
auto write_hi16 = [&](u64 val) {
|
||||
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
|
||||
*(U32<E> *)loc |= ((val + BIAS) >> 16) & 0xffff;
|
||||
else
|
||||
write_combined(val);
|
||||
};
|
||||
|
||||
auto write_lo16 = [&](u64 val) {
|
||||
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
|
||||
*(U32<E> *)loc |= val & 0xffff;
|
||||
else
|
||||
write_combined(val);
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_MIPS_64:
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
case R_MIPS_GPREL16:
|
||||
if (sym.is_local(ctx))
|
||||
write_lo16(S + A + GP0 - GP);
|
||||
else
|
||||
write_lo16(S + A - GP);
|
||||
break;
|
||||
case R_MIPS_GPREL32:
|
||||
write32(S + A + GP0 - GP);
|
||||
break;
|
||||
case R_MIPS_GOT_DISP:
|
||||
case R_MIPS_CALL16:
|
||||
write_lo16(ctx.extra.got->get_got_addr(ctx, sym, A) - GP);
|
||||
break;
|
||||
case R_MIPS_GOT_PAGE:
|
||||
write_lo16(ctx.extra.got->get_gotpage_got_addr(ctx, sym, A) - GP);
|
||||
break;
|
||||
case R_MIPS_GOT_OFST:
|
||||
write_lo16(S + A - ctx.extra.got->get_gotpage_page_addr(ctx, sym, A));
|
||||
break;
|
||||
case R_MIPS_JALR:
|
||||
break;
|
||||
case R_MIPS_TLS_TPREL_HI16:
|
||||
write_hi16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_TPREL_LO16:
|
||||
write_lo16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_GOTTPREL:
|
||||
write_lo16(ctx.extra.got->get_gottp_addr(ctx, sym) - GP);
|
||||
break;
|
||||
case R_MIPS_TLS_DTPREL_HI16:
|
||||
write_hi16(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_DTPREL_LO16:
|
||||
write_lo16(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_MIPS_TLS_GD:
|
||||
write_lo16(ctx.extra.got->get_tlsgd_addr(ctx, sym) + A - GP);
|
||||
break;
|
||||
case R_MIPS_TLS_LDM:
|
||||
write_lo16(ctx.extra.got->get_tlsld_addr(ctx) + A - GP);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(loc, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_MIPS_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_MIPS_GOT_DISP:
|
||||
case R_MIPS_CALL16: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
ctx.extra.got->got_syms.push_back({&sym, rel.r_addend});
|
||||
break;
|
||||
}
|
||||
case R_MIPS_GOT_PAGE: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
ctx.extra.got->gotpage_syms.push_back({&sym, rel.r_addend});
|
||||
break;
|
||||
}
|
||||
case R_MIPS_TLS_GOTTPREL: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
assert(rel.r_addend == 0);
|
||||
ctx.extra.got->gottp_syms.push_back(&sym);
|
||||
break;
|
||||
}
|
||||
case R_MIPS_TLS_TPREL_HI16:
|
||||
case R_MIPS_TLS_TPREL_LO16:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_MIPS_TLS_GD: {
|
||||
std::scoped_lock lock(ctx.extra.got->mu);
|
||||
assert(rel.r_addend == 0);
|
||||
ctx.extra.got->tlsgd_syms.push_back(&sym);
|
||||
break;
|
||||
}
|
||||
case R_MIPS_TLS_LDM:
|
||||
ctx.extra.got->needs_tlsld = true;
|
||||
break;
|
||||
case R_MIPS_64:
|
||||
case R_MIPS_GPREL16:
|
||||
case R_MIPS_GPREL32:
|
||||
case R_MIPS_GOT_OFST:
|
||||
case R_MIPS_JALR:
|
||||
case R_MIPS_TLS_DTPREL_HI16:
|
||||
case R_MIPS_TLS_DTPREL_LO16:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool MipsGotSection<E>::SymbolAddend::operator<(const SymbolAddend &other) const {
|
||||
return std::tuple(sym->file->priority, sym->sym_idx, addend) <
|
||||
std::tuple(other.sym->file->priority, other.sym->sym_idx, other.addend);
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static bool compare(const Symbol<E> *a, const Symbol<E> *b) {
|
||||
return std::tuple(a->file->priority, a->sym_idx) <
|
||||
std::tuple(b->file->priority, b->sym_idx);
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::SymbolAddend::get_addr(Context<E> &ctx, i64 flags) const {
|
||||
return sym->get_addr(ctx, flags) + addend;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64
|
||||
MipsGotSection<E>::get_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const {
|
||||
auto it = std::lower_bound(got_syms.begin(), got_syms.end(),
|
||||
SymbolAddend{&sym, addend});
|
||||
assert(it != got_syms.end());
|
||||
i64 idx = it - got_syms.begin();
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_gotpage_got_addr(Context<E> &ctx, Symbol<E> &sym,
|
||||
i64 addend) const {
|
||||
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
|
||||
SymbolAddend{&sym, addend});
|
||||
assert(it != gotpage_syms.end());
|
||||
i64 idx = got_syms.size() + (it - gotpage_syms.begin());
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_gotpage_page_addr(Context<E> &ctx, Symbol<E> &sym,
|
||||
i64 addend) const {
|
||||
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
|
||||
SymbolAddend{&sym, addend});
|
||||
assert(it != gotpage_syms.end());
|
||||
return it->get_addr(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const {
|
||||
auto it = std::lower_bound(tlsgd_syms.begin(), tlsgd_syms.end(),
|
||||
&sym, compare<E>);
|
||||
assert(it != tlsgd_syms.end());
|
||||
i64 idx = got_syms.size() + gotpage_syms.size() + (it - tlsgd_syms.begin()) * 2;
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const {
|
||||
auto it = std::lower_bound(gottp_syms.begin(), gottp_syms.end(),
|
||||
&sym, compare<E>);
|
||||
assert(it != gottp_syms.end());
|
||||
i64 idx = got_syms.size() + gotpage_syms.size() + tlsgd_syms.size() * 2 +
|
||||
(it - gottp_syms.begin());
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 MipsGotSection<E>::get_tlsld_addr(Context<E> &ctx) const {
|
||||
assert(needs_tlsld);
|
||||
i64 idx = got_syms.size() + gotpage_syms.size() + tlsgd_syms.size() * 2 +
|
||||
gottp_syms.size();
|
||||
return this->shdr.sh_addr + idx * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<typename MipsGotSection<E>::GotEntry>
|
||||
MipsGotSection<E>::get_got_entries(Context<E> &ctx) const {
|
||||
std::vector<GotEntry> entries;
|
||||
auto add = [&](GotEntry ent) { entries.push_back(ent); };
|
||||
|
||||
// Create GOT entries for ordinary symbols
|
||||
for (const SymbolAddend &ent : got_syms) {
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
if (ent.sym->is_imported) {
|
||||
add({0, E::R_GLOB_DAT, ent.sym});
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we know an address at link-time, fill that GOT entry now.
|
||||
// It may need a base relocation, though.
|
||||
if (ctx.arg.pic && ent.sym->is_relative())
|
||||
add({ent.get_addr(ctx, NO_PLT), E::R_RELATIVE});
|
||||
else
|
||||
add({ent.get_addr(ctx, NO_PLT)});
|
||||
}
|
||||
|
||||
// Create GOT entries for GOT_PAGE and GOT_OFST relocs
|
||||
for (const SymbolAddend &ent : gotpage_syms) {
|
||||
if (ctx.arg.pic && ent.sym->is_relative())
|
||||
add({ent.get_addr(ctx), E::R_RELATIVE});
|
||||
else
|
||||
add({ent.get_addr(ctx)});
|
||||
}
|
||||
|
||||
// Create GOT entries for TLVs
|
||||
for (Symbol<E> *sym : tlsgd_syms) {
|
||||
if (sym->is_imported) {
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
add({0, E::R_DTPMOD, sym});
|
||||
add({0, E::R_DTPOFF, sym});
|
||||
} else if (ctx.arg.shared) {
|
||||
// If we are creating a shared library, we know the TLV's offset
|
||||
// within the current TLS block. We don't know the module ID though.
|
||||
add({0, E::R_DTPMOD});
|
||||
add({sym->get_addr(ctx) - ctx.dtp_addr});
|
||||
} else {
|
||||
// If we are creating an executable, we know both the module ID and the
|
||||
// offset. Module ID 1 indicates the main executable.
|
||||
add({1});
|
||||
add({sym->get_addr(ctx) - ctx.dtp_addr});
|
||||
}
|
||||
}
|
||||
|
||||
for (Symbol<E> *sym : gottp_syms) {
|
||||
if (sym->is_imported) {
|
||||
// If we know nothing about the symbol, let the dynamic linker
|
||||
// to fill the GOT entry.
|
||||
add({0, E::R_TPOFF, sym});
|
||||
} else if (ctx.arg.shared) {
|
||||
// If we know the offset within the current thread vector,
|
||||
// let the dynamic linker to adjust it.
|
||||
add({sym->get_addr(ctx) - ctx.tls_begin, E::R_TPOFF});
|
||||
} else {
|
||||
// Otherwise, we know the offset from the thread pointer (TP) at
|
||||
// link-time, so we can fill the GOT entry directly.
|
||||
add({sym->get_addr(ctx) - ctx.tp_addr});
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_tlsld) {
|
||||
if (ctx.arg.shared)
|
||||
add({0, E::R_DTPMOD});
|
||||
else
|
||||
add({1}); // 1 means the main executable
|
||||
add({0});
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void MipsGotSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// Finalize got_syms
|
||||
sort(got_syms);
|
||||
remove_duplicates(got_syms);
|
||||
this->shdr.sh_size = got_syms.size() * sizeof(Word<E>);
|
||||
|
||||
// Finalize gotpage_syms
|
||||
sort(gotpage_syms);
|
||||
remove_duplicates(gotpage_syms);
|
||||
this->shdr.sh_size += gotpage_syms.size() * sizeof(Word<E>);
|
||||
|
||||
// Finalize tlsgd_syms
|
||||
sort(tlsgd_syms, compare<E>);
|
||||
remove_duplicates(tlsgd_syms);
|
||||
this->shdr.sh_size += tlsgd_syms.size() * 2 * sizeof(Word<E>);
|
||||
|
||||
// Finalize gottp_syms
|
||||
sort(gottp_syms, compare<E>);
|
||||
remove_duplicates(gottp_syms);
|
||||
this->shdr.sh_size += gottp_syms.size() * sizeof(Word<E>);
|
||||
|
||||
if (needs_tlsld)
|
||||
this->shdr.sh_size += 2 * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 MipsGotSection<E>::get_reldyn_size(Context<E> &ctx) const {
|
||||
i64 n = 0;
|
||||
for (GotEntry &ent : get_got_entries(ctx))
|
||||
if (ent.r_type != R_NONE)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void MipsGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
U64<E> *buf = (U64<E> *)(ctx.buf + this->shdr.sh_offset);
|
||||
memset(buf, 0, this->shdr.sh_size);
|
||||
|
||||
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; GotEntry &ent : get_got_entries(ctx)) {
|
||||
if (ent.r_type != R_NONE)
|
||||
*dynrel++ = ElfRel<E>(this->shdr.sh_addr + i * sizeof(Word<E>),
|
||||
ent.r_type,
|
||||
ent.sym ? ent.sym->get_dynsym_idx(ctx) : 0,
|
||||
ent.val);
|
||||
buf[i++] = ent.val;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void write_plt_header(Context<E> &, u8 *); \
|
||||
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void \
|
||||
EhFrameSection<E>::apply_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
|
||||
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::scan_relocations(Context<E> &); \
|
||||
template class MipsGotSection<E>;
|
||||
|
||||
INSTANTIATE(MIPS64LE);
|
||||
INSTANTIATE(MIPS64BE);
|
||||
|
||||
} // namespace mold::elf
|
||||
+25
-39
@@ -30,7 +30,7 @@
|
||||
// 0x10000 (or 65536) bytes.
|
||||
//
|
||||
// Since each object file has its own .got2, %r30 refers to different
|
||||
// places in a merged .got2 for two functions that are came from different
|
||||
// places in a merged .got2 for two functions that came from different
|
||||
// input files. Therefore, %r30 makes sense only within a single function.
|
||||
//
|
||||
// Technically, we can reuse a %r30 value in our PLT if we create a PLT
|
||||
@@ -38,7 +38,7 @@
|
||||
// doesn't seems to be worth its complexity. Our PLT simply doesn't rely
|
||||
// on a %r30 value.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/ppc32-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/ppc32.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -46,15 +46,11 @@ namespace mold::elf {
|
||||
|
||||
using E = PPC32;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
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 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
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; }
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
@@ -157,9 +153,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 got2 = 0;
|
||||
if (file.ppc32_got2)
|
||||
got2 = file.ppc32_got2->output_section->shdr.sh_addr + file.ppc32_got2->offset;
|
||||
u64 GOT2 = file.ppc32_got2 ? file.ppc32_got2->get_addr() : 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -201,16 +195,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc |= bits(S + A, 31, 2) << 2;
|
||||
break;
|
||||
case R_PPC_PLT16_LO:
|
||||
*(ub16 *)loc = lo(G + GOT - got2 - A);
|
||||
*(ub16 *)loc = lo(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT16_HI:
|
||||
*(ub16 *)loc = hi(G + GOT - got2 - A);
|
||||
*(ub16 *)loc = hi(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT16_HA:
|
||||
*(ub16 *)loc = ha(G + GOT - got2 - A);
|
||||
*(ub16 *)loc = ha(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT32:
|
||||
*(ub32 *)loc = G + GOT - got2 - A;
|
||||
*(ub32 *)loc = G + GOT - A - GOT2;
|
||||
break;
|
||||
case R_PPC_REL14:
|
||||
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
|
||||
@@ -299,17 +293,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -341,18 +330,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
@@ -385,30 +369,32 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC_PLT16_HI:
|
||||
case R_PPC_PLT16_HA:
|
||||
case R_PPC_PLT32:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC_REL24:
|
||||
case R_PPC_PLTREL24:
|
||||
case R_PPC_PLTREL32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC_GOT_TLSGD16:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC_GOT_TLSLD16:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC_GOT_TPREL16:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC_TPREL16_LO:
|
||||
case R_PPC_TPREL16_HI:
|
||||
case R_PPC_TPREL16_HA:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_LOCAL24PC:
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
case R_PPC_TLSLD:
|
||||
case R_PPC_TPREL16_LO:
|
||||
case R_PPC_TPREL16_HI:
|
||||
case R_PPC_TPREL16_HA:
|
||||
case R_PPC_DTPREL16_LO:
|
||||
case R_PPC_DTPREL16_HI:
|
||||
case R_PPC_DTPREL16_HA:
|
||||
|
||||
+98
-65
@@ -5,14 +5,15 @@
|
||||
//
|
||||
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
|
||||
// endianness. The most notable difference is, in ELFv1, a function
|
||||
// pointer doesn't directly refer the entry point of a function but
|
||||
// instead refers a data structure so-called "function descriptor".
|
||||
// pointer doesn't directly refer to the entry point of a function but
|
||||
// instead refers to a data structure so-called "function descriptor".
|
||||
//
|
||||
// The function descriptor is essentially a pair of a function entry point
|
||||
// address and a value that should be set to %r2 before calling that
|
||||
// function. There is actually a third member, but we don't need to know
|
||||
// what that is. In total, the function descriptor is 24 bytes long. Here
|
||||
// is why we need it.
|
||||
// function. There is also a third member for "the environment pointer for
|
||||
// languages such as Pascal and PL/1" according to the psABI, but it looks
|
||||
// like no one acutally uses it. In total, the function descriptor is 24
|
||||
// bytes long. Here is why we need it.
|
||||
//
|
||||
// PPC generally lacks PC-relative data access instructions. Position-
|
||||
// independent code sets GOT + 0x8000 to %r2 and access global variables
|
||||
@@ -25,8 +26,8 @@
|
||||
//
|
||||
// In this way, you can't call a function just by knowing the function's
|
||||
// entry point address. You also need to know a proper %r2 value for the
|
||||
// function. This is why a function pointer refers a tuple of an address
|
||||
// and a %r2 value.
|
||||
// function. This is why a function pointer refers to a tuple of an
|
||||
// address and a %r2 value.
|
||||
//
|
||||
// If a function call is made through PLT, PLT takes care of restoring %r2.
|
||||
// Therefore, the caller has to restore %r2 only for function calls
|
||||
@@ -39,8 +40,10 @@
|
||||
// few different addresses for different purposes. It may not only have an
|
||||
// entry point address but may also have PLT and/or GOT addresses.
|
||||
// In PPCV1, it may have an OPD address in addition to these. OPD address
|
||||
// is used for relocations that refers the address of a function as a
|
||||
// is used for relocations that refers to the address of a function as a
|
||||
// function pointer.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -51,15 +54,11 @@ namespace mold::elf {
|
||||
|
||||
using E = PPC64V1;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
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 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
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; }
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
@@ -93,9 +92,35 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub32 *)(buf + 0) = 0x3800'0000 | sym.get_plt_idx(ctx); // li %r0, PLT_INDEX
|
||||
*(ub32 *)(buf + 4) = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 idx = sym.get_plt_idx(ctx);
|
||||
|
||||
// The PPC64 ELFv1 ABI requires PLT entries to be vary in size depending
|
||||
// on their indices. Unlike other targets, .got.plt is filled not by us
|
||||
// but by the loader, so we don't have a control over where the initial
|
||||
// call to the PLT entry jumps to. So we need to strictly follow the PLT
|
||||
// section layout as the loader expect it to be.
|
||||
if (idx < 0x8000) {
|
||||
static const ub32 insn[] = {
|
||||
0x3800'0000, // li r0, PLT_INDEX
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= idx;
|
||||
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
|
||||
} else {
|
||||
static const ub32 insn[] = {
|
||||
0x3c00'0000, // lis r0, PLT_INDEX@high
|
||||
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= high(idx);
|
||||
loc[1] |= lo(idx);
|
||||
loc[2] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 8) & 0x00ff'ffff;
|
||||
}
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
@@ -169,12 +194,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ub16 *)loc = lo(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_DS: {
|
||||
i64 val = S + A - TOC;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= val & 0xfffc;
|
||||
case R_PPC64_TOC16_DS:
|
||||
check(S + A - TOC, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
@@ -194,6 +217,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -233,12 +259,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
@@ -263,17 +289,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -321,48 +342,45 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD,
|
||||
std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
|
||||
|
||||
// Any relocation except R_PPC64_REL24 is considered as an
|
||||
// address-taking relocation.
|
||||
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
|
||||
sym.flags.fetch_or(NEEDS_PPC_OPD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
@@ -375,8 +393,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
@@ -521,10 +537,10 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
// However, in reality, .opd isn't a normal input section. It needs many
|
||||
// special treatments as follows:
|
||||
//
|
||||
// 1. A function symbol refers not a .text but an .opd. Its address works
|
||||
// fine for address-taking relocations such as R_PPC64_ADDR64. However,
|
||||
// R_PPC64_REL24 (which is used for branch instruction) needs a
|
||||
// function's real address instead of the function's .opd address.
|
||||
// 1. A function symbol refers to not a .text but an .opd. Its address
|
||||
// works fine for address-taking relocations such as R_PPC64_ADDR64.
|
||||
// However, R_PPC64_REL24 (which is used for branch instruction) needs
|
||||
// a function's real address instead of the function's .opd address.
|
||||
// We need to read .opd contents to find out a function entry point
|
||||
// address to apply R_PPC64_REL24.
|
||||
//
|
||||
@@ -532,9 +548,9 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
// are taken. Just copying input .opd sections to an output would
|
||||
// produces lots of dead .opd entries.
|
||||
//
|
||||
// 3. In this design, all function symbols refer an .opd section, and that
|
||||
// doesn't work well with graph traversal optimizations such as garbage
|
||||
// collection or identical comdat folding. For example, garbage
|
||||
// 3. In this design, all function symbols refer to an .opd section, and
|
||||
// that doesn't work well with graph traversal optimizations such as
|
||||
// garbage collection or identical comdat folding. For example, garbage
|
||||
// collector would mark an .opd alive which in turn mark all functions
|
||||
// thatare referenced by .opd as alive, effectively keeping all
|
||||
// functions as alive.
|
||||
@@ -547,9 +563,9 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
//
|
||||
// So, in this function, we undo what the compiler did to .opd. We remove
|
||||
// function symbols from .opd and reattach them to their function entry
|
||||
// points. We also rewrite relocations that directly refer an input .opd
|
||||
// section so that they refer function symbols instead. We then mark input
|
||||
// .opd sections as dead.
|
||||
// points. We also rewrite relocations that directly refer to an input
|
||||
// .opd section so that they refer to function symbols instead. We then
|
||||
// mark input .opd sections as dead.
|
||||
//
|
||||
// After this function, we mark symbols with the NEEDS_PPC_OPD flag if the
|
||||
// symbol needs an .opd entry. We then create an output .opd just like we
|
||||
@@ -612,8 +628,8 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// When a function is exported, the dynamic symbol for the function should
|
||||
// refers the function's .opd entry. This function marks such symbols with
|
||||
// NEEDS_PPC_OPD.
|
||||
// refers to the function's .opd entry. This function marks such symbols
|
||||
// with NEEDS_PPC_OPD.
|
||||
void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
@@ -640,13 +656,30 @@ void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
this->shdr.sh_size += ENTRY_SIZE;
|
||||
}
|
||||
|
||||
i64 PPC64OpdSection::get_reldyn_size(Context<E> &ctx) const {
|
||||
if (ctx.arg.pic)
|
||||
return symbols.size() * 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
ElfRel<E> *rel = nullptr;
|
||||
if (ctx.arg.pic)
|
||||
rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
*buf++ = addr;
|
||||
*buf++ = ctx.extra.TOC->value;
|
||||
*buf++ = 0;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
u64 loc = sym->get_opd_addr(ctx);
|
||||
*rel++ = ElfRel<E>(loc, E::R_RELATIVE, 0, addr);
|
||||
*rel++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+175
-82
@@ -1,21 +1,29 @@
|
||||
// This file implements the PowerPC ELFv2 ABI which was standardized in
|
||||
// 2014. Modern little-endian PowerPC systems are based on this ABI.
|
||||
// The ABI is often referred to as "ppc64le". This shouldn't be confused
|
||||
// with "ppc64" which refers the original, big-endian PowerPC systems.
|
||||
// with "ppc64" which refers to the original, big-endian PowerPC systems.
|
||||
//
|
||||
// PPC64 is a bit tricky to support because PC-relative load/store
|
||||
// instructions are generally not available. Therefore, it's not easy
|
||||
// for position-independent code to load a value from, for example,
|
||||
// .got, as we can't do that with [PC + the offset to the .got entry].
|
||||
// instructions hadn't been available until Power10 which debuted in 2021.
|
||||
// Prior to Power10, it wasn't trivial for position-independent code (PIC)
|
||||
// to load a value from, for example, .got, as we can't do that with [PC +
|
||||
// the offset to the .got entry].
|
||||
//
|
||||
// We can get the program counter by the following four instructions
|
||||
// In the following, I'll explain how PIC is supported on pre-Power10
|
||||
// systems first and then explain what has changed with Power10.
|
||||
//
|
||||
//
|
||||
// Position-independent code on Power9 or earlier:
|
||||
//
|
||||
// We can get the program counter on older PPC64 systems with the
|
||||
// following four instructions
|
||||
//
|
||||
// mflr r1 // save the current link register to r1
|
||||
// bl .+4 // branch to the next instruction as if it were a function
|
||||
// mflr r0 // copy the return address to r0
|
||||
// mtlr r1 // restore the original link register value
|
||||
//
|
||||
// , but that's too expensive to do if we do this for each load/store.
|
||||
// , but it's too expensive to do if we do this for each load/store.
|
||||
//
|
||||
// As a workaround, most functions are compiled in such a way that r2 is
|
||||
// assumed to always contain the address of .got + 0x8000. With this, we
|
||||
@@ -27,9 +35,9 @@
|
||||
// calls are usually within the same ELF module, so this mechanism is
|
||||
// efficient.
|
||||
//
|
||||
// In PPC64, a function usually have two entry points, global and local.
|
||||
// The global entry point is usually 8 bytes precedes the local entry
|
||||
// point. In between is the following instructions:
|
||||
// A function compiled for pre-Power10 usually has two entry points,
|
||||
// global and local. The global entry point usually 8 bytes precedes
|
||||
// the local entry point. In between is the following instructions:
|
||||
//
|
||||
// addis r2, r12, .TOC.@ha
|
||||
// addi r2, r2, .TOC.@lo + 4;
|
||||
@@ -46,12 +54,31 @@
|
||||
// to that address. Then the callee computes its own TOC pointer using
|
||||
// r12.
|
||||
//
|
||||
//
|
||||
// Position-independent code on Power10:
|
||||
//
|
||||
// Power10 added 8-bytes-long instructions to the ISA. Some of them are
|
||||
// PC-relative load/store instructions that take 34 bits offsets.
|
||||
// Functions compiled with `-mcpu=power10` use these instructions for PIC.
|
||||
// r2 does not have a special meaning in such fucntions.
|
||||
//
|
||||
// When a fucntion compiled for Power10 calls a function that uses the TOC
|
||||
// pointer, we need to compute a correct value for TOC and set it to r2
|
||||
// before transferring the control to the callee. Thunks are responsible
|
||||
// for doing it.
|
||||
//
|
||||
// `_NOTOC` relocations such as `R_PPC64_REL24_NOTOC` indicate that the
|
||||
// callee does not use TOC (i.e. compiled with `-mcpu=power10`). If a
|
||||
// function using TOC is referenced via a `_NOTOC` relocation, that call
|
||||
// is made through a range extension thunk.
|
||||
//
|
||||
//
|
||||
// Note on section names: the PPC64 psABI uses a weird naming convention
|
||||
// which calls .got.plt .plt. We ignored that part because it's just
|
||||
// confusing. Since the runtime only cares about segments, we should be
|
||||
// able to name sections whatever we want.
|
||||
//
|
||||
// https://openpowerfoundation.org/specifications/64bitelfabi/
|
||||
// https://github.com/rui314/psabi/blob/main/ppc64v2.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -59,15 +86,15 @@ namespace mold::elf {
|
||||
|
||||
using E = PPC64V2;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
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 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
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);
|
||||
}
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
@@ -144,10 +171,11 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
|
||||
i64 val = sym.esym().ppc_local_entry;
|
||||
assert(val <= 7);
|
||||
if (val == 0 || val == 1)
|
||||
return 0;
|
||||
if (val == 7)
|
||||
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
|
||||
|
||||
if (val == 0 || val == 1)
|
||||
return 0;
|
||||
return 1 << val;
|
||||
}
|
||||
|
||||
@@ -168,13 +196,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
@@ -182,6 +203,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
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; };
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
@@ -199,22 +223,37 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = S + A - P + get_local_entry_offset(ctx, sym);
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.has_plt(ctx) || !sym.esym().preserves_r2()) {
|
||||
i64 val = r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
check(val, -(1 << 25), 1 << 25);
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
// If a callee is an external function, PLT saves %r2 to the
|
||||
// caller's r2 save slot. We need to restore it after function
|
||||
// return. To do so, there's usually a NOP as a placeholder
|
||||
// after a BL. 0x6000'0000 is a NOP.
|
||||
if (sym.has_plt(ctx) && *(ul32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
// The thunk saves %r2 to the caller's r2 save slot. We need to
|
||||
// restore it after function return. To do so, there's usually a
|
||||
// NOP as a placeholder after a BL. 0x6000'0000 is a NOP.
|
||||
if (*(ul32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
} else {
|
||||
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
}
|
||||
break;
|
||||
case R_PPC64_REL24_NOTOC:
|
||||
if (sym.has_plt(ctx) || sym.esym().uses_toc()) {
|
||||
i64 val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
} else {
|
||||
i64 val = S + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
}
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -236,38 +275,60 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(G + GOT - P);
|
||||
break;
|
||||
case R_PPC64_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(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);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(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);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(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);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_PLTCALL_NOTOC:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
@@ -284,17 +345,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -342,18 +398,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
@@ -363,21 +414,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_REL24_NOTOC:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
ctx.extra.is_power10 = true;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
@@ -388,10 +455,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_PLT16_HI:
|
||||
case R_PPC64_PLT16_LO:
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PCREL34:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_PLTCALL_NOTOC:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
@@ -400,6 +468,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL34:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -414,46 +483,70 @@ void RangeExtensionThunk<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[] = {
|
||||
// Save r2 to the r2 save slot reserved in the caller's stack frame
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
|
||||
// Jump to a PLT entry
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static const ul32 plt_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
||||
0xe580'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
// Jump to a local entry point
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static const ul32 local_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x0610'0000, // pla r12, foo@pcrel
|
||||
0x3980'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk_power10));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk_power10));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
i64 val = got - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(loc, plt_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(loc + 1) |= prefix34(got - get_addr(i) - 4);
|
||||
} else {
|
||||
i64 val = got - ctx.extra.TOC->value;
|
||||
memcpy(loc, plt_thunk, E::thunk_size);
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
}
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx) + get_local_entry_offset(ctx, sym) -
|
||||
ctx.extra.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(loc, local_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(loc + 1) |= prefix34(sym.get_addr(ctx) - get_addr(i) - 4);
|
||||
} else {
|
||||
i64 val = sym.get_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(loc, local_thunk, E::thunk_size);
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+177
-183
@@ -44,7 +44,7 @@
|
||||
// considered as an atomic unit. If we delete 4 bytes from the middle of a
|
||||
// section, all contents after that point needs to be shifted by 4. Symbol
|
||||
// values and relocation offsets have to be adjusted accordingly if they
|
||||
// refer past the deleted bytes.
|
||||
// refer to past the deleted bytes.
|
||||
//
|
||||
// In mold, we use `r_deltas` to memorize how many bytes have be adjusted
|
||||
// for relocations. For symbols, we directly mutate their `value` member.
|
||||
@@ -74,79 +74,60 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static u32 itype(u32 val) {
|
||||
return val << 20;
|
||||
static void write_itype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
|
||||
*(ul32 *)loc |= bits(val, 11, 0) << 20;
|
||||
}
|
||||
|
||||
static u32 stype(u32 val) {
|
||||
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
|
||||
static void write_stype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
|
||||
}
|
||||
|
||||
static u32 btype(u32 val) {
|
||||
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
|
||||
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
|
||||
static void write_btype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
|
||||
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
|
||||
}
|
||||
|
||||
static u32 utype(u32 val) {
|
||||
static void write_utype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
|
||||
|
||||
// U-type instructions are used in combination with I-type
|
||||
// instructions. U-type insn sets an immediate to the upper 20-bits
|
||||
// of a register. I-type insn sign-extends a 12-bits immediate and
|
||||
// adds it to a register value to construct a complete value. 0x800
|
||||
// is added here to compensate for the sign-extension.
|
||||
return (val + 0x800) & 0xffff'f000;
|
||||
}
|
||||
|
||||
static u32 jtype(u32 val) {
|
||||
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 cbtype(u32 val) {
|
||||
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static u32 cjtype(u32 val) {
|
||||
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
|
||||
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
|
||||
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_itype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
|
||||
*(ul32 *)loc |= itype(val);
|
||||
}
|
||||
|
||||
static void write_stype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= stype(val);
|
||||
}
|
||||
|
||||
static void write_btype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= btype(val);
|
||||
}
|
||||
|
||||
static void write_utype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
|
||||
*(ul32 *)loc |= utype(val);
|
||||
*(ul32 *)loc |= (val + 0x800) & 0xffff'f000;
|
||||
}
|
||||
|
||||
static void write_jtype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
|
||||
*(ul32 *)loc |= jtype(val);
|
||||
*(ul32 *)loc |= bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static void write_cbtype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'000'111'00000'11;
|
||||
*(ul16 *)loc |= cbtype(val);
|
||||
*(ul16 *)loc |= bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_cjtype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'00000000000'11;
|
||||
*(ul16 *)loc |= cjtype(val);
|
||||
*(ul16 *)loc |= bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
|
||||
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
|
||||
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void overwrite_uleb(u8 *loc, u64 val) {
|
||||
while (*loc & 0b1000'0000) {
|
||||
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
|
||||
val >>= 7;
|
||||
}
|
||||
*loc = val & 0b0111'1111;
|
||||
}
|
||||
|
||||
// Returns the rd register of an R/I/U/J-type instruction.
|
||||
@@ -309,6 +290,29 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto find_paired_reloc = [&] {
|
||||
Symbol<E> &sym = *file.symbols[rels[i].r_sym];
|
||||
assert(sym.get_input_section() == this);
|
||||
|
||||
if (sym.value < r_offset) {
|
||||
for (i64 j = i - 1; j >= 0; j--)
|
||||
if (u32 ty = rels[j].r_type;
|
||||
ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
|
||||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20)
|
||||
if (sym.value == rels[j].r_offset - get_r_delta(j))
|
||||
return j;
|
||||
} else {
|
||||
for (i64 j = i + 1; j < rels.size(); j++)
|
||||
if (u32 ty = rels[j].r_type;
|
||||
ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
|
||||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20)
|
||||
if (sym.value == rels[j].r_offset - get_r_delta(j))
|
||||
return j;
|
||||
}
|
||||
|
||||
Fatal(ctx) << *this << ": paired relocation is missing: " << i;
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + r_offset;
|
||||
@@ -326,18 +330,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
assert(E::is_64);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_RISCV_BRANCH: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 12), 1 << 12);
|
||||
write_btype(loc, val);
|
||||
case R_RISCV_BRANCH:
|
||||
check(S + A - P, -(1 << 12), 1 << 12);
|
||||
write_btype(loc, S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_JAL: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 20), 1 << 20);
|
||||
write_jtype(loc, val);
|
||||
case R_RISCV_JAL:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
write_jtype(loc, S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
|
||||
@@ -357,6 +357,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_cjtype(loc, S + A - P);
|
||||
} else {
|
||||
assert(removed_bytes == 0);
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, val);
|
||||
@@ -365,56 +368,82 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
*(ul32 *)loc = G + GOT + A - P;
|
||||
write_utype(loc, G + GOT + A - P);
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
write_utype(loc, sym.get_gottp_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
write_utype(loc, sym.get_tlsgd_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
*(ul32 *)loc = 0;
|
||||
} else {
|
||||
*(ul32 *)loc = S + A - P;
|
||||
}
|
||||
write_utype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_HI20: {
|
||||
i64 val = S + A;
|
||||
if (removed_bytes == 0) {
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, val);
|
||||
} else {
|
||||
assert(removed_bytes == 4);
|
||||
assert(sign_extend(val, 11) == val);
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S: {
|
||||
i64 idx2 = find_paired_reloc();
|
||||
const ElfRel<E> &rel2 = rels[idx2];
|
||||
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
|
||||
|
||||
u64 S = sym2.get_addr(ctx);
|
||||
u64 A = rel2.r_addend;
|
||||
u64 P = get_addr() + rel2.r_offset - get_r_delta(idx2);
|
||||
u64 G = sym2.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 val;
|
||||
|
||||
switch (rel2.r_type) {
|
||||
case R_RISCV_GOT_HI20:
|
||||
val = G + GOT + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
val = sym2.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
val = sym2.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
val = S + A - P;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S: {
|
||||
i64 val = S + A;
|
||||
if (rel.r_type == R_RISCV_LO12_I)
|
||||
|
||||
if (rel.r_type == R_RISCV_PCREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_HI20:
|
||||
assert(removed_bytes == 0 || removed_bytes == 4);
|
||||
if (removed_bytes == 0) {
|
||||
check(S + A, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, S + A);
|
||||
}
|
||||
break;
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
if (rel.r_type == R_RISCV_LO12_I)
|
||||
write_itype(loc, S + A);
|
||||
else
|
||||
write_stype(loc, S + A);
|
||||
|
||||
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
|
||||
// accessible relative to the zero register. If the upper 20 bits
|
||||
// are all zero, the corresponding LUI might have been removed.
|
||||
if (sign_extend(val, 11) == val)
|
||||
if (bits(S + A, 31, 12) == 0)
|
||||
set_rs1(loc, 0);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TPREL_HI20:
|
||||
assert(removed_bytes == 0 || removed_bytes == 4);
|
||||
if (removed_bytes == 0)
|
||||
write_utype(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_RISCV_TPREL_ADD:
|
||||
// This relocation just annotates an ADD instruction that can be
|
||||
// removed when a TPREL is relaxed. No value is needed to be
|
||||
// written.
|
||||
assert(removed_bytes == 0 || removed_bytes == 4);
|
||||
break;
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S: {
|
||||
@@ -472,20 +501,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)(loc + i) = 0x0001; // c.nop
|
||||
break;
|
||||
}
|
||||
case R_RISCV_RVC_BRANCH: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 8), 1 << 8);
|
||||
write_cbtype(loc, val);
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
check(S + A - P, -(1 << 8), 1 << 8);
|
||||
write_cbtype(loc, S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_RVC_JUMP: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 11), 1 << 11);
|
||||
write_cjtype(loc, val);
|
||||
case R_RISCV_RVC_JUMP:
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
write_cjtype(loc, S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
@@ -499,53 +524,23 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_PLT32:
|
||||
case R_RISCV_32_PCREL:
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
// These relocations are handled in the next loop.
|
||||
case R_RISCV_SET_ULEB128:
|
||||
overwrite_uleb(loc, S + A);
|
||||
break;
|
||||
case R_RISCV_SUB_ULEB128: {
|
||||
u8 *p = loc;
|
||||
u64 val = read_uleb(p);
|
||||
overwrite_uleb(loc, val - (S + A));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle PC-relative LO12 relocations. In the above loop, pcrel HI20
|
||||
// relocations overwrote instructions with full 32-bit values to allow
|
||||
// their corresponding pcrel LO12 relocations to read their values.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S: {
|
||||
Symbol<E> &sym = *file.symbols[rels[i].r_sym];
|
||||
assert(sym.get_input_section() == this);
|
||||
|
||||
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
|
||||
u32 val = *(ul32 *)(base + sym.value);
|
||||
|
||||
if (rels[i].r_type == R_RISCV_PCREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original instructions pcrel HI20 relocations overwrote.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_GOT_HI20:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
case R_RISCV_TLS_GD_HI20: {
|
||||
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
|
||||
u32 val = *(ul32 *)loc;
|
||||
memcpy(loc, contents.data() + rels[i].r_offset, 4);
|
||||
write_utype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -554,17 +549,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -607,7 +597,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
*(U64<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
@@ -621,6 +611,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET_ULEB128:
|
||||
overwrite_uleb(loc, S + A);
|
||||
break;
|
||||
case R_RISCV_SUB_ULEB128: {
|
||||
u8 *p = loc;
|
||||
u64 val = read_uleb(p);
|
||||
overwrite_uleb(loc, val - S - A);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
@@ -637,8 +636,7 @@ void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory-allocated sections may be relaxed, so copy each segment
|
||||
// individually.
|
||||
// A relaxed section is copied piece-wise.
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 pos = 0;
|
||||
|
||||
@@ -667,18 +665,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
@@ -697,22 +690,28 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
case R_RISCV_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_RISCV_GOT_HI20:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
@@ -720,10 +719,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
case R_RISCV_ADD8:
|
||||
case R_RISCV_ADD16:
|
||||
case R_RISCV_ADD32:
|
||||
@@ -741,6 +736,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_SET8:
|
||||
case R_RISCV_SET16:
|
||||
case R_RISCV_SET32:
|
||||
case R_RISCV_SET_ULEB128:
|
||||
case R_RISCV_SUB_ULEB128:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -791,7 +788,7 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
|
||||
// Handling R_RISCV_ALIGN is mandatory.
|
||||
//
|
||||
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate some
|
||||
// R_RISCV_ALIGN refers to NOP instructions. We need to eliminate some
|
||||
// or all of the instructions so that the instruction that immediately
|
||||
// follows the NOPs is aligned to a specified alignment boundary.
|
||||
if (r.r_type == R_RISCV_ALIGN) {
|
||||
@@ -821,15 +818,14 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
// These relocations refer an AUIPC + JALR instruction pair to
|
||||
// These relocations refer to an AUIPC + JALR instruction pair to
|
||||
// allow to jump to anywhere in PC ± 2 GiB. If the jump target is
|
||||
// close enough to PC, we can use C.J, C.JAL or JAL instead.
|
||||
i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
if (dist & 1)
|
||||
break;
|
||||
|
||||
std::string_view contents = isec.contents;
|
||||
i64 rd = get_rd(*(ul32 *)(contents.data() + r.r_offset + 4));
|
||||
i64 rd = get_rd(*(ul32 *)(isec.contents.data() + r.r_offset + 4));
|
||||
|
||||
if (rd == 0 && sign_extend(dist, 11) == dist && use_rvc) {
|
||||
// If rd is x0 and the jump target is within ±2 KiB, we can use
|
||||
@@ -845,41 +841,39 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_HI20: {
|
||||
case R_RISCV_HI20:
|
||||
// If the upper 20 bits are all zero, we can remove LUI.
|
||||
// The corresponding instructions referred by LO12_I/LO12_S
|
||||
// The corresponding instructions referred to by LO12_I/LO12_S
|
||||
// relocations will use the zero register instead.
|
||||
i64 val = sym.get_addr(ctx);
|
||||
if (sign_extend(val, 11) == val)
|
||||
if (bits(sym.get_addr(ctx), 31, 12) == 0)
|
||||
delta += 4;
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_ADD: {
|
||||
// These relocations are used to materialize the upper 20 bits of
|
||||
// an address relative to the thread pointer as follows:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
// These relocations are used to add a high 20-bit value to the
|
||||
// thread pointer. The following two instructions materializes
|
||||
// TP + HI20(foo) in %r5, for example.
|
||||
//
|
||||
// lui a5,%tprel_hi(foo) # R_RISCV_TPREL_HI20 (symbol)
|
||||
// add a5,a5,tp,%tprel_add(foo) # R_RISCV_TPREL_ADD (symbol)
|
||||
//
|
||||
// Then thread-local variable `foo` is accessed with a 12-bit offset
|
||||
// like this:
|
||||
// Then thread-local variable `foo` is accessed with a low 12-bit
|
||||
// offset like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
|
||||
//
|
||||
// However, if the offset is ±2 KiB, we don't need to materialize
|
||||
// the upper 20 bits in a register. We can instead access the
|
||||
// thread-local variable directly with TP like this:
|
||||
// However, if the variable is at TP ±2 KiB, TP + HI20(foo) is the
|
||||
// same as TP, so we can instead access the thread-local variable
|
||||
// directly using TP like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(tp)
|
||||
//
|
||||
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
|
||||
i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
|
||||
if (sign_extend(val, 11) == val)
|
||||
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
|
||||
sign_extend(val, 11) == val)
|
||||
delta += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isec.extra.r_deltas[rels.size()] = delta;
|
||||
|
||||
+107
-158
@@ -12,7 +12,7 @@
|
||||
// As an instruction set, s390x isn't particularly odd. It has 16 general-
|
||||
// purpose registers. Instructions are 2, 4 or 6 bytes long and always
|
||||
// aligned to 2 bytes boundaries. Despite unfamiliarty, I found that it
|
||||
// just feels like a 64-bit i386 in a parallel universe.
|
||||
// just feels like an x86-64 in a parallel universe.
|
||||
//
|
||||
// Here is the register usage in this ABI:
|
||||
//
|
||||
@@ -25,15 +25,15 @@
|
||||
// a1: upper 32 bits of TP (thread pointer)
|
||||
// a2: lower 32 bits of TP (thread pointer)
|
||||
//
|
||||
// TLS is supported on s390x in the same way as it is on other targets
|
||||
// with one exeption. On other targets, __tls_get_addr is used to get an
|
||||
// address of a thread-local variable. On s390x, __tls_get_offset is used
|
||||
// instead. The difference is __tls_get_offset returns an address of a
|
||||
// thread-local variable as an offset from TP. So we need to add TP to a
|
||||
// return value before use. I don't know why it is different, but that is
|
||||
// the way it is.
|
||||
// Thread-local storage (TLS) is supported on s390x in the same way as it
|
||||
// is on other targets with one exeption. On other targets, __tls_get_addr
|
||||
// is used to get an address of a thread-local variable. On s390x,
|
||||
// __tls_get_offset is used instead. The difference is __tls_get_offset
|
||||
// returns an address of a thread-local variable as an offset from TP. So
|
||||
// we need to add TP to a return value before use. I don't know why it is
|
||||
// different, but that is the way it is.
|
||||
//
|
||||
// https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/s390x.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -151,134 +151,95 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_390_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_390_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_12: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 12);
|
||||
*(ul16 *)loc = bits(val, 11, 0);
|
||||
case R_390_12:
|
||||
check(S + A, 0, 1 << 12);
|
||||
*(ul16 *)loc |= bits(S + A, 11, 0);
|
||||
break;
|
||||
}
|
||||
case R_390_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_20: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
case R_390_20:
|
||||
check(S + A, 0, 1 << 20);
|
||||
write_mid20(loc, S + A);
|
||||
break;
|
||||
}
|
||||
case R_390_32:
|
||||
case R_390_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_PLT32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_PLT64:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_390_PC12DBL:
|
||||
case R_390_PLT12DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 12), 1 << 12);
|
||||
*(ul16 *)loc = (val >> 1) & 0x0fff;
|
||||
case R_390_PLT12DBL:
|
||||
check_dbl(S + A - P, -(1 << 12), 1 << 12);
|
||||
*(ul16 *)loc |= bits(S + A - P, 12, 1);
|
||||
break;
|
||||
}
|
||||
case R_390_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_390_PC32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_PC32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_390_PC64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_390_PC16DBL:
|
||||
case R_390_PLT16DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc = val >> 1;
|
||||
case R_390_PLT16DBL:
|
||||
check_dbl(S + A - P, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc = (S + A - P) >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_PC24DBL:
|
||||
case R_390_PLT24DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 24), 1 << 24);
|
||||
*(ub32 *)loc |= bits(val, 24, 1);
|
||||
case R_390_PLT24DBL:
|
||||
check_dbl(S + A - P, -(1 << 24), 1 << 24);
|
||||
*(ub32 *)loc |= bits(S + A - P, 24, 1);
|
||||
break;
|
||||
}
|
||||
case R_390_PC32DBL:
|
||||
case R_390_PLT32DBL:
|
||||
if (ctx.is_static && &sym == ctx.tls_get_offset) {
|
||||
// __tls_get_offset() in libc.a is stub code that calls abort().
|
||||
// So we provide a replacement function.
|
||||
*(ub32 *)loc = (ctx.extra.tls_get_offset->shdr.sh_addr - P) >> 1;
|
||||
} else {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
}
|
||||
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (S + A - P) >> 1;
|
||||
break;
|
||||
case R_390_GOT12:
|
||||
case R_390_GOTPLT12: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 12);
|
||||
*(ul16 *)loc = bits(val, 11, 0);
|
||||
case R_390_GOTPLT12:
|
||||
check(G + A, 0, 1 << 12);
|
||||
*(ul16 *)loc |= bits(G + A, 11, 0);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT16:
|
||||
case R_390_GOTPLT16: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_GOTPLT16:
|
||||
check(G + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = G + A;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT20:
|
||||
case R_390_GOTPLT20: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
case R_390_GOTPLT20:
|
||||
check(G + A, 0, 1 << 20);
|
||||
write_mid20(loc, G + A);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT32:
|
||||
case R_390_GOTPLT32: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_GOTPLT32:
|
||||
check(G + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = G + A;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT64:
|
||||
case R_390_GOTPLT64:
|
||||
*(ub64 *)loc = G + A;
|
||||
break;
|
||||
case R_390_GOTOFF16:
|
||||
case R_390_PLTOFF16: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_PLTOFF16:
|
||||
check(S + A - GOT, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - GOT;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF32:
|
||||
case R_390_PLTOFF32: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_PLTOFF32:
|
||||
check(S + A - GOT, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - GOT;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF64:
|
||||
case R_390_PLTOFF64:
|
||||
*(ub64 *)loc = S + A - GOT;
|
||||
@@ -286,18 +247,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_GOTPC:
|
||||
*(ub64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_390_GOTPCDBL: {
|
||||
i64 val = GOT + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
case R_390_GOTPCDBL:
|
||||
check_dbl(GOT + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (GOT + A - P) >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTENT: {
|
||||
i64 val = GOT + G + A - P;
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
case R_390_GOTENT:
|
||||
check(GOT + G + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (GOT + G + A - P) >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
@@ -313,19 +270,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_TLS_GD32:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else if (sym.has_gottp(ctx))
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GD64:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub64 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else if (sym.has_gottp(ctx))
|
||||
*(ub64 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GDCALL:
|
||||
if (!sym.has_tlsgd(ctx)) {
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
// do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// lg %r2, 0(%r2, %r12)
|
||||
static u8 insn[] = { 0xe3, 0x22, 0xc0, 0x00, 0x00, 0x04 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
} else {
|
||||
// nop
|
||||
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
@@ -350,8 +318,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_390_TLS_LDCALL:
|
||||
if (!ctx.got->has_tlsld(ctx)) {
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
// nop
|
||||
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -366,17 +335,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -426,18 +390,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
@@ -473,7 +432,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_390_GOTPC:
|
||||
case R_390_GOTPCDBL:
|
||||
case R_390_GOTENT:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_390_PLT12DBL:
|
||||
case R_390_PLT16DBL:
|
||||
@@ -485,24 +444,38 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_390_PLTOFF32:
|
||||
case R_390_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_390_TLS_GOTIE20:
|
||||
case R_390_TLS_IEENT:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_390_TLS_GD32:
|
||||
case R_390_TLS_GD64:
|
||||
if (!relax_tlsgd(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
// We always want to relax calls to __tls_get_offset() in statically-
|
||||
// linked executables because __tls_get_offset() in libc.a just calls
|
||||
// abort().
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
|
||||
// do nothing
|
||||
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
|
||||
!ctx.arg.z_dlopen) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
case R_390_TLS_LDM64:
|
||||
if (!relax_tlsld(ctx))
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
case R_390_TLS_LDM64: {
|
||||
bool do_relax = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
|
||||
if (!do_relax)
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
}
|
||||
case R_390_TLS_LE32:
|
||||
case R_390_TLS_LE64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_TLS_LDO32:
|
||||
case R_390_TLS_LDO64:
|
||||
case R_390_TLS_GDCALL:
|
||||
@@ -514,28 +487,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// __tls_get_offset() in libc.a just calls abort(), assuming that the
|
||||
// linker always relaxes TLS calls for statically-linkd executables.
|
||||
// We don't always do that because we believe --relax and --static
|
||||
// should be orthogonal.
|
||||
//
|
||||
// This section provides a replacement for __tls_get_offset() in libc.a.
|
||||
void S390XTlsGetOffsetSection::copy_buf(Context<E> &ctx) {
|
||||
static const u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT
|
||||
0xb9, 0x08, 0x00, 0x21, // agr %r2, %r1
|
||||
0xe3, 0x20, 0x20, 0x08, 0x00, 0x04, // lg %r2, 8(%r2)
|
||||
0xc0, 0x11, 0, 0, 0, 0, // lgfi %r1, TLS_BLOCK_SIZE
|
||||
0xb9, 0x09, 0x00, 0x21, // sgr %r2, %r1
|
||||
0x07, 0xfe, // br %r14
|
||||
};
|
||||
|
||||
assert(this->shdr.sh_size == sizeof(insn));
|
||||
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset;
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
*(ub32 *)(loc + 2) = (ctx.got->shdr.sh_addr - this->shdr.sh_addr) >> 1;
|
||||
*(ub32 *)(loc + 18) = ctx.tp_addr - ctx.tls_begin;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+17
-18
@@ -26,6 +26,13 @@
|
||||
// small to contain large immediates. On SH-4, large immediates are
|
||||
// loaded from memory using `mov.l` PC-relative load instruction.
|
||||
//
|
||||
// - Many RISC ISAs are, despite their name, actually fairly complex.
|
||||
// They tend to have hundreds if not thousands of different instructions.
|
||||
// SH-4 doesn't really have that many instructions because its 16-bit
|
||||
// machine code simply can't encode many different opcodes. As a
|
||||
// result, the number of relocations the linker has to support is also
|
||||
// small.
|
||||
//
|
||||
// Beside these, SH-4 has a delay branch slot just like contemporary MIPS
|
||||
// and SPARC. That is, one instruction after a branch instruction will
|
||||
// always be executed even if the branch is taken. Delay branch slot allows
|
||||
@@ -264,17 +271,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -305,16 +307,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
|
||||
|
||||
@@ -326,25 +323,27 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_GOT32:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_SH_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SH_TLS_GD_32:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_SH_TLS_LD_32:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_SH_TLS_IE_32:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_SH_TLS_LE_32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_TLS_LDO_32:
|
||||
case R_SH_TLS_LE_32:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
|
||||
+71
-108
@@ -54,8 +54,7 @@
|
||||
// This scheme is very similar to i386. That may not be a coincidence
|
||||
// because the i386 ELF psABI is created by Sun Microsystems too.
|
||||
//
|
||||
// https://docs.oracle.com/cd/E36784_01/html/E36857/chapter6-62988.html
|
||||
// https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-40/index.html
|
||||
// https://github.com/rui314/psabi/blob/main/sparc.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -173,97 +172,71 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_SPARC_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_SPARC_5: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(val, 4, 0);
|
||||
case R_SPARC_5:
|
||||
check(S + A, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(S + A, 4, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_6: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 6);
|
||||
*(ub32 *)loc |= bits(val, 5, 0);
|
||||
case R_SPARC_6:
|
||||
check(S + A, 0, 1 << 6);
|
||||
*(ub32 *)loc |= bits(S + A, 5, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_7: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 7);
|
||||
*(ub32 *)loc |= bits(val, 6, 0);
|
||||
case R_SPARC_7:
|
||||
check(S + A, 0, 1 << 7);
|
||||
*(ub32 *)loc |= bits(S + A, 6, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*(u8 *)loc = val;
|
||||
case R_SPARC_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*(u8 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_10: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 10);
|
||||
*(ub32 *)loc |= bits(val, 9, 0);
|
||||
case R_SPARC_10:
|
||||
check(S + A, 0, 1 << 10);
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_LO10:
|
||||
case R_SPARC_LOPLT10:
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0);
|
||||
break;
|
||||
case R_SPARC_11: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 11);
|
||||
*(ub32 *)loc |= bits(val, 10, 0);
|
||||
case R_SPARC_11:
|
||||
check(S + A, 0, 1 << 11);
|
||||
*(ub32 *)loc |= bits(S + A, 10, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_13: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 13);
|
||||
*(ub32 *)loc |= bits(val, 12, 0);
|
||||
case R_SPARC_13:
|
||||
check(S + A, 0, 1 << 13);
|
||||
*(ub32 *)loc |= bits(S + A, 12, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_16:
|
||||
case R_SPARC_UA16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
case R_SPARC_UA16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_22: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 22);
|
||||
*(ub32 *)loc |= bits(val, 21, 0);
|
||||
case R_SPARC_22:
|
||||
check(S + A, 0, 1 << 22);
|
||||
*(ub32 *)loc |= bits(S + A, 21, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_UA32:
|
||||
case R_SPARC_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_SPARC_PLT32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_PLT64:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_REGISTER:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_SPARC_DISP8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*(u8 *)loc = val;
|
||||
case R_SPARC_DISP8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*(u8 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
case R_SPARC_DISP16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP32:
|
||||
case R_SPARC_PCPLT32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
case R_SPARC_PCPLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -273,25 +246,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub16 *)loc |= (bit(val, 16) << 21) | bits(val, 15, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 20), 1 << 20);
|
||||
*(ub32 *)loc |= bits(val, 20, 2);
|
||||
case R_SPARC_WDISP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
*(ub32 *)loc |= bits(S + A - P, 20, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP22: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 23), 1 << 23);
|
||||
*(ub32 *)loc |= bits(val, 23, 2);
|
||||
case R_SPARC_WDISP22:
|
||||
check(S + A - P, -(1 << 23), 1 << 23);
|
||||
*(ub32 *)loc |= bits(S + A - P, 23, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP30:
|
||||
case R_SPARC_WPLT30: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc |= bits(val, 31, 2);
|
||||
case R_SPARC_WPLT30:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc |= bits(S + A - P, 31, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_HI22:
|
||||
case R_SPARC_HIPLT22:
|
||||
case R_SPARC_LM22:
|
||||
@@ -402,12 +369,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL: {
|
||||
u64 addr;
|
||||
if (ctx.arg.is_static)
|
||||
*(ub32 *)loc |= bits(ctx.extra.tls_get_addr->shdr.sh_addr + A - P, 31, 2);
|
||||
addr = ctx.extra.tls_get_addr_sec->shdr.sh_addr;
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + A - P, 31, 2);
|
||||
addr = ctx.extra.tls_get_addr_sym->get_addr(ctx);
|
||||
|
||||
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
|
||||
break;
|
||||
@@ -454,17 +425,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -516,18 +482,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
@@ -571,17 +532,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_PCPLT10:
|
||||
case R_SPARC_PLT64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SPARC_GOT13:
|
||||
case R_SPARC_GOT10:
|
||||
case R_SPARC_GOT22:
|
||||
case R_SPARC_GOTDATA_HIX22:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_HIX22:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_SPARC_DISP16:
|
||||
case R_SPARC_DISP32:
|
||||
@@ -597,18 +558,22 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_SPARC_TLS_IE_HI22:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
|
||||
ctx.tls_get_addr->flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
if (!ctx.arg.is_static && ctx.extra.tls_get_addr_sym->is_imported)
|
||||
ctx.extra.tls_get_addr_sym->flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
@@ -624,8 +589,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_TLS_IE_LD:
|
||||
case R_SPARC_TLS_IE_LDX:
|
||||
case R_SPARC_TLS_IE_LO10:
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
case R_SPARC_SIZE32:
|
||||
break;
|
||||
default:
|
||||
|
||||
+222
-202
@@ -23,8 +23,8 @@
|
||||
// TLS block as TP (with some addend). As a result, offsets from TP to
|
||||
// thread-local variables (TLVs) in the main executable are all negative.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
|
||||
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/x86-64.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -187,6 +187,142 @@ static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
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.
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// or
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val - 12;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val - 13;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. The difference from relax_gd_to_le is that we are
|
||||
// materializing a Dynamic Thread Pointer for the current ELF module
|
||||
// instead of an address for a particular thread-local variable.
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
@@ -231,18 +367,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_X86_64_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_X86_64_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
@@ -252,18 +384,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_X86_64_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_X86_64_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
case R_X86_64_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_X86_64_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
@@ -294,79 +422,43 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul64 *)loc = G + GOTPLT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOTPLT + A - P);
|
||||
} else {
|
||||
// We always want to relax GOTPCRELX relocs even if --no-relax
|
||||
// was given because some static PIE runtime code depends on these
|
||||
// relaxations.
|
||||
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOTPLT + A - P);
|
||||
} else {
|
||||
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
relax_gd_to_ie(loc, rels[i + 1], sym.get_gottp_addr(ctx) - P);
|
||||
i++;
|
||||
} else {
|
||||
// Relax GD to LE. If we are creating an exectuable, the offset of
|
||||
// a thread-local variable from TP is a link-time constant. So we
|
||||
// don't need to call __tls_get_addr to obtain the address of a TLV.
|
||||
i64 val = S - ctx.tp_addr;
|
||||
assert(A == -4);
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// or
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
@@ -374,64 +466,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
} else {
|
||||
// Relax LD to LE. If we are creating an executable, we don't need
|
||||
// to call __tls_get_addr to obtain the address of the beginning
|
||||
// of the current TLS block. TP points past the end of the TLS
|
||||
// block, and we know the exact size of the TLS block we have
|
||||
// created, so we can just subtract it from TP.
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
assert(A == -4);
|
||||
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
@@ -508,17 +543,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -544,18 +574,14 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_X86_64_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_X86_64_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
@@ -580,6 +606,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
else
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
*(ul64 *)loc = S + A - ctx.gotplt->shdr.sh_addr;
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
// PC-relative relocation doesn't make sense for non-memory-allocated
|
||||
// section, but GCC 6.3.0 seems to create this reloc for
|
||||
// _GLOBAL_OFFSET_TABLE_.
|
||||
*(ul64 *)loc = ctx.gotplt->shdr.sh_addr + A;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
@@ -609,19 +644,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
@@ -645,34 +675,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_gotpcrelx(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_X86_64_TLSGD: {
|
||||
case R_X86_64_TLSGD:
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
|
||||
|
||||
@@ -685,13 +697,20 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
i++;
|
||||
else
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
|
||||
!ctx.arg.z_dlopen) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
i++;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD: {
|
||||
case R_X86_64_TLSLD:
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
|
||||
|
||||
@@ -704,22 +723,21 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (relax_tlsld(ctx))
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
|
||||
|
||||
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
|
||||
|
||||
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
|
||||
!sym.is_imported && relax_gottpoff(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPC32_TLSDESC: {
|
||||
@@ -731,14 +749,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
|
||||
+25
-7
@@ -1,5 +1,5 @@
|
||||
#include "mold.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../common/cmdline.h"
|
||||
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
@@ -98,7 +98,7 @@ Options:
|
||||
--gc-sections Remove unreferenced sections
|
||||
--no-gc-sections
|
||||
--gdb-index Create .gdb_index for faster gdb startup
|
||||
--hash-style [sysv,gnu,both]
|
||||
--hash-style [sysv,gnu,both,none]
|
||||
Set hash style
|
||||
--icf=[all,safe,none] Fold identical code
|
||||
--no-icf
|
||||
@@ -191,6 +191,7 @@ Options:
|
||||
Separate all loadable segments to different pages
|
||||
-z separate-code Separate code and data into different pages
|
||||
-z noseparate-code Allow overlap in pages
|
||||
-z stack-size=VALUE Set size of stack segment
|
||||
-z relro Make some sections read-only after relocation (default)
|
||||
-z norelro
|
||||
-z text Report error if DT_TEXTREL is set
|
||||
@@ -522,6 +523,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
|
||||
<< " elf64_sparc\n m68kelf\n shlelf_linux\n elf64alpha";
|
||||
version_shown = true;
|
||||
} else if (read_flag("mips32") || read_flag("mips32r2") ||
|
||||
read_flag("mips32r3") || read_flag("mips32r4") ||
|
||||
read_flag("mips32r5") || read_flag("mips32r6") ||
|
||||
read_flag("mips64") || read_flag("mips64r2") ||
|
||||
read_flag("mips64r3") || read_flag("mips64r4") ||
|
||||
read_flag("mips64r5") || read_flag("mips64r6")) {
|
||||
// Ignore useless MIPS-specific flags
|
||||
} else if (read_arg("m")) {
|
||||
if (arg == "elf_x86_64") {
|
||||
ctx.arg.emulation = X86_64::target_name;
|
||||
@@ -555,6 +563,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.emulation = SH4::target_name;
|
||||
} else if (arg == "elf64alpha") {
|
||||
ctx.arg.emulation = ALPHA::target_name;
|
||||
} else if (arg == "elf64ltsmip") {
|
||||
ctx.arg.emulation = MIPS64LE::target_name;
|
||||
} else if (arg == "elf64btsmip") {
|
||||
ctx.arg.emulation = MIPS64BE::target_name;
|
||||
} else {
|
||||
Fatal(ctx) << "unknown -m argument: " << arg;
|
||||
}
|
||||
@@ -582,9 +594,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag("print-dependencies")) {
|
||||
ctx.arg.print_dependencies = 1;
|
||||
} else if (read_flag("print-dependencies=full")) {
|
||||
ctx.arg.print_dependencies = 2;
|
||||
ctx.arg.print_dependencies = true;
|
||||
} else if (read_flag("print-map") || read_flag("M")) {
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag("Bstatic") || read_flag("dn") || read_flag("static")) {
|
||||
@@ -652,7 +662,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_IGNORE;
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg("u") || read_arg("undefined")) {
|
||||
} else if (read_arg("undefined") || read_arg("u")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
} else if (read_arg("require-defined")) {
|
||||
ctx.arg.require_defined.push_back(arg);
|
||||
@@ -670,6 +680,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (arg == "both") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
ctx.arg.hash_style_gnu = true;
|
||||
} else if (arg == "none") {
|
||||
ctx.arg.hash_style_sysv = false;
|
||||
ctx.arg.hash_style_gnu = false;
|
||||
} else {
|
||||
Fatal(ctx) << "invalid --hash-style argument: " << arg;
|
||||
}
|
||||
@@ -691,7 +704,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("pie") || read_flag("pic-executable")) {
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.pie = true;
|
||||
} else if (read_flag("no-pie") || read_flag("no-pic-executable")) {
|
||||
} else if (read_flag("no-pie") || read_flag("no-pic-executable") ||
|
||||
read_flag("nopie")) {
|
||||
ctx.arg.pic = false;
|
||||
ctx.arg.pie = false;
|
||||
} else if (read_flag("relax")) {
|
||||
@@ -864,6 +878,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
z_separate_code = SEPARATE_CODE;
|
||||
} else if (read_z_flag("noseparate-code")) {
|
||||
z_separate_code = NOSEPARATE_CODE;
|
||||
} else if (read_z_arg("stack-size")) {
|
||||
ctx.arg.z_stack_size = parse_number(ctx, "-z stack-size", arg);
|
||||
} else if (read_z_flag("dynamic-undefined-weak")) {
|
||||
ctx.arg.z_dynamic_undefined_weak = true;
|
||||
} else if (read_z_flag("nodynamic-undefined-weak")) {
|
||||
@@ -1046,6 +1062,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("filter") || read_arg("F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_arg("O")) {
|
||||
} else if (read_flag("EB")) {
|
||||
} else if (read_flag("EL")) {
|
||||
} else if (read_flag("O0")) {
|
||||
} else if (read_flag("O1")) {
|
||||
} else if (read_flag("O2")) {
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(U32<E> *)data.data() == 0xffffffff)
|
||||
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));
|
||||
@@ -295,7 +295,7 @@ public:
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
|
||||
+95
@@ -403,6 +403,9 @@ std::string rel_to_string<RV64LE>(u32 r_type) {
|
||||
case R_RISCV_SET32: return "R_RISCV_SET32";
|
||||
case R_RISCV_32_PCREL: return "R_RISCV_32_PCREL";
|
||||
case R_RISCV_IRELATIVE: return "R_RISCV_IRELATIVE";
|
||||
case R_RISCV_PLT32: return "R_RISCV_PLT32";
|
||||
case R_RISCV_SET_ULEB128: return "R_RISCV_SET_ULEB128";
|
||||
case R_RISCV_SUB_ULEB128: return "R_RISCV_SUB_ULEB128";
|
||||
}
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
@@ -602,9 +605,13 @@ std::string rel_to_string<PPC64V1>(u32 r_type) {
|
||||
case R_PPC64_REL24_NOTOC: return "R_PPC64_REL24_NOTOC";
|
||||
case R_PPC64_PLTSEQ: return "R_PPC64_PLTSEQ";
|
||||
case R_PPC64_PLTCALL: return "R_PPC64_PLTCALL";
|
||||
case R_PPC64_PLTSEQ_NOTOC: return "R_PPC64_PLTSEQ_NOTOC";
|
||||
case R_PPC64_PLTCALL_NOTOC: return "R_PPC64_PLTCALL_NOTOC";
|
||||
case R_PPC64_PCREL_OPT: return "R_PPC64_PCREL_OPT";
|
||||
case R_PPC64_PCREL34: return "R_PPC64_PCREL34";
|
||||
case R_PPC64_GOT_PCREL34: return "R_PPC64_GOT_PCREL34";
|
||||
case R_PPC64_PLT_PCREL34: return "R_PPC64_PLT_PCREL34";
|
||||
case R_PPC64_PLT_PCREL34_NOTOC: return "R_PPC64_PLT_PCREL34_NOTOC";
|
||||
case R_PPC64_TPREL34: return "R_PPC64_TPREL34";
|
||||
case R_PPC64_DTPREL34: return "R_PPC64_DTPREL34";
|
||||
case R_PPC64_GOT_TLSGD_PCREL34: return "R_PPC64_GOT_TLSGD_PCREL34";
|
||||
@@ -911,4 +918,92 @@ std::string rel_to_string<ALPHA>(u32 r_type) {
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<MIPS64LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
case R_MIPS_NONE: return "R_MIPS_NONE";
|
||||
case R_MIPS_16: return "R_MIPS_16";
|
||||
case R_MIPS_32: return "R_MIPS_32";
|
||||
case R_MIPS_REL32: return "R_MIPS_REL32";
|
||||
case R_MIPS_26: return "R_MIPS_26";
|
||||
case R_MIPS_HI16: return "R_MIPS_HI16";
|
||||
case R_MIPS_LO16: return "R_MIPS_LO16";
|
||||
case R_MIPS_GPREL16: return "R_MIPS_GPREL16";
|
||||
case R_MIPS_LITERAL: return "R_MIPS_LITERAL";
|
||||
case R_MIPS_GOT16: return "R_MIPS_GOT16";
|
||||
case R_MIPS_PC16: return "R_MIPS_PC16";
|
||||
case R_MIPS_CALL16: return "R_MIPS_CALL16";
|
||||
case R_MIPS_GPREL32: return "R_MIPS_GPREL32";
|
||||
case R_MIPS_UNUSED1: return "R_MIPS_UNUSED1";
|
||||
case R_MIPS_UNUSED2: return "R_MIPS_UNUSED2";
|
||||
case R_MIPS_UNUSED3: return "R_MIPS_UNUSED3";
|
||||
case R_MIPS_SHIFT5: return "R_MIPS_SHIFT5";
|
||||
case R_MIPS_SHIFT6: return "R_MIPS_SHIFT6";
|
||||
case R_MIPS_64: return "R_MIPS_64";
|
||||
case R_MIPS_GOT_DISP: return "R_MIPS_GOT_DISP";
|
||||
case R_MIPS_GOT_PAGE: return "R_MIPS_GOT_PAGE";
|
||||
case R_MIPS_GOT_OFST: return "R_MIPS_GOT_OFST";
|
||||
case R_MIPS_GOT_HI16: return "R_MIPS_GOT_HI16";
|
||||
case R_MIPS_GOT_LO16: return "R_MIPS_GOT_LO16";
|
||||
case R_MIPS_SUB: return "R_MIPS_SUB";
|
||||
case R_MIPS_INSERT_A: return "R_MIPS_INSERT_A";
|
||||
case R_MIPS_INSERT_B: return "R_MIPS_INSERT_B";
|
||||
case R_MIPS_DELETE: return "R_MIPS_DELETE";
|
||||
case R_MIPS_HIGHER: return "R_MIPS_HIGHER";
|
||||
case R_MIPS_HIGHEST: return "R_MIPS_HIGHEST";
|
||||
case R_MIPS_CALL_HI16: return "R_MIPS_CALL_HI16";
|
||||
case R_MIPS_CALL_LO16: return "R_MIPS_CALL_LO16";
|
||||
case R_MIPS_SCN_DISP: return "R_MIPS_SCN_DISP";
|
||||
case R_MIPS_REL16: return "R_MIPS_REL16";
|
||||
case R_MIPS_ADD_IMMEDIATE: return "R_MIPS_ADD_IMMEDIATE";
|
||||
case R_MIPS_PJUMP: return "R_MIPS_PJUMP";
|
||||
case R_MIPS_RELGOT: return "R_MIPS_RELGOT";
|
||||
case R_MIPS_JALR: return "R_MIPS_JALR";
|
||||
case R_MIPS_TLS_DTPMOD32: return "R_MIPS_TLS_DTPMOD32";
|
||||
case R_MIPS_TLS_DTPREL32: return "R_MIPS_TLS_DTPREL32";
|
||||
case R_MIPS_TLS_DTPMOD64: return "R_MIPS_TLS_DTPMOD64";
|
||||
case R_MIPS_TLS_DTPREL64: return "R_MIPS_TLS_DTPREL64";
|
||||
case R_MIPS_TLS_GD: return "R_MIPS_TLS_GD";
|
||||
case R_MIPS_TLS_LDM: return "R_MIPS_TLS_LDM";
|
||||
case R_MIPS_TLS_DTPREL_HI16: return "R_MIPS_TLS_DTPREL_HI16";
|
||||
case R_MIPS_TLS_DTPREL_LO16: return "R_MIPS_TLS_DTPREL_LO16";
|
||||
case R_MIPS_TLS_GOTTPREL: return "R_MIPS_TLS_GOTTPREL";
|
||||
case R_MIPS_TLS_TPREL32: return "R_MIPS_TLS_TPREL32";
|
||||
case R_MIPS_TLS_TPREL64: return "R_MIPS_TLS_TPREL64";
|
||||
case R_MIPS_TLS_TPREL_HI16: return "R_MIPS_TLS_TPREL_HI16";
|
||||
case R_MIPS_TLS_TPREL_LO16: return "R_MIPS_TLS_TPREL_LO16";
|
||||
case R_MIPS_GLOB_DAT: return "R_MIPS_GLOB_DAT";
|
||||
case R_MIPS_PC21_S2: return "R_MIPS_PC21_S2";
|
||||
case R_MIPS_PC26_S2: return "R_MIPS_PC26_S2";
|
||||
case R_MIPS_PC18_S3: return "R_MIPS_PC18_S3";
|
||||
case R_MIPS_PC19_S2: return "R_MIPS_PC19_S2";
|
||||
case R_MIPS_PCHI16: return "R_MIPS_PCHI16";
|
||||
case R_MIPS_PCLO16: return "R_MIPS_PCLO16";
|
||||
case R_MIPS16_26: return "R_MIPS16_26";
|
||||
case R_MIPS16_GPREL: return "R_MIPS16_GPREL";
|
||||
case R_MIPS16_GOT16: return "R_MIPS16_GOT16";
|
||||
case R_MIPS16_CALL16: return "R_MIPS16_CALL16";
|
||||
case R_MIPS16_HI16: return "R_MIPS16_HI16";
|
||||
case R_MIPS16_LO16: return "R_MIPS16_LO16";
|
||||
case R_MIPS16_TLS_GD: return "R_MIPS16_TLS_GD";
|
||||
case R_MIPS16_TLS_LDM: return "R_MIPS16_TLS_LDM";
|
||||
case R_MIPS16_TLS_DTPREL_HI16: return "R_MIPS16_TLS_DTPREL_HI16";
|
||||
case R_MIPS16_TLS_DTPREL_LO16: return "R_MIPS16_TLS_DTPREL_LO16";
|
||||
case R_MIPS16_TLS_GOTTPREL: return "R_MIPS16_TLS_GOTTPREL";
|
||||
case R_MIPS16_TLS_TPREL_HI16: return "R_MIPS16_TLS_TPREL_HI16";
|
||||
case R_MIPS16_TLS_TPREL_LO16: return "R_MIPS16_TLS_TPREL_LO16";
|
||||
case R_MIPS_COPY: return "R_MIPS_COPY";
|
||||
case R_MIPS_JUMP_SLOT: return "R_MIPS_JUMP_SLOT";
|
||||
case R_MIPS_NUM: return "R_MIPS_NUM";
|
||||
case R_MIPS_PC32: return "R_MIPS_PC32";
|
||||
case R_MIPS_EH: return "R_MIPS_EH";
|
||||
}
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<MIPS64BE>(u32 r_type) {
|
||||
return rel_to_string<MIPS64LE>(r_type);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,8 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../integers.h"
|
||||
#include "../common/integers.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -25,6 +24,8 @@ struct SPARC64;
|
||||
struct M68K;
|
||||
struct SH4;
|
||||
struct ALPHA;
|
||||
struct MIPS64LE;
|
||||
struct MIPS64BE;
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
@@ -101,6 +102,7 @@ enum : u32 {
|
||||
SHF_TLS = 0x400,
|
||||
SHF_COMPRESSED = 0x800,
|
||||
SHF_GNU_RETAIN = 0x200000,
|
||||
SHF_MIPS_GPREL = 0x10000000,
|
||||
SHF_EXCLUDE = 0x80000000,
|
||||
};
|
||||
|
||||
@@ -182,6 +184,7 @@ enum : u32 {
|
||||
PT_GNU_EH_FRAME = 0x6474e550,
|
||||
PT_GNU_STACK = 0x6474e551,
|
||||
PT_GNU_RELRO = 0x6474e552,
|
||||
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
|
||||
PT_ARM_EXIDX = 0x70000001,
|
||||
};
|
||||
|
||||
@@ -217,6 +220,7 @@ enum : u32 {
|
||||
EM_NONE = 0,
|
||||
EM_386 = 3,
|
||||
EM_68K = 4,
|
||||
EM_MIPS = 8,
|
||||
EM_PPC = 20,
|
||||
EM_PPC64 = 21,
|
||||
EM_S390X = 22,
|
||||
@@ -285,6 +289,13 @@ enum : u32 {
|
||||
DT_VERNEEDNUM = 0x6fffffff,
|
||||
DT_PPC_GOT = 0x70000000,
|
||||
DT_PPC64_GLINK = 0x70000000,
|
||||
DT_MIPS_RLD_VERSION = 0x70000001,
|
||||
DT_MIPS_FLAGS = 0x70000005,
|
||||
DT_MIPS_BASE_ADDRESS = 0x70000006,
|
||||
DT_MIPS_LOCAL_GOTNO = 0x7000000a,
|
||||
DT_MIPS_SYMTABNO = 0x70000011,
|
||||
DT_MIPS_GOTSYM = 0x70000013,
|
||||
DT_MIPS_OPTIONS = 0x70000029,
|
||||
DT_AUXILIARY = 0x7ffffffd,
|
||||
DT_FILTER = 0x7fffffff,
|
||||
};
|
||||
@@ -757,6 +768,9 @@ enum : u32 {
|
||||
R_RISCV_SET32 = 56,
|
||||
R_RISCV_32_PCREL = 57,
|
||||
R_RISCV_IRELATIVE = 58,
|
||||
R_RISCV_PLT32 = 59,
|
||||
R_RISCV_SET_ULEB128 = 60,
|
||||
R_RISCV_SUB_ULEB128 = 61,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -933,9 +947,13 @@ enum : u32 {
|
||||
R_PPC64_REL24_NOTOC = 116,
|
||||
R_PPC64_PLTSEQ = 119,
|
||||
R_PPC64_PLTCALL = 120,
|
||||
R_PPC64_PLTSEQ_NOTOC = 121,
|
||||
R_PPC64_PLTCALL_NOTOC = 122,
|
||||
R_PPC64_PCREL_OPT = 123,
|
||||
R_PPC64_PCREL34 = 132,
|
||||
R_PPC64_GOT_PCREL34 = 133,
|
||||
R_PPC64_PLT_PCREL34 = 134,
|
||||
R_PPC64_PLT_PCREL34_NOTOC = 135,
|
||||
R_PPC64_TPREL34 = 146,
|
||||
R_PPC64_DTPREL34 = 147,
|
||||
R_PPC64_GOT_TLSGD_PCREL34 = 148,
|
||||
@@ -1217,6 +1235,85 @@ enum : u32 {
|
||||
R_ALPHA_TPREL16 = 41,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
R_MIPS_NONE = 0,
|
||||
R_MIPS_16 = 1,
|
||||
R_MIPS_32 = 2,
|
||||
R_MIPS_REL32 = 3,
|
||||
R_MIPS_26 = 4,
|
||||
R_MIPS_HI16 = 5,
|
||||
R_MIPS_LO16 = 6,
|
||||
R_MIPS_GPREL16 = 7,
|
||||
R_MIPS_LITERAL = 8,
|
||||
R_MIPS_GOT16 = 9,
|
||||
R_MIPS_PC16 = 10,
|
||||
R_MIPS_CALL16 = 11,
|
||||
R_MIPS_GPREL32 = 12,
|
||||
R_MIPS_UNUSED1 = 13,
|
||||
R_MIPS_UNUSED2 = 14,
|
||||
R_MIPS_UNUSED3 = 15,
|
||||
R_MIPS_SHIFT5 = 16,
|
||||
R_MIPS_SHIFT6 = 17,
|
||||
R_MIPS_64 = 18,
|
||||
R_MIPS_GOT_DISP = 19,
|
||||
R_MIPS_GOT_PAGE = 20,
|
||||
R_MIPS_GOT_OFST = 21,
|
||||
R_MIPS_GOT_HI16 = 22,
|
||||
R_MIPS_GOT_LO16 = 23,
|
||||
R_MIPS_SUB = 24,
|
||||
R_MIPS_INSERT_A = 25,
|
||||
R_MIPS_INSERT_B = 26,
|
||||
R_MIPS_DELETE = 27,
|
||||
R_MIPS_HIGHER = 28,
|
||||
R_MIPS_HIGHEST = 29,
|
||||
R_MIPS_CALL_HI16 = 30,
|
||||
R_MIPS_CALL_LO16 = 31,
|
||||
R_MIPS_SCN_DISP = 32,
|
||||
R_MIPS_REL16 = 33,
|
||||
R_MIPS_ADD_IMMEDIATE = 34,
|
||||
R_MIPS_PJUMP = 35,
|
||||
R_MIPS_RELGOT = 36,
|
||||
R_MIPS_JALR = 37,
|
||||
R_MIPS_TLS_DTPMOD32 = 38,
|
||||
R_MIPS_TLS_DTPREL32 = 39,
|
||||
R_MIPS_TLS_DTPMOD64 = 40,
|
||||
R_MIPS_TLS_DTPREL64 = 41,
|
||||
R_MIPS_TLS_GD = 42,
|
||||
R_MIPS_TLS_LDM = 43,
|
||||
R_MIPS_TLS_DTPREL_HI16 = 44,
|
||||
R_MIPS_TLS_DTPREL_LO16 = 45,
|
||||
R_MIPS_TLS_GOTTPREL = 46,
|
||||
R_MIPS_TLS_TPREL32 = 47,
|
||||
R_MIPS_TLS_TPREL64 = 48,
|
||||
R_MIPS_TLS_TPREL_HI16 = 49,
|
||||
R_MIPS_TLS_TPREL_LO16 = 50,
|
||||
R_MIPS_GLOB_DAT = 51,
|
||||
R_MIPS_PC21_S2 = 60,
|
||||
R_MIPS_PC26_S2 = 61,
|
||||
R_MIPS_PC18_S3 = 62,
|
||||
R_MIPS_PC19_S2 = 63,
|
||||
R_MIPS_PCHI16 = 64,
|
||||
R_MIPS_PCLO16 = 65,
|
||||
R_MIPS16_26 = 100,
|
||||
R_MIPS16_GPREL = 101,
|
||||
R_MIPS16_GOT16 = 102,
|
||||
R_MIPS16_CALL16 = 103,
|
||||
R_MIPS16_HI16 = 104,
|
||||
R_MIPS16_LO16 = 105,
|
||||
R_MIPS16_TLS_GD = 106,
|
||||
R_MIPS16_TLS_LDM = 107,
|
||||
R_MIPS16_TLS_DTPREL_HI16 = 108,
|
||||
R_MIPS16_TLS_DTPREL_LO16 = 109,
|
||||
R_MIPS16_TLS_GOTTPREL = 110,
|
||||
R_MIPS16_TLS_TPREL_HI16 = 111,
|
||||
R_MIPS16_TLS_TPREL_LO16 = 112,
|
||||
R_MIPS_COPY = 126,
|
||||
R_MIPS_JUMP_SLOT = 127,
|
||||
R_MIPS_NUM = 218,
|
||||
R_MIPS_PC32 = 248,
|
||||
R_MIPS_EH = 249,
|
||||
};
|
||||
|
||||
//
|
||||
// DWARF data types
|
||||
//
|
||||
@@ -1453,14 +1550,8 @@ struct ElfPhdr<E> {
|
||||
// with a newly computed value.
|
||||
//
|
||||
// We don't want to have too many `if (REL)`s and `if (RELA)`s in our
|
||||
// codebase, so we write dynamic relocations in the following manner:
|
||||
//
|
||||
// - We always create a dynamic relocation with an addend. If it's REL,
|
||||
// the addend will be discarded.
|
||||
//
|
||||
// - We also always write an addend to the relocated place by default
|
||||
// even though it's redundant for RELA. If RELA, the written value
|
||||
// will be ovewritten by the dynamic linker at load-time.
|
||||
// codebase, so ElfRel always takes r_addend as a constructor argument.
|
||||
// If it's REL, the argument will simply be ignored.
|
||||
template <typename E> requires E::is_le && E::is_rela
|
||||
struct ElfRel<E> {
|
||||
ElfRel() = default;
|
||||
@@ -1582,6 +1673,9 @@ struct ElfSym<PPC64V2> {
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
bool preserves_r2() const { return ppc_local_entry != 1; }
|
||||
bool uses_toc() const { return ppc_local_entry > 1; }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
@@ -1644,10 +1738,72 @@ struct ElfRel<SPARC64> {
|
||||
ib64 r_addend;
|
||||
};
|
||||
|
||||
static inline u8 get_mips_r_type2(u8 r_type) {
|
||||
if (r_type == R_MIPS_REL32 || r_type == R_MIPS_GLOB_DAT)
|
||||
return R_MIPS_64;
|
||||
return R_NONE;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct ElfRel<MIPS64LE> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
: r_offset(offset), r_sym(sym), r_type(type), r_type2(get_mips_r_type2(type)),
|
||||
r_type3(0), r_padding(0), r_addend(addend) {}
|
||||
|
||||
ul64 r_offset;
|
||||
ul32 r_sym;
|
||||
u8 r_padding;
|
||||
u8 r_type3;
|
||||
u8 r_type2;
|
||||
u8 r_type;
|
||||
il64 r_addend;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfRel<MIPS64BE> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
: r_offset(offset), r_sym(sym), r_padding(0), r_type3(0),
|
||||
r_type2(get_mips_r_type2(type)), r_type(type), r_addend(addend) {}
|
||||
|
||||
ub64 r_offset;
|
||||
ub32 r_sym;
|
||||
u8 r_padding;
|
||||
u8 r_type3;
|
||||
u8 r_type2;
|
||||
u8 r_type;
|
||||
ib64 r_addend;
|
||||
};
|
||||
|
||||
// .MIPS.options section
|
||||
template <typename E>
|
||||
struct MipsOptions {
|
||||
u8 kind;
|
||||
u8 size;
|
||||
U16<E> section;
|
||||
U32<E> info;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct MipsRegInfo {
|
||||
U32<E> ri_gprmask;
|
||||
U32<E> ri_pad;
|
||||
U32<E> ri_cprmask[4];
|
||||
U64<E> ri_gp_value;
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
ODK_REGINFO = 1,
|
||||
};
|
||||
|
||||
//
|
||||
// Machine descriptions
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
static constexpr bool supports_ifunc = requires { E::R_IRELATIVE; };
|
||||
|
||||
template <typename E>
|
||||
static constexpr bool supports_tlsdesc = requires { E::R_TLSDESC; };
|
||||
|
||||
@@ -1670,6 +1826,8 @@ template <typename E> static constexpr bool is_sparc64 = std::is_same_v<E, SPARC
|
||||
template <typename E> static constexpr bool is_m68k = std::is_same_v<E, M68K>;
|
||||
template <typename E> static constexpr bool is_sh4 = std::is_same_v<E, SH4>;
|
||||
template <typename E> static constexpr bool is_alpha = std::is_same_v<E, ALPHA>;
|
||||
template <typename E> static constexpr bool is_mips64le = std::is_same_v<E, MIPS64LE>;
|
||||
template <typename E> static constexpr bool is_mips64be = std::is_same_v<E, MIPS64BE>;
|
||||
|
||||
template <typename E> static constexpr bool is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> static constexpr bool is_arm = is_arm64<E> || is_arm32<E>;
|
||||
@@ -1679,6 +1837,8 @@ template <typename E> static constexpr bool is_riscv = is_rv64<E> || is_rv32<E>;
|
||||
template <typename E> static constexpr bool is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
|
||||
template <typename E> static constexpr bool is_ppc = is_ppc64<E> || is_ppc32<E>;
|
||||
template <typename E> static constexpr bool is_sparc = is_sparc64<E>;
|
||||
template <typename E> static constexpr bool is_mips64 = is_mips64le<E> || is_mips64be<E>;
|
||||
template <typename E> static constexpr bool is_mips = is_mips64<E>;
|
||||
|
||||
struct X86_64 {
|
||||
static constexpr std::string_view target_name = "x86_64";
|
||||
@@ -1896,7 +2056,6 @@ struct PPC64V1 {
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_PPC64;
|
||||
static constexpr u32 plt_hdr_size = 52;
|
||||
static constexpr u32 plt_size = 8;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 28;
|
||||
@@ -1996,7 +2155,6 @@ struct M68K {
|
||||
static constexpr u32 R_JUMP_SLOT = R_68K_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_68K_32;
|
||||
static constexpr u32 R_RELATIVE = R_68K_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_NONE;
|
||||
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;
|
||||
@@ -2018,7 +2176,6 @@ struct SH4 {
|
||||
static constexpr u32 R_JUMP_SLOT = R_SH_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_SH_DIR32;
|
||||
static constexpr u32 R_RELATIVE = R_SH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_NONE;
|
||||
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;
|
||||
@@ -2040,10 +2197,51 @@ struct ALPHA {
|
||||
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
|
||||
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_NONE;
|
||||
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
|
||||
};
|
||||
|
||||
struct MIPS64LE {
|
||||
static constexpr std::string_view target_name = "mips64le";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_MIPS;
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
|
||||
static constexpr u32 R_COPY = R_MIPS_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_MIPS_64;
|
||||
static constexpr u32 R_RELATIVE = R_MIPS_REL32;
|
||||
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
|
||||
};
|
||||
|
||||
struct MIPS64BE {
|
||||
static constexpr std::string_view target_name = "mips64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_MIPS;
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
|
||||
static constexpr u32 R_COPY = R_MIPS_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_MIPS_64;
|
||||
static constexpr u32 R_RELATIVE = R_MIPS_REL32;
|
||||
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
|
||||
};
|
||||
|
||||
} // namespace mold::elf
|
||||
+11
-29
@@ -30,7 +30,7 @@ static bool should_keep(const InputSection<E> &isec) {
|
||||
|
||||
template <typename E>
|
||||
static bool mark_section(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && fast_mark(isec->is_visited);
|
||||
return isec && isec->is_alive && !isec->is_visited.test_and_set();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -53,19 +53,18 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
// Mark a fragment as alive.
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
frag->is_alive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mark_section(sym.get_input_section()))
|
||||
continue;
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
if (mark_section(sym.get_input_section())) {
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +81,7 @@ static void collect_root_set(Context<E> &ctx,
|
||||
auto enqueue_symbol = [&](Symbol<E> *sym) {
|
||||
if (sym) {
|
||||
if (SectionFragment<E> *frag = sym->get_frag())
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
frag->is_alive = true;
|
||||
else
|
||||
enqueue_section(sym->get_input_section());
|
||||
}
|
||||
@@ -94,10 +93,10 @@ static void collect_root_set(Context<E> &ctx,
|
||||
if (!isec || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
// -gc-sections discards only SHF_ALLOC sections. If you want to
|
||||
// --gc-sections discards only SHF_ALLOC sections. If you want to
|
||||
// reduce the amount of non-memory-mapped segments, you should
|
||||
// use `strip` command, compile without debug info or use
|
||||
// -strip-all linker option.
|
||||
// --strip-all linker option.
|
||||
u32 flags = isec->shdr().sh_flags;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
@@ -163,27 +162,10 @@ static void sweep(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
// Non-alloc section fragments are not subject of garbage collection.
|
||||
// This function marks such fragments.
|
||||
template <typename E>
|
||||
static void mark_nonalloc_fragments(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_nonalloc_fragments");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
|
||||
if (m)
|
||||
for (SectionFragment<E> *frag : m->fragments)
|
||||
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
|
||||
mark_nonalloc_fragments(ctx);
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
collect_root_set(ctx, rootset);
|
||||
mark(ctx, rootset);
|
||||
|
||||
+1
-11
@@ -65,7 +65,7 @@
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
#include "../common/sha.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
@@ -90,16 +90,6 @@ template<> struct hash<Digest> {
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
class BitVector {
|
||||
public:
|
||||
BitVector(u32 size) : vec((size + 7) / 8) {}
|
||||
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
|
||||
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
|
||||
|
||||
private:
|
||||
std::vector<u8> vec;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static void uniquify_cies(Context<E> &ctx) {
|
||||
Timer t(ctx, "uniquify_cies");
|
||||
|
||||
+91
-199
@@ -42,6 +42,12 @@ InputFile<E>::InputFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
shstrtab = this->get_string(ctx, shstrtab_idx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<ElfPhdr<E>> InputFile<E>::get_phdrs() {
|
||||
ElfEhdr<E> &ehdr = get_ehdr();
|
||||
return {(ElfPhdr<E> *)(mf->data + ehdr.e_phoff), ehdr.e_phnum};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ElfShdr<E> *InputFile<E>::find_section(i64 type) {
|
||||
for (ElfShdr<E> &sec : elf_sections)
|
||||
@@ -50,21 +56,20 @@ ElfShdr<E> *InputFile<E>::find_section(i64 type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This function is actually racy, in that "tearing" might be observed
|
||||
// during the read or write of `sym->file`. However, on most platforms
|
||||
// read/write of pointers never tears, and even if it did, it would be
|
||||
// harmless, as `sym->file` will not become equal to `sym`, or even if
|
||||
// it did by chance, we just wipe a symbol twice.
|
||||
//
|
||||
// Doing this in a standard-compliant way is too cumbersome, hence
|
||||
// here we just pretend a compiler will never optimize this into a
|
||||
// form that actually exploits the UB.
|
||||
template <typename E>
|
||||
__attribute__((no_sanitize("thread")))
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : get_global_syms())
|
||||
if (sym->file == this)
|
||||
sym->clear();
|
||||
for (Symbol<E> *sym : get_global_syms()) {
|
||||
if (__atomic_load_n(&sym->file, __ATOMIC_ACQUIRE) == this) {
|
||||
sym->origin = 0;
|
||||
sym->value = -1;
|
||||
sym->sym_idx = -1;
|
||||
sym->ver_idx = 0;
|
||||
sym->is_weak = false;
|
||||
sym->is_imported = false;
|
||||
sym->is_exported = false;
|
||||
__atomic_store_n(&sym->file, nullptr, __ATOMIC_RELEASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the source filename. It should be listed in symtab as STT_FILE.
|
||||
@@ -135,6 +140,26 @@ ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 read_mips_gp0(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::string_view data = isec.contents;
|
||||
while (!data.empty()) {
|
||||
if (data.size() < sizeof(MipsOptions<E>))
|
||||
Fatal(ctx) << isec << ": corrupted .MIPS.options section";
|
||||
|
||||
MipsOptions<E> *opt = (MipsOptions<E> *)data.data();
|
||||
if (opt->kind == ODK_REGINFO) {
|
||||
if (data.size() < sizeof(MipsOptions<E>) + sizeof(MipsRegInfo<E>))
|
||||
Fatal(ctx) << isec << ": corrupted .MIPS.options section";
|
||||
MipsRegInfo<E> *info = (MipsRegInfo<E> *)(opt + 1);
|
||||
return info->ri_gp_value;
|
||||
}
|
||||
|
||||
data = data.substr(opt->size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// Read sections
|
||||
@@ -155,7 +180,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
std::string_view signature;
|
||||
if (esym.st_type == STT_SECTION) {
|
||||
signature = this->shstrtab.data() +
|
||||
this->elf_sections[esym.st_shndx].sh_name;
|
||||
this->elf_sections[get_shndx(esym)].sh_name;
|
||||
} else {
|
||||
signature = this->symbol_strtab.data() + esym.st_name;
|
||||
}
|
||||
@@ -181,10 +206,8 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
comdat_groups.push_back({group, (u32)i, entries.subspan(1)});
|
||||
break;
|
||||
}
|
||||
case SHT_SYMTAB_SHNDX:
|
||||
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, shdr);
|
||||
break;
|
||||
case SHT_SYMTAB:
|
||||
case SHT_SYMTAB_SHNDX:
|
||||
case SHT_STRTAB:
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
@@ -198,8 +221,11 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// area in GNU linkers. We ignore that section because silently
|
||||
// making the stack area executable is too dangerous. Tell our
|
||||
// users about the difference if that matters.
|
||||
//
|
||||
// MIPS object files don't contain .note.GNU-stack for some reason,
|
||||
// so ignore this error on MIPS.
|
||||
if (name == ".note.GNU-stack" && !ctx.arg.relocatable) {
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
if ((shdr.sh_flags & SHF_EXECINSTR) && !is_mips<E>) {
|
||||
if (!ctx.arg.z_execstack && !ctx.arg.z_execstack_if_needed)
|
||||
Warn(ctx) << *this << ": this file may cause a segmentation"
|
||||
" fault because it requires an executable stack. See"
|
||||
@@ -254,6 +280,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (name == ".got2")
|
||||
ppc32_got2 = this->sections[i].get();
|
||||
|
||||
if constexpr (is_mips<E>)
|
||||
if (name == ".MIPS.options")
|
||||
mips_gp0 = read_mips_gp0(ctx, *this->sections[i]);
|
||||
|
||||
// Save debug sections for --gdb-index.
|
||||
if (ctx.arg.gdb_index) {
|
||||
InputSection<E> *isec = this->sections[i].get();
|
||||
@@ -319,7 +349,6 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];
|
||||
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
|
||||
read_ehframe(ctx, *isec);
|
||||
isec->is_alive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -490,7 +519,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
this->symbols.resize(this->elf_syms.size());
|
||||
|
||||
i64 num_globals = this->elf_syms.size() - this->first_global;
|
||||
symvers.resize(num_globals);
|
||||
has_symver.resize(num_globals);
|
||||
|
||||
for (i64 i = 0; i < this->first_global; i++)
|
||||
this->symbols[i] = &this->local_syms[i];
|
||||
@@ -505,14 +534,13 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
|
||||
// Parse symbol version after atsign
|
||||
if (i64 pos = name.find('@'); pos != name.npos) {
|
||||
std::string_view ver = name.substr(pos + 1);
|
||||
std::string_view ver = name.substr(pos);
|
||||
name = name.substr(0, pos);
|
||||
|
||||
if (!ver.empty() && ver != "@") {
|
||||
if (ver.starts_with('@'))
|
||||
if (ver != "@" && ver != "@@") {
|
||||
if (ver.starts_with("@@"))
|
||||
key = name;
|
||||
if (!esym.is_undef())
|
||||
symvers[i - this->first_global] = ver.data();
|
||||
has_symver.set(i - this->first_global);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,7 +603,7 @@ static size_t find_null(std::string_view data, u64 entsize) {
|
||||
template <typename E>
|
||||
static std::unique_ptr<MergeableSection<E>>
|
||||
split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
if (!sec.is_alive || sec.sh_size == 0 || sec.relsec_idx != -1)
|
||||
if (!sec.is_alive || sec.relsec_idx != -1)
|
||||
return nullptr;
|
||||
|
||||
const ElfShdr<E> &shdr = sec.shdr();
|
||||
@@ -587,6 +615,9 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
shdr.sh_flags);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
if (sec.sh_size == 0)
|
||||
return rec;
|
||||
|
||||
// If thes section contents are compressed, uncompress them.
|
||||
sec.uncompress(ctx);
|
||||
|
||||
@@ -708,7 +739,7 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
if (m) {
|
||||
m->fragments.reserve(m->strings.size());
|
||||
for (i64 i = 0; i < m->strings.size(); i++)
|
||||
m->fragments.push_back(m->parent->insert(m->strings[i], m->hashes[i],
|
||||
m->fragments.push_back(m->parent->insert(ctx, m->strings[i], m->hashes[i],
|
||||
m->p2align));
|
||||
|
||||
// Shrink vectors that we will never use again to reclaim memory.
|
||||
@@ -726,7 +757,7 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[get_shndx(esym)];
|
||||
if (!m)
|
||||
if (!m || m->fragments.empty())
|
||||
continue;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
@@ -833,6 +864,9 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
this->first_global = symtab_sec->sh_info;
|
||||
this->elf_syms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
|
||||
if (ElfShdr<E> *shdr = this->find_section(SHT_SYMTAB_SHNDX))
|
||||
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, *shdr);
|
||||
}
|
||||
|
||||
initialize_sections(ctx);
|
||||
@@ -854,22 +888,22 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_lazy) {
|
||||
if (esym.is_common()) {
|
||||
assert(!file->is_dso);
|
||||
if (is_lazy)
|
||||
return (6 << 24) + file->priority;
|
||||
return (5 << 24) + file->priority;
|
||||
}
|
||||
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_in_archive) {
|
||||
auto get_sym_rank = [&] {
|
||||
if (esym.is_common()) {
|
||||
assert(!file->is_dso);
|
||||
return is_in_archive ? 6 : 5;
|
||||
}
|
||||
|
||||
if (file->is_dso || is_in_archive)
|
||||
return (esym.st_bind == STB_WEAK) ? 4 : 3;
|
||||
|
||||
if (file->is_dso || is_lazy) {
|
||||
if (esym.st_bind == STB_WEAK)
|
||||
return (4 << 24) + file->priority;
|
||||
return (3 << 24) + file->priority;
|
||||
}
|
||||
if (esym.st_bind == STB_WEAK)
|
||||
return (2 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
return 2;
|
||||
return 1;
|
||||
};
|
||||
|
||||
return (get_sym_rank() << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -943,8 +977,6 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = esym.is_weak();
|
||||
sym.is_imported = false;
|
||||
sym.is_exported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -974,7 +1006,7 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
continue;
|
||||
|
||||
bool keep = esym.is_undef() || (esym.is_common() && !sym.esym().is_common());
|
||||
if (keep && fast_mark(sym.file->is_alive)) {
|
||||
if (keep && !sym.file->is_alive.test_and_set()) {
|
||||
feeder(sym.file);
|
||||
|
||||
if (sym.is_traced)
|
||||
@@ -984,135 +1016,6 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Comdat groups are used to de-duplicate functions and data that may
|
||||
// be included into multiple object files. C++ compiler uses comdat
|
||||
// groups to de-duplicate instantiated templates.
|
||||
//
|
||||
// For example, if a compiler decides to instantiate `std::vector<int>`,
|
||||
// it generates code and data for `std::vector<int>` and put them into a
|
||||
// comdat group whose name is the mangled name of `std::vector<int>`.
|
||||
// The instantiation may happen multiple times for different translation
|
||||
// units. Then linker de-duplicates them so that the resulting executable
|
||||
// contains only a single copy of `std::vector<int>`.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_comdat_groups() {
|
||||
for (ComdatGroupRef<E> &ref : comdat_groups)
|
||||
update_minimum(ref.group->owner, this->priority);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::eliminate_duplicate_comdat_groups() {
|
||||
for (ComdatGroupRef<E> &ref : comdat_groups)
|
||||
if (ref.group->owner != this->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (sections[i])
|
||||
sections[i]->kill();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
if (!this->is_alive)
|
||||
return;
|
||||
|
||||
auto report_undef = [&](Symbol<E> &sym) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = this->get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
};
|
||||
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (!esym.is_undef())
|
||||
continue;
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
// If a protected/hidden undefined symbol is resolved to an
|
||||
// imported symbol, it's handled as if no symbols were found.
|
||||
if (sym.file && sym.file->is_dso &&
|
||||
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
|
||||
report_undef(sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.file &&
|
||||
(!sym.esym().is_undef() || sym.file->priority <= this->priority))
|
||||
continue;
|
||||
|
||||
// If a symbol name is in the form of "foo@version", search for
|
||||
// symbol "foo" and check if the symbol has version "version".
|
||||
std::string_view key = this->symbol_strtab.data() + esym.st_name;
|
||||
if (i64 pos = key.find('@'); pos != key.npos) {
|
||||
Symbol<E> *sym2 = get_symbol(ctx, key.substr(0, pos));
|
||||
if (sym2->file && sym2->file->is_dso &&
|
||||
sym2->get_version() == key.substr(pos + 1)) {
|
||||
this->symbols[i] = sym2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto claim = [&](bool is_imported) {
|
||||
if (sym.is_traced)
|
||||
SyncOut(ctx) << "trace-symbol: " << *this << ": unresolved"
|
||||
<< (esym.is_weak() ? " weak" : "")
|
||||
<< " symbol " << sym;
|
||||
|
||||
sym.file = this;
|
||||
sym.origin = 0;
|
||||
sym.value = 0;
|
||||
sym.sym_idx = i;
|
||||
sym.is_weak = false;
|
||||
sym.is_imported = is_imported;
|
||||
sym.is_exported = false;
|
||||
sym.ver_idx = is_imported ? 0 : ctx.default_version;
|
||||
};
|
||||
|
||||
if (esym.is_undef_weak()) {
|
||||
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
|
||||
ctx.arg.z_dynamic_undefined_weak) {
|
||||
// Global weak undefined symbols are promoted to dynamic symbols
|
||||
// when when linking a DSO, unless `-z nodynamic_undefined_weak`
|
||||
// was given.
|
||||
claim(true);
|
||||
} else {
|
||||
// Otherwise, weak undefs are converted to absolute symbols with value 0.
|
||||
claim(false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
report_undef(sym);
|
||||
|
||||
// Traditionally, remaining undefined symbols cause a link failure
|
||||
// only when we are creating an executable. Undefined symbols in
|
||||
// shared objects are promoted to dynamic symbols, so that they'll
|
||||
// get another chance to be resolved at run-time. You can change the
|
||||
// behavior by passing `-z defs` to the linker.
|
||||
//
|
||||
// Even if `-z defs` is given, weak undefined symbols are still
|
||||
// promoted to dynamic symbols for compatibility with other linkers.
|
||||
// Some major programs, notably Firefox, depend on the behavior
|
||||
// (they use this loophole to export symbols from libxul.so).
|
||||
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
|
||||
(!ctx.arg.z_defs || ctx.arg.unresolved_symbols != UNRESOLVED_ERROR)) {
|
||||
claim(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert remaining undefined symbols to absolute symbols with value 0.
|
||||
if (ctx.arg.unresolved_symbols != UNRESOLVED_ERROR || ctx.arg.noinhibit_exec)
|
||||
claim(false);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations against seciton contents
|
||||
@@ -1200,8 +1103,6 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = false;
|
||||
sym.is_imported = false;
|
||||
sym.is_exported = false;
|
||||
|
||||
sections.push_back(std::move(isec));
|
||||
}
|
||||
@@ -1289,8 +1190,7 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
auto write_sym = [&](Symbol<E> &sym, i64 &symtab_idx) {
|
||||
U32<E> *xindex = nullptr;
|
||||
if (ctx.symtab_shndx)
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
|
||||
symtab_idx * 4);
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + symtab_idx;
|
||||
|
||||
symtab_base[symtab_idx++] = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab_base + strtab_off, sym.name());
|
||||
@@ -1481,8 +1381,6 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = versyms[i];
|
||||
sym.is_weak = false;
|
||||
sym.is_imported = false;
|
||||
sym.is_exported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1498,8 +1396,7 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
if (sym.is_traced)
|
||||
print_trace_symbol(ctx, *this, esym, sym);
|
||||
|
||||
if (esym.is_undef() && sym.file && !sym.file->is_dso &&
|
||||
fast_mark(sym.file->is_alive)) {
|
||||
if (esym.is_undef() && sym.file && !sym.file->is_alive.test_and_set()) {
|
||||
feeder(sym.file);
|
||||
|
||||
if (sym.is_traced)
|
||||
@@ -1510,7 +1407,7 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
std::span<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
assert(sym->file == this);
|
||||
|
||||
std::call_once(init_aliases, [&] {
|
||||
@@ -1526,16 +1423,12 @@ std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
});
|
||||
});
|
||||
|
||||
struct Cmp {
|
||||
bool operator()(Symbol<E> *sym, u64 val) { return sym->esym().st_value < val; }
|
||||
bool operator()(u64 val, Symbol<E> *sym) { return val < sym->esym().st_value; }
|
||||
};
|
||||
auto [begin, end] = std::equal_range(aliases.begin(), aliases.end(), sym,
|
||||
[&](Symbol<E> *x, Symbol<E> *y) {
|
||||
return x->esym().st_value < y->esym().st_value;
|
||||
});
|
||||
|
||||
auto [begin, end] =
|
||||
std::equal_range(aliases.begin(), aliases.end(), sym->esym().st_value, Cmp{});
|
||||
std::vector<Symbol<E> *> vec{begin, end};
|
||||
std::erase(vec, sym);
|
||||
return vec;
|
||||
return {&*begin, &*end};
|
||||
}
|
||||
|
||||
// Infer an alignment of a DSO symbol. An alignment of a symbol in other
|
||||
@@ -1555,13 +1448,12 @@ i64 SharedFile<E>::get_alignment(Symbol<E> *sym) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool SharedFile<E>::is_readonly(Context<E> &ctx, Symbol<E> *sym) {
|
||||
ElfPhdr<E> *phdr = this->get_phdr();
|
||||
bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
|
||||
u64 val = sym->esym().st_value;
|
||||
|
||||
for (i64 i = 0; i < this->get_ehdr().e_phnum; i++)
|
||||
if (phdr[i].p_type == PT_LOAD && !(phdr[i].p_flags & PF_W) &&
|
||||
phdr[i].p_vaddr <= val && val < phdr[i].p_vaddr + phdr[i].p_memsz)
|
||||
for (ElfPhdr<E> &phdr : this->get_phdrs())
|
||||
if (phdr.p_type == PT_LOAD && !(phdr.p_flags & PF_W) &&
|
||||
phdr.p_vaddr <= val && val < phdr.p_vaddr + phdr.p_memsz)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -1601,8 +1493,8 @@ void SharedFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
|
||||
U32<E> *xindex = nullptr;
|
||||
if (ctx.symtab_shndx)
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
|
||||
(this->global_symtab_idx + i) * 4);
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) +
|
||||
this->global_symtab_idx + i;
|
||||
|
||||
*symtab++ = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab + strtab_off, sym.name());
|
||||
|
||||
+78
-56
@@ -57,7 +57,10 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
if constexpr (!E::is_rela)
|
||||
//
|
||||
// SH-4 stores addends to sections despite being RELA, which is a
|
||||
// special (and buggy) case.
|
||||
if constexpr (!E::is_rela || is_sh4<E>)
|
||||
uncompress(ctx);
|
||||
}
|
||||
|
||||
@@ -206,7 +209,7 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
break;
|
||||
case IFUNC:
|
||||
dynrel();
|
||||
ctx.num_ifunc_dynrels.fetch_add(1, std::memory_order_relaxed);
|
||||
ctx.num_ifunc_dynrels++;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -306,6 +309,15 @@ void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::check_tlsle(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
if (ctx.arg.shared)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against `" << sym
|
||||
<< "` can not be used when making a shared object;"
|
||||
<< " recompile with -fPIC";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel, u8 *loc,
|
||||
@@ -349,13 +361,16 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
case DYNREL:
|
||||
apply_dynrel();
|
||||
break;
|
||||
case IFUNC: {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = addr;
|
||||
case IFUNC:
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = addr;
|
||||
} else {
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -385,11 +400,10 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
if constexpr (is_riscv<E>) {
|
||||
if constexpr (is_riscv<E>)
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else {
|
||||
else
|
||||
uncompress_to(ctx, buf);
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (!ctx.arg.relocatable) {
|
||||
@@ -402,7 +416,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
|
||||
for (const ElfSym<E> &esym : file.elf_syms) {
|
||||
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
|
||||
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
|
||||
@@ -415,61 +429,69 @@ std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Record an undefined symbol error which will be displayed all at
|
||||
// once by report_undef_errors().
|
||||
// Test if the symbol a given relocation refers to has already been resolved.
|
||||
// If not, record that error and returns true.
|
||||
template <typename E>
|
||||
void InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = file.get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
ss << ">>> " << file;
|
||||
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
|
||||
ss << ":(" << func << ")";
|
||||
bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
// If a relocation refers to a linker-synthesized symbol for a
|
||||
// section fragment, it's always been resolved.
|
||||
if (file.elf_syms.size() <= rel.r_sym)
|
||||
return false;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
const ElfSym<E> &esym = file.elf_syms[rel.r_sym];
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
}
|
||||
|
||||
// Report all undefined symbols, grouped by symbol.
|
||||
template <typename E>
|
||||
void report_undef_errors(Context<E> &ctx) {
|
||||
constexpr i64 max_errors = 3;
|
||||
|
||||
for (auto &pair : ctx.undef_errors) {
|
||||
std::string_view sym_name = pair.first;
|
||||
std::span<std::string> errors = pair.second;
|
||||
|
||||
if (ctx.arg.demangle)
|
||||
sym_name = demangle(sym_name);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "undefined symbol: " << sym_name << "\n";
|
||||
|
||||
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
|
||||
ss << errors[i];
|
||||
|
||||
if (errors.size() > max_errors)
|
||||
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
Error(ctx) << ss.str();
|
||||
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
Warn(ctx) << ss.str();
|
||||
// If a symbol is defined in a comdat group, and the comdat group is
|
||||
// discarded, the symbol may not have an owner. It is technically an
|
||||
// violation of the One Definition Rule, so it is a programmer's fault.
|
||||
if (!sym.file) {
|
||||
Error(ctx) << *this << ": " << sym << " refers to a discarded COMDAT section"
|
||||
<< " probably due to an ODR violation";
|
||||
return true;
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
auto record = [&] {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = file.get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
ss << ">>> " << file;
|
||||
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
|
||||
ss << ":(" << func << ")";
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
};
|
||||
|
||||
// A non-weak undefined symbol must be promoted to an imported
|
||||
// symbol or resolved to an defined symbol. Otherwise, it's an
|
||||
// undefined symbol error.
|
||||
//
|
||||
// Every ELF file has an absolute local symbol as its first symbol.
|
||||
// Referring to that symbol is always valid.
|
||||
bool is_undef = esym.is_undef() && !esym.is_weak() && sym.sym_idx;
|
||||
if (!sym.is_imported && is_undef && sym.esym().is_undef()) {
|
||||
record();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a protected/hidden undefined symbol is resolved to other .so,
|
||||
// it's handled as if no symbols were found.
|
||||
if (sym.file->is_dso &&
|
||||
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
|
||||
record();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template struct CieRecord<E>;
|
||||
template class InputSection<E>;
|
||||
template void report_undef_errors(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#include "mold.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <fcntl.h>
|
||||
# include <pwd.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
void acquire_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || std::string(jobs) != "1")
|
||||
return;
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (!home)
|
||||
home = getpwuid(getuid())->pw_dir;
|
||||
|
||||
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;
|
||||
|
||||
if (lockf(fd, F_LOCK, 0) == -1)
|
||||
return;
|
||||
|
||||
ctx.global_lock_fd = fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void release_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
if (ctx.global_lock_fd)
|
||||
close(*ctx.global_lock_fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void acquire_global_lock(Context<E> &);
|
||||
template void release_global_lock(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+30
-13
@@ -641,14 +641,34 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
// Initialize object symbols
|
||||
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
|
||||
obj->has_symver.resize(plugin_symbols.size());
|
||||
obj->lto_symbol_versions.resize(plugin_symbols.size());
|
||||
|
||||
for (PluginSymbol &psym : plugin_symbols) {
|
||||
for (i64 i = 0; i < plugin_symbols.size(); i++) {
|
||||
PluginSymbol &psym = plugin_symbols[i];
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
|
||||
|
||||
std::string_view key = save_string(ctx, psym.name);
|
||||
std::string_view name = key;
|
||||
|
||||
// Parse symbol version after atsign
|
||||
if (i64 pos = name.find('@'); pos != name.npos) {
|
||||
std::string_view ver = name.substr(pos);
|
||||
name = name.substr(0, pos);
|
||||
|
||||
if (ver != "@" && ver != "@@") {
|
||||
if (ver.starts_with("@@"))
|
||||
key = name;
|
||||
obj->has_symver.set(i);
|
||||
obj->lto_symbol_versions[i] = ver.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
obj->symbols.push_back(get_symbol(ctx, key, name));
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->symvers.resize(esyms->size());
|
||||
obj->has_symver.resize(esyms->size());
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
}
|
||||
@@ -666,16 +686,13 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file->is_lto_obj)
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
|
||||
if (sym.file && !sym.file->is_dso &&
|
||||
((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.referenced_by_regular_obj = true;
|
||||
if (!file->is_lto_obj) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file && !sym->file->is_dso &&
|
||||
((ObjectFile<E> *)sym->file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->referenced_by_regular_obj = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../integers.h"
|
||||
#include "../common/integers.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
|
||||
+31
-15
@@ -1,7 +1,7 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../output-file.h"
|
||||
#include "../common/archive-file.h"
|
||||
#include "../common/cmdline.h"
|
||||
#include "../common/output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
@@ -70,26 +70,28 @@ std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return SH4::target_name;
|
||||
case EM_ALPHA:
|
||||
return ALPHA::target_name;
|
||||
case EM_MIPS:
|
||||
if (is_64)
|
||||
return is_le ? MIPS64LE::target_name : MIPS64BE::target_name;
|
||||
return "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
bool opt_plugin = !ctx.arg.plugin.empty();
|
||||
|
||||
switch (get_file_type(mf, opt_plugin)) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
|
||||
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
|
||||
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
@@ -163,9 +165,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ctx.visited.contains(mf->name))
|
||||
return;
|
||||
|
||||
bool opt_plugin = !ctx.arg.plugin.empty();
|
||||
FileType type = get_file_type(mf, opt_plugin);
|
||||
switch (type) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, mf, ""));
|
||||
return;
|
||||
@@ -176,7 +176,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(child, opt_plugin)) {
|
||||
switch (get_file_type(ctx, child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
@@ -185,6 +185,10 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
|
||||
ctx.objs.push_back(file);
|
||||
break;
|
||||
case FileType::ELF_DSO:
|
||||
Warn(ctx) << mf->name << "(" << child->name
|
||||
<< "): shared object file in an archive is ignored";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -371,6 +375,10 @@ static int redo_main(int argc, char **argv, std::string_view target) {
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == MIPS64LE::target_name)
|
||||
return elf_main<MIPS64LE>(argc, argv);
|
||||
if (target == MIPS64BE::target_name)
|
||||
return elf_main<MIPS64BE>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
@@ -416,6 +424,8 @@ int elf_main(int argc, char **argv) {
|
||||
on_complete = fork_child();
|
||||
#endif
|
||||
|
||||
acquire_global_lock(ctx);
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
@@ -464,6 +474,9 @@ int elf_main(int argc, char **argv) {
|
||||
// put together in a single phase.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// "Kill" .eh_frame input sections after symbol resolution.
|
||||
kill_eh_frame_sections(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
resolve_section_pieces(ctx);
|
||||
|
||||
@@ -546,10 +559,8 @@ int elf_main(int argc, char **argv) {
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
if (ctx.arg.print_dependencies)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
@@ -729,6 +740,8 @@ int elf_main(int argc, char **argv) {
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
release_global_lock(ctx);
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
|
||||
@@ -741,6 +754,7 @@ int elf_main(int argc, char **argv) {
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
template MappedFile<Context<E>> *open_library(Context<E> &, std::string);
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
|
||||
@@ -759,6 +773,8 @@ extern template int elf_main<SPARC64>(int, char **);
|
||||
extern template int elf_main<M68K>(int, char **);
|
||||
extern template int elf_main<SH4>(int, char **);
|
||||
extern template int elf_main<ALPHA>(int, char **);
|
||||
extern template int elf_main<MIPS64LE>(int, char **);
|
||||
extern template int elf_main<MIPS64BE>(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
|
||||
+2
-1
@@ -61,7 +61,8 @@ static bool is_ld(const char *path) {
|
||||
ptr--;
|
||||
|
||||
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd") ||
|
||||
!strcmp(ptr, "ld.mold");
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
|
||||
+198
-126
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "elf.h"
|
||||
#include "../mold.h"
|
||||
#include "../common/common.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
@@ -60,18 +60,15 @@ std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
|
||||
|
||||
template <typename E>
|
||||
struct SectionFragment {
|
||||
SectionFragment(MergedSection<E> *sec) : output_section(*sec) {}
|
||||
|
||||
SectionFragment(const SectionFragment &other)
|
||||
: output_section(other.output_section), offset(other.offset),
|
||||
p2align(other.p2align.load()), is_alive(other.is_alive.load()) {}
|
||||
SectionFragment(MergedSection<E> *sec, bool is_alive)
|
||||
: output_section(*sec), is_alive(is_alive) {}
|
||||
|
||||
u64 get_addr(Context<E> &ctx) const;
|
||||
|
||||
MergedSection<E> &output_section;
|
||||
u32 offset = -1;
|
||||
std::atomic_uint8_t p2align = 0;
|
||||
std::atomic_bool is_alive = false;
|
||||
Atomic<u8> p2align = 0;
|
||||
Atomic<bool> is_alive = false;
|
||||
};
|
||||
|
||||
// Additional class members for dynamic symbols. Because most symbols
|
||||
@@ -105,7 +102,9 @@ class RangeExtensionThunk {};
|
||||
template <typename E> requires needs_thunk<E>
|
||||
class RangeExtensionThunk<E> {
|
||||
public:
|
||||
RangeExtensionThunk(OutputSection<E> &osec) : output_section(osec) {}
|
||||
RangeExtensionThunk(OutputSection<E> &osec, i64 thunk_idx, i64 offset)
|
||||
: output_section(osec), thunk_idx(thunk_idx), offset(offset) {}
|
||||
|
||||
i64 size() const { return E::thunk_hdr_size + symbols.size() * E::thunk_size; }
|
||||
void copy_buf(Context<E> &ctx);
|
||||
|
||||
@@ -117,15 +116,15 @@ public:
|
||||
static constexpr i64 alignment = 4;
|
||||
|
||||
OutputSection<E> &output_section;
|
||||
i32 thunk_idx = -1;
|
||||
i64 offset = -1;
|
||||
i64 thunk_idx;
|
||||
i64 offset;
|
||||
std::mutex mu;
|
||||
std::vector<Symbol<E> *> symbols;
|
||||
};
|
||||
|
||||
struct RangeExtensionRef {
|
||||
i32 thunk_idx = -1;
|
||||
i32 sym_idx = -1;
|
||||
i16 thunk_idx = -1;
|
||||
i16 sym_idx = -1;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -202,20 +201,6 @@ struct FdeRecord {
|
||||
FdeRecord(u32 input_offset, u32 rel_idx)
|
||||
: input_offset(input_offset), rel_idx(rel_idx) {}
|
||||
|
||||
FdeRecord(const FdeRecord &other)
|
||||
: input_offset(other.input_offset), output_offset(other.output_offset),
|
||||
rel_idx(other.rel_idx), cie_idx(other.cie_idx),
|
||||
is_alive(other.is_alive.load()) {}
|
||||
|
||||
FdeRecord &operator=(const FdeRecord<E> &other) {
|
||||
input_offset = other.input_offset;
|
||||
output_offset = other.output_offset;
|
||||
rel_idx = other.rel_idx;
|
||||
cie_idx = other.cie_idx;
|
||||
is_alive = other.is_alive.load();
|
||||
return *this;
|
||||
}
|
||||
|
||||
i64 size(ObjectFile<E> &file) const;
|
||||
std::string_view get_contents(ObjectFile<E> &file) const;
|
||||
std::span<ElfRel<E>> get_rels(ObjectFile<E> &file) const;
|
||||
@@ -224,7 +209,7 @@ struct FdeRecord {
|
||||
u32 output_offset = -1;
|
||||
u32 rel_idx = -1;
|
||||
u16 cie_idx = -1;
|
||||
std::atomic_bool is_alive = true;
|
||||
Atomic<bool> is_alive = true;
|
||||
};
|
||||
|
||||
// A struct to hold target-dependent input section members.
|
||||
@@ -262,11 +247,11 @@ public:
|
||||
const ElfShdr<E> &shdr() const;
|
||||
std::span<ElfRel<E>> get_rels(Context<E> &ctx) const;
|
||||
std::span<FdeRecord<E>> get_fdes() const;
|
||||
std::string_view get_func_name(Context<E> &ctx, i64 offset);
|
||||
bool is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel);
|
||||
std::string_view get_func_name(Context<E> &ctx, i64 offset) const;
|
||||
bool is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) const;
|
||||
bool is_killed_by_icf() const;
|
||||
|
||||
void record_undef_error(Context<E> &ctx, const ElfRel<E> &rel);
|
||||
bool record_undef_error(Context<E> &ctx, const ElfRel<E> &rel);
|
||||
|
||||
ObjectFile<E> &file;
|
||||
OutputSection<E> *output_section = nullptr;
|
||||
@@ -292,7 +277,7 @@ public:
|
||||
bool uncompressed : 1 = false;
|
||||
|
||||
// For garbage collection
|
||||
std::atomic_bool is_visited = false;
|
||||
Atomic<bool> is_visited = false;
|
||||
|
||||
// For ICF
|
||||
//
|
||||
@@ -312,6 +297,8 @@ private:
|
||||
void scan_dyn_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
|
||||
void scan_toc_rel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
|
||||
|
||||
void check_tlsle(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
|
||||
|
||||
void apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel,
|
||||
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> *&dynrel);
|
||||
|
||||
@@ -328,8 +315,13 @@ private:
|
||||
std::optional<u64> get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag);
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void report_undef_errors(Context<E> &ctx);
|
||||
//
|
||||
// tls.cc
|
||||
//
|
||||
|
||||
template<typename E> u64 get_tls_begin(Context<E> &);
|
||||
template<typename E> u64 get_tp_addr(Context<E> &);
|
||||
template<typename E> u64 get_dtp_addr(Context<E> &);
|
||||
|
||||
//
|
||||
// output-chunks.cc
|
||||
@@ -365,6 +357,7 @@ public:
|
||||
virtual ~Chunk() = default;
|
||||
virtual ChunkKind kind() { return SYNTHETIC; }
|
||||
virtual OutputSection<E> *to_osec() { return nullptr; }
|
||||
virtual i64 get_reldyn_size(Context<E> &ctx) const { return 0; }
|
||||
virtual void copy_buf(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
@@ -373,7 +366,7 @@ public:
|
||||
virtual u8 *get_uncompressed_data() { return nullptr; }
|
||||
|
||||
std::string_view name;
|
||||
ElfShdr<E> shdr = {};
|
||||
ElfShdr<E> shdr = { .sh_addralign = 1 };
|
||||
i64 shndx = 0;
|
||||
|
||||
// Some synethetic sections add local symbols to the output.
|
||||
@@ -388,11 +381,11 @@ public:
|
||||
i64 strtab_size = 0;
|
||||
i64 strtab_offset = 0;
|
||||
|
||||
// Offset in .rel.dyn
|
||||
i64 reldyn_offset = 0;
|
||||
|
||||
// For --section-order
|
||||
i64 sect_order = 0;
|
||||
|
||||
protected:
|
||||
Chunk() { shdr.sh_addralign = 1; }
|
||||
};
|
||||
|
||||
// ELF header
|
||||
@@ -491,9 +484,10 @@ public:
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
|
||||
// We always create a .got so that _GLOBAL_OFFSET_TABLE_ has
|
||||
// something to point to. s390x psABI defines GOT[1] as a
|
||||
// reserved slot, so we allocate one more on s390x.
|
||||
this->shdr.sh_size = (is_s390x<E> ? 2 : 1) * sizeof(Word<E>);
|
||||
// something to point to. s390x/MIPS psABIs define GOT[1] as a
|
||||
// reserved slot, so we allocate one more for them.
|
||||
i64 n = (is_s390x<E> || is_mips<E>) ? 2 : 1;
|
||||
this->shdr.sh_size = n * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void add_got_symbol(Context<E> &ctx, Symbol<E> *sym);
|
||||
@@ -504,16 +498,16 @@ public:
|
||||
|
||||
u64 get_tlsld_addr(Context<E> &ctx) const;
|
||||
bool has_tlsld(Context<E> &ctx) const { return tlsld_idx != -1; }
|
||||
i64 get_reldyn_size(Context<E> &ctx) const;
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
void compute_symtab_size(Context<E> &ctx) override;
|
||||
void populate_symtab(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> got_syms;
|
||||
std::vector<Symbol<E> *> gottp_syms;
|
||||
std::vector<Symbol<E> *> tlsgd_syms;
|
||||
std::vector<Symbol<E> *> tlsdesc_syms;
|
||||
std::vector<Symbol<E> *> gottp_syms;
|
||||
u32 tlsld_idx = -1;
|
||||
|
||||
void construct_relr(Context<E> &ctx);
|
||||
@@ -610,7 +604,6 @@ public:
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
void sort(Context<E> &ctx);
|
||||
};
|
||||
|
||||
@@ -779,7 +772,9 @@ public:
|
||||
static MergedSection<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view name, u64 type, u64 flags);
|
||||
|
||||
SectionFragment<E> *insert(std::string_view data, u64 hash, i64 p2align);
|
||||
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
|
||||
u64 hash, i64 p2align);
|
||||
|
||||
void assign_offsets(Context<E> &ctx);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx, u8 *buf) override;
|
||||
@@ -855,6 +850,7 @@ public:
|
||||
|
||||
void add_symbol(Context<E> &ctx, Symbol<E> *sym);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override { return symbols.size(); }
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
bool is_relro;
|
||||
@@ -1151,7 +1147,7 @@ public:
|
||||
std::string_view get_string(Context<E> &ctx, i64 idx);
|
||||
|
||||
ElfEhdr<E> &get_ehdr() { return *(ElfEhdr<E> *)mf->data; }
|
||||
ElfPhdr<E> *get_phdr() { return (ElfPhdr<E> *)(mf->data + get_ehdr().e_phoff); }
|
||||
std::span<ElfPhdr<E>> get_phdrs();
|
||||
|
||||
ElfShdr<E> *find_section(i64 type);
|
||||
|
||||
@@ -1174,7 +1170,7 @@ public:
|
||||
std::string filename;
|
||||
bool is_dso = false;
|
||||
u32 priority;
|
||||
std::atomic_bool is_alive = false;
|
||||
Atomic<bool> is_alive = false;
|
||||
std::string_view shstrtab;
|
||||
std::string_view symbol_strtab;
|
||||
|
||||
@@ -1210,10 +1206,7 @@ public:
|
||||
void mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(InputFile<E> *)> feeder) override;
|
||||
void convert_undefined_weak_symbols(Context<E> &ctx);
|
||||
void resolve_comdat_groups();
|
||||
void mark_addrsig(Context<E> &ctx);
|
||||
void eliminate_duplicate_comdat_groups();
|
||||
void claim_unresolved_symbols(Context<E> &ctx);
|
||||
void scan_relocations(Context<E> &ctx);
|
||||
void convert_common_symbols(Context<E> &ctx);
|
||||
void compute_symtab_size(Context<E> &ctx);
|
||||
@@ -1229,7 +1222,7 @@ public:
|
||||
std::vector<ElfShdr<E>> elf_sections2;
|
||||
std::vector<CieRecord<E>> cies;
|
||||
std::vector<FdeRecord<E>> fdes;
|
||||
std::vector<const char *> symvers;
|
||||
BitVector has_symver;
|
||||
std::vector<ComdatGroupRef<E>> comdat_groups;
|
||||
bool exclude_libs = false;
|
||||
std::map<u32, u32> gnu_properties;
|
||||
@@ -1262,9 +1255,15 @@ public:
|
||||
i64 num_areas = 0;
|
||||
i64 area_offset = 0;
|
||||
|
||||
// For LTO
|
||||
std::vector<std::string_view> lto_symbol_versions;
|
||||
|
||||
// For PPC32
|
||||
InputSection<E> *ppc32_got2 = nullptr;
|
||||
|
||||
// For MIPS
|
||||
u64 mips_gp0 = 0;
|
||||
|
||||
private:
|
||||
ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name, bool is_in_lib);
|
||||
@@ -1293,9 +1292,9 @@ public:
|
||||
|
||||
void parse(Context<E> &ctx);
|
||||
void resolve_symbols(Context<E> &ctx) override;
|
||||
std::vector<Symbol<E> *> find_aliases(Symbol<E> *sym);
|
||||
std::span<Symbol<E> *> find_aliases(Symbol<E> *sym);
|
||||
i64 get_alignment(Symbol<E> *sym);
|
||||
bool is_readonly(Context<E> &ctx, Symbol<E> *sym);
|
||||
bool is_readonly(Symbol<E> *sym);
|
||||
|
||||
void mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(InputFile<E> *)> feeder) override;
|
||||
@@ -1391,6 +1390,13 @@ template <typename E>
|
||||
[[noreturn]]
|
||||
void process_run_subcommand(Context<E> &ctx, int argc, char **argv);
|
||||
|
||||
//
|
||||
// jobs.cc
|
||||
//
|
||||
|
||||
template <typename E> void acquire_global_lock(Context<E> &ctx);
|
||||
template <typename E> void release_global_lock(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// commandline.cc
|
||||
//
|
||||
@@ -1407,6 +1413,7 @@ template <typename E> void apply_exclude_libs(Context<E> &);
|
||||
template <typename E> void create_synthetic_sections(Context<E> &);
|
||||
template <typename E> void set_file_priority(Context<E> &);
|
||||
template <typename E> void resolve_symbols(Context<E> &);
|
||||
template <typename E> void kill_eh_frame_sections(Context<E> &);
|
||||
template <typename E> void resolve_section_pieces(Context<E> &);
|
||||
template <typename E> void convert_common_symbols(Context<E> &);
|
||||
template <typename E> void compute_merged_section_sizes(Context<E> &);
|
||||
@@ -1414,8 +1421,6 @@ template <typename E> void create_output_sections(Context<E> &);
|
||||
template <typename E> void add_synthetic_symbols(Context<E> &);
|
||||
template <typename E> void check_cet_errors(Context<E> &);
|
||||
template <typename E> void print_dependencies(Context<E> &);
|
||||
template <typename E> void print_dependencies(Context<E> &);
|
||||
template <typename E> void print_dependencies_full(Context<E> &);
|
||||
template <typename E> void write_repro_file(Context<E> &);
|
||||
template <typename E> void check_duplicate_symbols(Context<E> &);
|
||||
template <typename E> void check_symbol_types(Context<E> &);
|
||||
@@ -1428,6 +1433,7 @@ template <typename E> void claim_unresolved_symbols(Context<E> &);
|
||||
template <typename E> void scan_relocations(Context<E> &);
|
||||
template <typename E> void construct_relr(Context<E> &);
|
||||
template <typename E> void create_output_symtab(Context<E> &);
|
||||
template <typename E> void report_undef_errors(Context<E> &);
|
||||
template <typename E> void create_reloc_sections(Context<E> &);
|
||||
template <typename E> void copy_chunks(Context<E> &);
|
||||
template <typename E> void apply_version_script(Context<E> &);
|
||||
@@ -1472,6 +1478,7 @@ public:
|
||||
}
|
||||
|
||||
void add_symbol(Context<PPC64V1> &ctx, Symbol<PPC64V1> *sym);
|
||||
i64 get_reldyn_size(Context<PPC64V1> &ctx) const override;
|
||||
void copy_buf(Context<PPC64V1> &ctx) override;
|
||||
|
||||
static constexpr i64 ENTRY_SIZE = sizeof(Word<PPC64V1>) * 3;
|
||||
@@ -1479,23 +1486,6 @@ public:
|
||||
std::vector<Symbol<PPC64V1> *> symbols;
|
||||
};
|
||||
|
||||
//
|
||||
// arch-s390x.cc
|
||||
//
|
||||
|
||||
class S390XTlsGetOffsetSection : public Chunk<S390X> {
|
||||
public:
|
||||
S390XTlsGetOffsetSection() {
|
||||
this->name = ".tls_get_offset";
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
|
||||
this->shdr.sh_addralign = 4;
|
||||
this->shdr.sh_size = 28;
|
||||
}
|
||||
|
||||
void copy_buf(Context<S390X> &ctx) override;
|
||||
};
|
||||
|
||||
//
|
||||
// arch-sparc.cc
|
||||
//
|
||||
@@ -1529,7 +1519,7 @@ public:
|
||||
void add_symbol(Symbol<ALPHA> &sym, i64 addend);
|
||||
void finalize();
|
||||
u64 get_addr(Symbol<ALPHA> &sym, i64 addend);
|
||||
i64 get_reldyn_size(Context<ALPHA> &ctx);
|
||||
i64 get_reldyn_size(Context<ALPHA> &ctx) const override;
|
||||
void copy_buf(Context<ALPHA> &ctx) override;
|
||||
|
||||
struct Entry {
|
||||
@@ -1538,13 +1528,62 @@ public:
|
||||
i64 addend;
|
||||
};
|
||||
|
||||
i64 reldyn_offset = 0;
|
||||
|
||||
private:
|
||||
std::vector<Entry> entries;
|
||||
std::mutex mu;
|
||||
};
|
||||
|
||||
//
|
||||
// arch-mips64.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class MipsGotSection : public Chunk<E> {
|
||||
public:
|
||||
MipsGotSection() {
|
||||
this->name = ".mips_got";
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_MIPS_GPREL;
|
||||
this->shdr.sh_addralign = 8;
|
||||
this->shdr.sh_size = 8;
|
||||
}
|
||||
|
||||
u64 get_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
|
||||
u64 get_gotpage_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
|
||||
u64 get_gotpage_page_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
|
||||
u64 get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const;
|
||||
u64 get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const;
|
||||
u64 get_tlsld_addr(Context<E> &ctx) const;
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct GotEntry {
|
||||
u64 val = 0;
|
||||
i64 r_type = R_NONE;
|
||||
Symbol<E> *sym = nullptr;
|
||||
};
|
||||
|
||||
struct SymbolAddend {
|
||||
bool operator==(const SymbolAddend &) const = default;
|
||||
bool operator<(const SymbolAddend &) const;
|
||||
u64 get_addr(Context<E> &ctx, i64 flags = 0) const;
|
||||
|
||||
Symbol<E> *sym;
|
||||
i64 addend;
|
||||
};
|
||||
|
||||
std::vector<GotEntry> get_got_entries(Context<E> &ctx) const;
|
||||
|
||||
std::vector<SymbolAddend> got_syms;
|
||||
std::vector<SymbolAddend> gotpage_syms;
|
||||
std::vector<Symbol<E> *> tlsgd_syms;
|
||||
std::vector<Symbol<E> *> gottp_syms;
|
||||
std::atomic_bool needs_tlsld = false;
|
||||
std::mutex mu;
|
||||
};
|
||||
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
@@ -1597,7 +1636,7 @@ struct SectionOrder {
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
// Target-specific context clss
|
||||
// Target-specific context members
|
||||
template <typename E> struct ContextExtras {};
|
||||
|
||||
template <> struct ContextExtras<PPC32> {
|
||||
@@ -1611,20 +1650,23 @@ template <> struct ContextExtras<PPC64V1> {
|
||||
|
||||
template <> struct ContextExtras<PPC64V2> {
|
||||
Symbol<PPC64V2> *TOC = nullptr;
|
||||
Atomic<bool> is_power10 = false;
|
||||
};
|
||||
|
||||
template <> struct ContextExtras<SPARC64> {
|
||||
SparcTlsGetAddrSection *tls_get_addr = nullptr;
|
||||
};
|
||||
|
||||
template <> struct ContextExtras<S390X> {
|
||||
S390XTlsGetOffsetSection *tls_get_offset = nullptr;
|
||||
SparcTlsGetAddrSection *tls_get_addr_sec = nullptr;
|
||||
Symbol<SPARC64> *tls_get_addr_sym = nullptr;
|
||||
};
|
||||
|
||||
template <> struct ContextExtras<ALPHA> {
|
||||
AlphaGotSection *got = nullptr;
|
||||
};
|
||||
|
||||
template <typename E> requires is_mips<E>
|
||||
struct ContextExtras<E> {
|
||||
MipsGotSection<E> *got = nullptr;
|
||||
};
|
||||
|
||||
// Context represents a context object for each invocation of the linker.
|
||||
// It contains command line flags, pointers to singleton objects
|
||||
// (such as linker-synthesized output sections), unique_ptrs for
|
||||
@@ -1652,11 +1694,11 @@ struct Context {
|
||||
bool Bsymbolic = false;
|
||||
bool Bsymbolic_functions = false;
|
||||
bool allow_multiple_definition = false;
|
||||
bool apply_dynamic_relocs = true;
|
||||
bool color_diagnostics = false;
|
||||
bool default_symver = false;
|
||||
bool demangle = true;
|
||||
bool discard_all = false;
|
||||
bool apply_dynamic_relocs = true;
|
||||
bool discard_locals = false;
|
||||
bool eh_frame_hdr = true;
|
||||
bool emit_relocs = false;
|
||||
@@ -1667,7 +1709,7 @@ struct Context {
|
||||
bool fork = true;
|
||||
bool gc_sections = false;
|
||||
bool gdb_index = false;
|
||||
bool hash_style_gnu = true;
|
||||
bool hash_style_gnu = !is_mips<E>;
|
||||
bool hash_style_sysv = true;
|
||||
bool icf = false;
|
||||
bool icf_all = false;
|
||||
@@ -1681,6 +1723,7 @@ struct Context {
|
||||
bool perf = false;
|
||||
bool pic = false;
|
||||
bool pie = false;
|
||||
bool print_dependencies = false;
|
||||
bool print_gc_sections = false;
|
||||
bool print_icf_sections = false;
|
||||
bool print_map = false;
|
||||
@@ -1695,6 +1738,7 @@ struct Context {
|
||||
bool stats = false;
|
||||
bool strip_all = false;
|
||||
bool strip_debug = false;
|
||||
bool suppress_warnings = false;
|
||||
bool trace = false;
|
||||
bool undefined_version = false;
|
||||
bool warn_common = false;
|
||||
@@ -1718,11 +1762,11 @@ struct Context {
|
||||
bool z_relro = true;
|
||||
bool z_shstk = false;
|
||||
bool z_text = false;
|
||||
std::string_view emulation;
|
||||
i64 filler = -1;
|
||||
i64 print_dependencies = 0;
|
||||
i64 spare_dynamic_tags = 5;
|
||||
i64 thread_count = 0;
|
||||
i64 z_stack_size = 0;
|
||||
std::string_view emulation;
|
||||
std::optional<Glob> unique;
|
||||
std::optional<u64> physical_image_base;
|
||||
std::optional<u64> shuffle_sections_seed;
|
||||
@@ -1731,7 +1775,7 @@ struct Context {
|
||||
std::string dependency_file;
|
||||
std::string directory;
|
||||
std::string dynamic_linker;
|
||||
std::string entry = "_start";
|
||||
std::string entry = is_mips<E> ? "__start" : "_start";
|
||||
std::string fini = "_fini";
|
||||
std::string init = "_init";
|
||||
std::string output = "a.out";
|
||||
@@ -1761,8 +1805,11 @@ struct Context {
|
||||
|
||||
std::vector<VersionPattern> version_patterns;
|
||||
u16 default_version = VER_NDX_GLOBAL;
|
||||
bool default_version_from_version_script = false; // true if default_version is set by a wildcard in version script.
|
||||
i64 page_size = -1;
|
||||
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;
|
||||
@@ -1811,7 +1858,6 @@ struct Context {
|
||||
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
std::atomic_bool needs_tlsld = false;
|
||||
std::atomic_bool has_gottp_rel = false;
|
||||
std::atomic_bool has_textrel = false;
|
||||
std::atomic_uint32_t num_ifunc_dynrels = 0;
|
||||
|
||||
@@ -1854,10 +1900,6 @@ struct Context {
|
||||
|
||||
[[no_unique_address]] ContextExtras<E> extra;
|
||||
|
||||
// Frequently accessed symbols
|
||||
Symbol<E> *tls_get_addr = nullptr;
|
||||
Symbol<E> *tls_get_offset = nullptr;
|
||||
|
||||
// For --gdb-index
|
||||
Chunk<E> *debug_info = nullptr;
|
||||
Chunk<E> *debug_abbrev = nullptr;
|
||||
@@ -1894,6 +1936,7 @@ struct Context {
|
||||
Symbol<E> *_edata = nullptr;
|
||||
Symbol<E> *_end = nullptr;
|
||||
Symbol<E> *_etext = nullptr;
|
||||
Symbol<E> *_gp = nullptr;
|
||||
Symbol<E> *edata = nullptr;
|
||||
Symbol<E> *end = nullptr;
|
||||
Symbol<E> *etext = nullptr;
|
||||
@@ -1932,7 +1975,6 @@ enum {
|
||||
NEEDS_COPYREL = 1 << 5,
|
||||
NEEDS_TLSDESC = 1 << 6,
|
||||
NEEDS_PPC_OPD = 1 << 7, // for PPCv1
|
||||
NEEDS_ALPHA_GOT = 1 << 7, // for Alpha
|
||||
};
|
||||
|
||||
// A struct to hold target-dependent symbol members.
|
||||
@@ -1942,8 +1984,8 @@ struct SymbolExtras {};
|
||||
template <typename E> requires needs_thunk<E>
|
||||
struct SymbolExtras<E> {
|
||||
// For range extension thunks
|
||||
i32 thunk_idx = -1;
|
||||
i32 thunk_sym_idx = -1;
|
||||
i16 thunk_idx = -1;
|
||||
i16 thunk_sym_idx = -1;
|
||||
};
|
||||
|
||||
// Flags for Symbol<E>::get_addr()
|
||||
@@ -2022,7 +2064,7 @@ public:
|
||||
std::string_view get_version() const;
|
||||
i64 get_output_sym_idx(Context<E> &ctx) const;
|
||||
const ElfSym<E> &esym() const;
|
||||
void clear();
|
||||
void add_aux(Context<E> &ctx);
|
||||
|
||||
// A symbol is owned by a file. If two or more files define the
|
||||
// same symbol, the one with the strongest definition owns the symbol.
|
||||
@@ -2059,10 +2101,10 @@ public:
|
||||
u16 ver_idx = 0;
|
||||
|
||||
// `flags` has NEEDS_ flags.
|
||||
std::atomic_uint8_t flags = 0;
|
||||
Atomic<u8> flags = 0;
|
||||
|
||||
tbb::spin_mutex mu;
|
||||
std::atomic_uint8_t visibility = STV_DEFAULT;
|
||||
Atomic<u8> visibility = STV_DEFAULT;
|
||||
|
||||
bool is_weak : 1 = false;
|
||||
bool write_to_symtab : 1 = false; // for --strip-all and the like
|
||||
@@ -2168,10 +2210,10 @@ public:
|
||||
//
|
||||
// `has_copyrel` is true if we need to emit a copy relocation for this
|
||||
// symbol. If the original symbol in a DSO is in a read-only memory
|
||||
// region, `copyrel_readonly` is set to true so that the copied data
|
||||
// region, `is_copyrel_readonly` is set to true so that the copied data
|
||||
// will become read-only at run-time.
|
||||
bool has_copyrel : 1 = false;
|
||||
bool copyrel_readonly : 1 = false;
|
||||
bool is_copyrel_readonly : 1 = false;
|
||||
|
||||
// For LTO. True if the symbol is referenced by a regular object (as
|
||||
// opposed to IR object).
|
||||
@@ -2282,6 +2324,9 @@ i64 get_addend(InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
template <typename E>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel);
|
||||
|
||||
template <typename E> requires E::is_rela
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {}
|
||||
|
||||
template <typename E>
|
||||
inline const ElfShdr<E> &InputSection<E>::shdr() const {
|
||||
if (shndx < file.elf_sections.size())
|
||||
@@ -2367,7 +2412,8 @@ InputSection<E>::get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool InputSection<E>::is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
inline bool
|
||||
InputSection<E>::is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) const {
|
||||
return ctx.arg.pack_dyn_relocs_relr &&
|
||||
!(shdr().sh_flags & SHF_EXECINSTR) &&
|
||||
(shdr().sh_addralign % sizeof(Word<E>)) == 0 &&
|
||||
@@ -2445,6 +2491,24 @@ inline InputSection<E> *ObjectFile<E>::get_section(const ElfSym<E> &esym) {
|
||||
return sections[get_shndx(esym)].get();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
OutputSection<E> *find_section(Context<E> &ctx, u32 sh_type) {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_type == sh_type)
|
||||
return osec;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
OutputSection<E> *find_section(Context<E> &ctx, std::string_view name) {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->name == name)
|
||||
return osec;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
|
||||
if (SectionFragment<E> *frag = get_frag()) {
|
||||
@@ -2460,7 +2524,7 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
|
||||
}
|
||||
|
||||
if (has_copyrel) {
|
||||
return copyrel_readonly
|
||||
return is_copyrel_readonly
|
||||
? ctx.copyrel_relro->shdr.sh_addr + value
|
||||
: ctx.copyrel->shdr.sh_addr + value;
|
||||
}
|
||||
@@ -2487,11 +2551,17 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
|
||||
// so pointing to a specific location in a source .eh_frame
|
||||
// section doesn't make much sense. However, CRT files contain
|
||||
// symbols pointing to the very beginning and ending of the section.
|
||||
if (name() == "__EH_FRAME_BEGIN__" || name() == "__EH_FRAME_LIST__" ||
|
||||
name() == ".eh_frame_seg" || esym().st_type == STT_SECTION)
|
||||
//
|
||||
// If LTO is enabled, GCC may add `.lto_priv.<whatever>` as a symbol
|
||||
// suffix. That's why we use starts_with() instead of `==` here.
|
||||
if (name().starts_with("__EH_FRAME_BEGIN__") ||
|
||||
name().starts_with("__EH_FRAME_LIST__") ||
|
||||
name().starts_with(".eh_frame_seg") ||
|
||||
esym().st_type == STT_SECTION)
|
||||
return ctx.eh_frame->shdr.sh_addr;
|
||||
|
||||
if (name() == "__FRAME_END__" || name() == "__EH_FRAME_LIST_END__")
|
||||
if (name().starts_with("__FRAME_END__") ||
|
||||
name().starts_with("__EH_FRAME_LIST_END__"))
|
||||
return ctx.eh_frame->shdr.sh_addr + ctx.eh_frame->shdr.sh_size;
|
||||
|
||||
// ARM object files contain "$d" local symbol at the beginning
|
||||
@@ -2500,7 +2570,7 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
|
||||
if (name() == "$d" || name().starts_with("$d."))
|
||||
return ctx.eh_frame->shdr.sh_addr;
|
||||
|
||||
Fatal(ctx) << "symbol referring .eh_frame is not supported: "
|
||||
Fatal(ctx) << "symbol referring to .eh_frame is not supported: "
|
||||
<< *this << " " << *file;
|
||||
}
|
||||
|
||||
@@ -2545,10 +2615,25 @@ inline u64 Symbol<E>::get_tlsdesc_addr(Context<E> &ctx) const {
|
||||
return ctx.got->shdr.sh_addr + get_tlsdesc_idx(ctx) * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline u64 to_plt_offset(i32 pltidx) {
|
||||
if constexpr (is_ppc64v1<E>) {
|
||||
// The PPC64 ELFv1 ABI requires PLT entries to vary in size
|
||||
// depending on their indices. For entries whose PLT index is
|
||||
// less than 32768, the entry size is 8 bytes. Other entries are
|
||||
// 12 bytes long.
|
||||
if (pltidx < 0x8000)
|
||||
return E::plt_hdr_size + pltidx * 8;
|
||||
return E::plt_hdr_size + 0x8000 * 8 + (pltidx - 0x8000) * 12;
|
||||
} else {
|
||||
return E::plt_hdr_size + pltidx * E::plt_size;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline u64 Symbol<E>::get_plt_addr(Context<E> &ctx) const {
|
||||
if (i32 idx = get_plt_idx(ctx); idx != -1)
|
||||
return ctx.plt->shdr.sh_addr + E::plt_hdr_size + idx * E::plt_size;
|
||||
return ctx.plt->shdr.sh_addr + to_plt_offset<E>(idx);
|
||||
return ctx.pltgot->shdr.sh_addr + get_pltgot_idx(ctx) * E::pltgot_size;
|
||||
}
|
||||
|
||||
@@ -2777,15 +2862,12 @@ inline std::string_view Symbol<E>::name() const {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline void Symbol<E>::clear() {
|
||||
file = nullptr;
|
||||
origin = 0;
|
||||
value = -1;
|
||||
sym_idx = -1;
|
||||
ver_idx = 0;
|
||||
is_weak = false;
|
||||
is_imported = false;
|
||||
is_exported = false;
|
||||
inline void Symbol<E>::add_aux(Context<E> &ctx) {
|
||||
if (aux_idx == -1) {
|
||||
i64 sz = ctx.symbol_aux.size();
|
||||
aux_idx = sz;
|
||||
ctx.symbol_aux.resize(sz + 1);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_c_identifier(std::string_view s) {
|
||||
@@ -2808,16 +2890,6 @@ inline bool is_c_identifier(std::string_view s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool relax_tlsgd(Context<E> &ctx, Symbol<E> &sym) {
|
||||
return ctx.arg.relax && !ctx.arg.shared && !sym.is_imported;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool relax_tlsld(Context<E> &ctx) {
|
||||
return ctx.arg.relax && !ctx.arg.shared;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool relax_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// TLSDESC relocs must be always relaxed for statically-linked
|
||||
|
||||
+216
-241
@@ -1,5 +1,5 @@
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
#include "../common/sha.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <set>
|
||||
@@ -49,9 +49,19 @@ u64 get_eflags(Context<E> &ctx) {
|
||||
return 0;
|
||||
|
||||
u32 ret = objs[0]->get_ehdr().e_flags;
|
||||
for (i64 i = 1; i < objs.size(); i++)
|
||||
if (objs[i]->get_ehdr().e_flags & EF_RISCV_RVC)
|
||||
for (i64 i = 1; i < objs.size(); i++) {
|
||||
u32 flags = objs[i]->get_ehdr().e_flags;
|
||||
if (flags & EF_RISCV_RVC)
|
||||
ret |= EF_RISCV_RVC;
|
||||
|
||||
if ((flags & EF_RISCV_FLOAT_ABI) != (ret & EF_RISCV_FLOAT_ABI))
|
||||
Error(ctx) << *objs[i] << ": cannot link object files with different"
|
||||
<< " floating-point ABI from " << *objs[0];
|
||||
|
||||
if ((flags & EF_RISCV_RVE) != (ret & EF_RISCV_RVE))
|
||||
Error(ctx) << *objs[i] << ": cannot link object files with different"
|
||||
<< " EF_RISCV_RVE from " << *objs[0];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -74,9 +84,8 @@ void OutputEhdr<E>::copy_buf(Context<E> &ctx) {
|
||||
sym->file && !sym->file->is_dso)
|
||||
return sym->get_addr(ctx);
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->name == ".text")
|
||||
return chunk->shdr.sh_addr;
|
||||
if (OutputSection<E> *osec = find_section(ctx, ".text"))
|
||||
return osec->shdr.sh_addr;
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -193,80 +202,6 @@ bool is_relro(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some types of TLS relocations are defined relative to the TLS
|
||||
// segment, so save its addresses for easy access.
|
||||
template <typename E>
|
||||
static void init_thread_pointers(Context<E> &ctx, ElfPhdr<E> phdr) {
|
||||
assert(phdr.p_type == PT_TLS);
|
||||
ctx.tls_begin = phdr.p_vaddr;
|
||||
|
||||
// Each thread has its own value in TP (thread pointer) register.
|
||||
// Thread-local variables (TLVs) defined in the main executable are
|
||||
// accessed relative to TP.
|
||||
//
|
||||
// On x86, SPARC and s390x, TP (%gs on i386, %fs on x86-64, %g7 on SPARC
|
||||
// and %a0/%a1 on s390x) refers past the end of all TLVs for historical
|
||||
// reasons. TLVs are accessed with negative offsets from TP.
|
||||
//
|
||||
// On ARM and SH4, the runtime appends two words at the beginning of TLV
|
||||
// template image when copying TLVs to per-thread area, so we need
|
||||
// to offset it.
|
||||
//
|
||||
// On PPC64 and m68k, TP is 0x7000 (28 KiB) past the beginning of the
|
||||
// TLV block to maximize the addressable range for load/store
|
||||
// instructions with 16-bits signed immediates. It's not exactly 0x8000
|
||||
// (32 KiB) off because there's a small implementation-defined piece of
|
||||
// data before the TLV block, and the runtime wants to access them
|
||||
// efficiently too.
|
||||
//
|
||||
// RISC-V just uses the beginning of the TLV block as TP. RISC-V
|
||||
// load/store instructions usually take 12-bits signed immediates,
|
||||
// so the beginning of TLV ± 2 KiB is accessible with a single
|
||||
// load/store instruction.
|
||||
if constexpr (is_x86<E> || is_sparc<E> || is_s390x<E>) {
|
||||
ctx.tp_addr = align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
|
||||
} else if constexpr (is_arm<E> || is_sh4<E> || is_alpha<E>) {
|
||||
ctx.tp_addr = align_down(phdr.p_vaddr - sizeof(Word<E>) * 2, phdr.p_align);
|
||||
} else if constexpr (is_ppc<E> || is_m68k<E>) {
|
||||
ctx.tp_addr = phdr.p_vaddr + 0x7000;
|
||||
} else {
|
||||
static_assert(is_riscv<E>);
|
||||
ctx.tp_addr = phdr.p_vaddr;
|
||||
}
|
||||
|
||||
// When __tls_get_addr is called to resolve a thread-local variable's
|
||||
// address, the following two arguments are passed to the function:
|
||||
//
|
||||
// 1. the module number that the variable belongs, and
|
||||
// 2. the variable's offset within the module's TLS block.
|
||||
//
|
||||
// These values are usually computed by the dynamic linker and set to
|
||||
// GOT slots as a result of resolving R_DTPMOD and R_DTPOFF dynamic
|
||||
// relocations.
|
||||
//
|
||||
// On PPC64 and m68k, R_DTPOFF is resolved to the address 0x8000 (32 KiB)
|
||||
// past the start of the TLS block. The bias maximizes the accessible
|
||||
// range for load/store instructions with 16-bits signed immediates.
|
||||
// That is, if the offset were right at the beginning of the start of the
|
||||
// TLS block, the half of addressible space (negative immediates) would
|
||||
// have been wasted.
|
||||
//
|
||||
// On RISC-V, the bias is 0x800 as the load/store instructions in the ISA
|
||||
// usually have a 12-bit immediate.
|
||||
//
|
||||
// In most cases we don't have to think about the bias, as the DTPOFF
|
||||
// values are usually computed and used only by runtime. But when we do
|
||||
// compute DTPOFF for statically-linked executable, we need to offset
|
||||
// the bias by subtracting the psABI-specific value.
|
||||
if constexpr (is_ppc<E> || is_m68k<E>) {
|
||||
ctx.dtp_addr = ctx.tls_begin + 0x8000;
|
||||
} else if constexpr (is_riscv<E>) {
|
||||
ctx.dtp_addr = ctx.tls_begin + 0x800;
|
||||
} else {
|
||||
ctx.dtp_addr = ctx.tls_begin;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
|
||||
std::vector<ElfPhdr<E>> vec;
|
||||
@@ -278,8 +213,8 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
|
||||
phdr.p_flags = flags;
|
||||
phdr.p_align = std::max<u64>(min_align, chunk->shdr.sh_addralign);
|
||||
phdr.p_offset = chunk->shdr.sh_offset;
|
||||
phdr.p_filesz =
|
||||
(chunk->shdr.sh_type == SHT_NOBITS) ? 0 : (u64)chunk->shdr.sh_size;
|
||||
if (chunk->shdr.sh_type != SHT_NOBITS)
|
||||
phdr.p_filesz = chunk->shdr.sh_size;
|
||||
phdr.p_vaddr = chunk->shdr.sh_addr;
|
||||
phdr.p_paddr = chunk->shdr.sh_addr;
|
||||
phdr.p_memsz = chunk->shdr.sh_size;
|
||||
@@ -288,7 +223,7 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
|
||||
auto append = [&](Chunk<E> *chunk) {
|
||||
ElfPhdr<E> &phdr = vec.back();
|
||||
phdr.p_align = std::max<u64>(phdr.p_align, chunk->shdr.sh_addralign);
|
||||
if (!(chunk->shdr.sh_type == SHT_NOBITS))
|
||||
if (chunk->shdr.sh_type != SHT_NOBITS)
|
||||
phdr.p_filesz = chunk->shdr.sh_addr + chunk->shdr.sh_size - phdr.p_vaddr;
|
||||
phdr.p_memsz = chunk->shdr.sh_addr + chunk->shdr.sh_size - phdr.p_vaddr;
|
||||
};
|
||||
@@ -370,15 +305,11 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
|
||||
|
||||
// Create a PT_TLS.
|
||||
for (i64 i = 0; i < ctx.chunks.size(); i++) {
|
||||
if (!(ctx.chunks[i]->shdr.sh_flags & SHF_TLS))
|
||||
continue;
|
||||
|
||||
define(PT_TLS, PF_R, 1, ctx.chunks[i++]);
|
||||
while (i < ctx.chunks.size() && (ctx.chunks[i]->shdr.sh_flags & SHF_TLS))
|
||||
append(ctx.chunks[i++]);
|
||||
|
||||
// Initialize ctx.tls_begin and ctx.tp_addr
|
||||
init_thread_pointers(ctx, vec.back());
|
||||
if (ctx.chunks[i]->shdr.sh_flags & SHF_TLS) {
|
||||
define(PT_TLS, PF_R, 1, ctx.chunks[i++]);
|
||||
while (i < ctx.chunks.size() && (ctx.chunks[i]->shdr.sh_flags & SHF_TLS))
|
||||
append(ctx.chunks[i++]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add PT_DYNAMIC
|
||||
@@ -391,11 +322,14 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
|
||||
|
||||
// Add PT_GNU_STACK, which is a marker segment that doesn't really
|
||||
// contain any segments. It controls executable bit of stack area.
|
||||
ElfPhdr<E> phdr = {};
|
||||
phdr.p_type = PT_GNU_STACK,
|
||||
phdr.p_flags = ctx.arg.z_execstack ? (PF_R | PF_W | PF_X) : (PF_R | PF_W),
|
||||
phdr.p_align = 1;
|
||||
vec.push_back(phdr);
|
||||
{
|
||||
ElfPhdr<E> phdr = {};
|
||||
phdr.p_type = PT_GNU_STACK;
|
||||
phdr.p_flags = ctx.arg.z_execstack ? (PF_R | PF_W | PF_X) : (PF_R | PF_W);
|
||||
phdr.p_memsz = ctx.arg.z_stack_size;
|
||||
phdr.p_align = 1;
|
||||
vec.push_back(phdr);
|
||||
}
|
||||
|
||||
// Create a PT_GNU_RELRO.
|
||||
if (ctx.arg.z_relro) {
|
||||
@@ -410,15 +344,15 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add PT_ARM_EDXIDX
|
||||
if constexpr (is_arm32<E>) {
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk->shdr.sh_type == SHT_ARM_EXIDX) {
|
||||
define(PT_ARM_EXIDX, PF_R, 4, chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create a PT_ARM_EDXIDX
|
||||
if constexpr (is_arm32<E>)
|
||||
if (OutputSection<E> *osec = find_section(ctx, SHT_ARM_EXIDX))
|
||||
define(PT_ARM_EXIDX, PF_R, 4, osec);
|
||||
|
||||
// Create a PT_OPENBSD_RANDOMIZE
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->name == ".openbsd.randomdata")
|
||||
define(PT_OPENBSD_RANDOMIZE, PF_R | PF_W, 1, chunk);
|
||||
|
||||
// Set p_paddr if --physical-image-base was given. --physical-image-base
|
||||
// is typically used in embedded programming to specify the base address
|
||||
@@ -473,6 +407,10 @@ template <typename E>
|
||||
void OutputPhdr<E>::update_shdr(Context<E> &ctx) {
|
||||
phdrs = create_phdr(ctx);
|
||||
this->shdr.sh_size = phdrs.size() * sizeof(ElfPhdr<E>);
|
||||
|
||||
ctx.tls_begin = get_tls_begin(ctx);
|
||||
ctx.tp_addr = get_tp_addr(ctx);
|
||||
ctx.dtp_addr = get_dtp_addr(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -492,22 +430,11 @@ void InterpSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void RelDynSection<E>::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_link = ctx.dynsym->shndx;
|
||||
i64 offset = 0;
|
||||
|
||||
// .rel.dyn contents are also filled by GotSection::copy_buf() and
|
||||
// by InputSection::apply_reloc_alloc().
|
||||
i64 offset = ctx.got->get_reldyn_size(ctx);
|
||||
|
||||
offset += ctx.copyrel->symbols.size() * sizeof(ElfRel<E>);
|
||||
offset += ctx.copyrel_relro->symbols.size() * sizeof(ElfRel<E>);
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
if (ctx.arg.pic)
|
||||
offset += ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>) * 2;
|
||||
|
||||
if constexpr (is_alpha<E>) {
|
||||
ctx.extra.got->reldyn_offset = offset;
|
||||
offset += ctx.extra.got->get_reldyn_size(ctx) * sizeof(ElfRel<E>);
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
chunk->reldyn_offset = offset;
|
||||
offset += chunk->get_reldyn_size(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
@@ -516,29 +443,7 @@ void RelDynSection<E>::update_shdr(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
this->shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RelDynSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->shdr.sh_offset +
|
||||
ctx.got->get_reldyn_size(ctx));
|
||||
|
||||
for (Symbol<E> *sym : ctx.copyrel->symbols)
|
||||
*rel++ = ElfRel<E>(sym->get_addr(ctx), E::R_COPY, sym->get_dynsym_idx(ctx), 0);
|
||||
|
||||
for (Symbol<E> *sym : ctx.copyrel_relro->symbols)
|
||||
*rel++ = ElfRel<E>(sym->get_addr(ctx), E::R_COPY, sym->get_dynsym_idx(ctx), 0);
|
||||
|
||||
if constexpr (is_ppc64v1<E>) {
|
||||
if (ctx.arg.pic) {
|
||||
for (Symbol<E> *sym : ctx.extra.opd->symbols) {
|
||||
u64 addr = sym->get_opd_addr(ctx);
|
||||
*rel++ = ElfRel<E>(addr, E::R_RELATIVE, 0,
|
||||
sym->get_addr(ctx, NO_PLT | NO_OPD));
|
||||
*rel++ = ElfRel<E>(addr + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->shdr.sh_link = ctx.dynsym->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -549,11 +454,12 @@ void RelDynSection<E>::sort(Context<E> &ctx) {
|
||||
ElfRel<E> *end = (ElfRel<E> *)((u8 *)begin + this->shdr.sh_size);
|
||||
|
||||
auto get_rank = [](u32 r_type) {
|
||||
switch (r_type) {
|
||||
case E::R_RELATIVE: return 0;
|
||||
case E::R_IRELATIVE: return 2;
|
||||
default: return 1;
|
||||
}
|
||||
if (r_type == E::R_RELATIVE)
|
||||
return 0;
|
||||
if constexpr (supports_ifunc<E>)
|
||||
if (r_type == E::R_IRELATIVE)
|
||||
return 2;
|
||||
return 1;
|
||||
};
|
||||
|
||||
// This is the reason why we sort dynamic relocations. Quote from
|
||||
@@ -699,7 +605,7 @@ void SymtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
|
||||
// Section symbols
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shndx && (chunk->shdr.sh_flags & SHF_ALLOC))
|
||||
if (chunk->shndx)
|
||||
nsyms++;
|
||||
|
||||
// Linker-synthesized symbols
|
||||
@@ -740,12 +646,19 @@ void SymtabSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
// Create section symbols
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk->shndx && (chunk->shdr.sh_flags & SHF_ALLOC)) {
|
||||
if (chunk->shndx) {
|
||||
ElfSym<E> &sym = buf[chunk->shndx];
|
||||
memset(&sym, 0, sizeof(sym));
|
||||
sym.st_type = STT_SECTION;
|
||||
sym.st_value = chunk->shdr.sh_addr;
|
||||
sym.st_shndx = chunk->shndx;
|
||||
|
||||
if (ctx.symtab_shndx) {
|
||||
U32<E> *xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset);
|
||||
xindex[chunk->shndx] = chunk->shndx;
|
||||
sym.st_shndx = SHN_XINDEX;
|
||||
} else {
|
||||
sym.st_shndx = chunk->shndx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,6 +726,8 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
|
||||
} else if constexpr (is_ppc32<E>) {
|
||||
if (ctx.gotplt->shdr.sh_size)
|
||||
define(DT_PLTGOT, ctx.gotplt->shdr.sh_addr + GotPltSection<E>::HDR_SIZE);
|
||||
} else if constexpr (is_mips<E>) {
|
||||
define(DT_PLTGOT, ctx.got->shdr.sh_addr);
|
||||
} else {
|
||||
if (ctx.gotplt->shdr.sh_size)
|
||||
define(DT_PLTGOT, ctx.gotplt->shdr.sh_addr);
|
||||
@@ -828,26 +743,19 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
|
||||
define(DT_STRSZ, ctx.dynstr->shdr.sh_size);
|
||||
}
|
||||
|
||||
auto has_output_section = [&](u32 sh_type) {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_type == sh_type)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
if (has_output_section(SHT_INIT_ARRAY)) {
|
||||
if (find_section(ctx, SHT_INIT_ARRAY)) {
|
||||
define(DT_INIT_ARRAY, ctx.__init_array_start->value);
|
||||
define(DT_INIT_ARRAYSZ,
|
||||
ctx.__init_array_end->value - ctx.__init_array_start->value);
|
||||
}
|
||||
|
||||
if (has_output_section(SHT_PREINIT_ARRAY)) {
|
||||
if (find_section(ctx, SHT_PREINIT_ARRAY)) {
|
||||
define(DT_PREINIT_ARRAY, ctx.__preinit_array_start->value);
|
||||
define(DT_PREINIT_ARRAYSZ,
|
||||
ctx.__preinit_array_end->value - ctx.__preinit_array_start->value);
|
||||
}
|
||||
|
||||
if (has_output_section(SHT_FINI_ARRAY)) {
|
||||
if (find_section(ctx, SHT_FINI_ARRAY)) {
|
||||
define(DT_FINI_ARRAY, ctx.__fini_array_start->value);
|
||||
define(DT_FINI_ARRAYSZ,
|
||||
ctx.__fini_array_end->value - ctx.__fini_array_start->value);
|
||||
@@ -910,7 +818,7 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
|
||||
if (ctx.arg.z_interpose)
|
||||
flags1 |= DF_1_INTERPOSE;
|
||||
|
||||
if (ctx.has_gottp_rel)
|
||||
if (!ctx.got->gottp_syms.empty())
|
||||
flags |= DF_STATIC_TLS;
|
||||
if (ctx.has_textrel)
|
||||
flags |= DF_TEXTREL;
|
||||
@@ -930,6 +838,16 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
|
||||
define(DT_PPC64_GLINK, ctx.plt->shdr.sh_addr + E::plt_hdr_size - 32);
|
||||
}
|
||||
|
||||
if constexpr (is_mips<E>) {
|
||||
define(DT_MIPS_RLD_VERSION, 1);
|
||||
define(DT_MIPS_FLAGS, 0);
|
||||
define(DT_MIPS_BASE_ADDRESS, ctx.arg.image_base);
|
||||
define(DT_MIPS_LOCAL_GOTNO, 2);
|
||||
define(DT_MIPS_SYMTABNO, ctx.dynsym->symbols.size());
|
||||
define(DT_MIPS_GOTSYM, 0);
|
||||
define(DT_MIPS_OPTIONS, 0);
|
||||
}
|
||||
|
||||
// GDB needs a DT_DEBUG entry in an executable to store a word-size
|
||||
// data for its own purpose. Its content is not important.
|
||||
if (!ctx.arg.shared)
|
||||
@@ -968,16 +886,18 @@ void OutputSection<E>::copy_buf(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void OutputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
auto clear = [&](u8 *loc, i64 size) {
|
||||
// As a special case, .init and .fini are filled with NOPs because the
|
||||
// runtime executes the sections as if they were a single function.
|
||||
// .init and .fini are superceded by .init_array and .fini_array and
|
||||
// being actively used only on s390x though.
|
||||
if (is_s390x<E> && (this->name == ".init" || this->name == ".fini")) {
|
||||
for (i64 i = 0; i < size; i += 2)
|
||||
*(ub16 *)(loc + i) = 0x0700; // nop
|
||||
} else {
|
||||
memset(loc, 0, size);
|
||||
// As a special case, .init and .fini are filled with NOPs for s390x
|
||||
// because the runtime executes the sections as if they were a single
|
||||
// function. .init and .fini are superceded by .init_array and
|
||||
// .fini_array but being actively used only on s390x.
|
||||
if constexpr (is_s390x<E>) {
|
||||
if (this->name == ".init" || this->name == ".fini") {
|
||||
for (i64 i = 0; i < size; i += 2)
|
||||
*(ub16 *)(loc + i) = 0x0700; // nop
|
||||
return;
|
||||
}
|
||||
}
|
||||
memset(loc, 0, size);
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
|
||||
@@ -1177,7 +1097,6 @@ void GotSection<E>::add_tlsgd_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
sym->set_tlsgd_idx(ctx, this->shdr.sh_size / sizeof(Word<E>));
|
||||
this->shdr.sh_size += sizeof(Word<E>) * 2;
|
||||
tlsgd_syms.push_back(sym);
|
||||
ctx.dynsym->add_symbol(ctx, sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1186,7 +1105,9 @@ void GotSection<E>::add_tlsdesc_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
sym->set_tlsdesc_idx(ctx, this->shdr.sh_size / sizeof(Word<E>));
|
||||
this->shdr.sh_size += sizeof(Word<E>) * 2;
|
||||
tlsdesc_syms.push_back(sym);
|
||||
ctx.dynsym->add_symbol(ctx, sym);
|
||||
|
||||
if (sym != ctx._TLS_MODULE_BASE_)
|
||||
ctx.dynsym->add_symbol(ctx, sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1245,9 +1166,11 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// IFUNC always needs to be fixed up by the dynamic linker.
|
||||
if (sym->is_ifunc()) {
|
||||
entries.push_back({idx, sym->get_addr(ctx, NO_PLT), E::R_IRELATIVE});
|
||||
continue;
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
if (sym->is_ifunc()) {
|
||||
entries.push_back({idx, sym->get_addr(ctx, NO_PLT), E::R_IRELATIVE});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If we know an address at link-time, fill that GOT entry now.
|
||||
@@ -1262,13 +1185,25 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : ctx.got->tlsgd_syms) {
|
||||
i64 idx = sym->get_tlsgd_idx(ctx);
|
||||
|
||||
if (ctx.arg.is_static) {
|
||||
entries.push_back({idx, 1}); // One indicates the main executable file
|
||||
entries.push_back({idx + 1, sym->get_addr(ctx) - ctx.dtp_addr});
|
||||
} else {
|
||||
// If a symbol is imported, let the dynamic linker to resolve it.
|
||||
if (sym->is_imported) {
|
||||
entries.push_back({idx, 0, E::R_DTPMOD, sym});
|
||||
entries.push_back({idx + 1, 0, E::R_DTPOFF, sym});
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are creating a shared library, we know the TLV's offset
|
||||
// within the current TLS block. We don't know the module ID though.
|
||||
if (ctx.arg.shared) {
|
||||
entries.push_back({idx, 0, E::R_DTPMOD});
|
||||
entries.push_back({idx + 1, sym->get_addr(ctx) - ctx.dtp_addr});
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are creating an executable, we know both the module ID and the
|
||||
// offset. Module ID 1 indicates the main executable.
|
||||
entries.push_back({idx, 1});
|
||||
entries.push_back({idx + 1, sym->get_addr(ctx) - ctx.dtp_addr});
|
||||
}
|
||||
|
||||
if constexpr (supports_tlsdesc<E>) {
|
||||
@@ -1305,10 +1240,10 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
if (ctx.got->tlsld_idx != -1) {
|
||||
if (ctx.arg.is_static)
|
||||
entries.push_back({ctx.got->tlsld_idx, 1}); // 1 means the main executable
|
||||
else
|
||||
if (ctx.arg.shared)
|
||||
entries.push_back({ctx.got->tlsld_idx, 0, E::R_DTPMOD});
|
||||
else
|
||||
entries.push_back({ctx.got->tlsld_idx, 1}); // 1 means the main executable
|
||||
}
|
||||
|
||||
return entries;
|
||||
@@ -1320,7 +1255,7 @@ i64 GotSection<E>::get_reldyn_size(Context<E> &ctx) const {
|
||||
for (GotEntry<E> &ent : get_got_entries(ctx))
|
||||
if (!ent.is_relr(ctx) && ent.r_type != R_NONE)
|
||||
n++;
|
||||
return n * sizeof(ElfRel<E>);
|
||||
return n;
|
||||
}
|
||||
|
||||
// Fill .got and .rel.dyn.
|
||||
@@ -1342,7 +1277,13 @@ void GotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
if (ctx.dynamic && ctx.arg.is_static && ctx.arg.pie)
|
||||
buf[0] = ctx.dynamic->shdr.sh_addr;
|
||||
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset);
|
||||
// It is not clear how the runtime uses it, but all MIPS binaries
|
||||
// have this value in GOT[1].
|
||||
if constexpr (is_mips<E>)
|
||||
buf[1] = E::is_64 ? 0x8000'0000'0000'0000 : 0x8000'0000;
|
||||
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
this->reldyn_offset);
|
||||
|
||||
for (GotEntry<E> &ent : get_got_entries(ctx)) {
|
||||
if (ent.is_relr(ctx) || ent.r_type == R_NONE) {
|
||||
@@ -1484,7 +1425,7 @@ void PltSection<E>::update_shdr(Context<E> &ctx) {
|
||||
if (symbols.empty())
|
||||
this->shdr.sh_size = 0;
|
||||
else
|
||||
this->shdr.sh_size = E::plt_hdr_size + symbols.size() * E::plt_size;
|
||||
this->shdr.sh_size = to_plt_offset<E>(symbols.size());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1493,7 +1434,7 @@ void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
write_plt_header(ctx, buf);
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++)
|
||||
write_plt_entry(ctx, buf + E::plt_hdr_size + i * E::plt_size, *symbols[i]);
|
||||
write_plt_entry(ctx, buf + to_plt_offset<E>(i), *symbols[i]);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1598,17 +1539,15 @@ template <typename E>
|
||||
void RelPltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *buf = (ElfRel<E> *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
for (i64 i = 0; i < ctx.plt->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *ctx.plt->symbols[i];
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
// SPARC doesn't have a .got.plt because its role is merged to .plt.
|
||||
// On SPARC, .plt is writable (!) and the dynamic linker directly
|
||||
// modifies .plt's machine instructions as it resolves dynamic symbols.
|
||||
// Therefore, it doesn't need a separate section to store the symbol
|
||||
// resolution results. That is of course horrible from the security
|
||||
// point of view, though.
|
||||
u64 addr = is_sparc<E> ? sym.get_plt_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
buf[i] = ElfRel<E>(addr, E::R_JUMP_SLOT, sym.get_dynsym_idx(ctx), 0);
|
||||
u64 addr = is_sparc<E> ? sym->get_plt_addr(ctx) : sym->get_gotplt_addr(ctx);
|
||||
*buf++ = ElfRel<E>(addr, E::R_JUMP_SLOT, sym->get_dynsym_idx(ctx), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1619,7 +1558,7 @@ ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
|
||||
esym.st_name = st_name;
|
||||
esym.st_type = sym.esym().st_type;
|
||||
esym.st_type = sym.get_type();
|
||||
esym.st_size = sym.esym().st_size;
|
||||
|
||||
if (sym.is_local(ctx))
|
||||
@@ -1658,7 +1597,7 @@ ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
|
||||
u32 shndx = 0;
|
||||
if (sym.has_copyrel) {
|
||||
shndx = sym.copyrel_readonly ? ctx.copyrel_relro->shndx : ctx.copyrel->shndx;
|
||||
shndx = sym.is_copyrel_readonly ? ctx.copyrel_relro->shndx : ctx.copyrel->shndx;
|
||||
esym.st_value = sym.get_addr(ctx);
|
||||
} else if (sym.file->is_dso || sym.esym().is_undef()) {
|
||||
shndx = SHN_UNDEF;
|
||||
@@ -1760,10 +1699,13 @@ void DynsymSection<E>::finalize(Context<E> &ctx) {
|
||||
// Compute .dynstr size
|
||||
ctx.dynstr->dynsym_offset = ctx.dynstr->shdr.sh_size;
|
||||
|
||||
for (i64 i = 1; i < symbols.size(); i++) {
|
||||
tbb::enumerable_thread_specific<i64> size;
|
||||
tbb::parallel_for((i64)1, (i64)symbols.size(), [&](i64 i) {
|
||||
symbols[i]->set_dynsym_idx(ctx, i);
|
||||
ctx.dynstr->shdr.sh_size += symbols[i]->name().size() + 1;
|
||||
}
|
||||
size.local() += symbols[i]->name().size() + 1;
|
||||
});
|
||||
|
||||
ctx.dynstr->shdr.sh_size += size.combine(std::plus());
|
||||
|
||||
// ELF's symbol table sh_info holds the offset of the first global symbol.
|
||||
this->shdr.sh_info = first_global - symbols.begin();
|
||||
@@ -1960,17 +1902,22 @@ MergedSection<E>::get_instance(Context<E> &ctx, std::string_view name,
|
||||
|
||||
template <typename E>
|
||||
SectionFragment<E> *
|
||||
MergedSection<E>::insert(std::string_view data, u64 hash, i64 p2align) {
|
||||
MergedSection<E>::insert(Context<E> &ctx, std::string_view data, u64 hash,
|
||||
i64 p2align) {
|
||||
std::call_once(once_flag, [&] {
|
||||
// We aim 2/3 occupation ratio
|
||||
map.resize(estimator.get_cardinality() * 3 / 2);
|
||||
});
|
||||
|
||||
// Even if GC is enabled, we garbage-collect only memory-mapped strings.
|
||||
// Non-memory-allocated strings are typically identifiers used by debug info.
|
||||
// To remove such strings, use the `strip` command.
|
||||
bool is_alive = !ctx.arg.gc_sections || !(this->shdr.sh_flags & SHF_ALLOC);
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
bool inserted;
|
||||
std::tie(frag, inserted) = map.insert(data, hash, SectionFragment(this));
|
||||
assert(frag);
|
||||
|
||||
std::tie(frag, inserted) =
|
||||
map.insert(data, hash, SectionFragment(this, is_alive));
|
||||
update_maximum(frag->p2align, p2align);
|
||||
return frag;
|
||||
}
|
||||
@@ -1993,8 +1940,9 @@ void MergedSection<E>::assign_offsets(Context<E> &ctx) {
|
||||
fragments.reserve(shard_size);
|
||||
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
|
||||
if (SectionFragment<E> &frag = map.values[j]; frag.is_alive)
|
||||
fragments.push_back({{map.keys[j], map.key_sizes[j]}, &frag});
|
||||
if (const char *key = map.get_key(j))
|
||||
if (SectionFragment<E> &frag = map.values[j]; frag.is_alive)
|
||||
fragments.push_back({{key, map.key_sizes[j]}, &frag});
|
||||
|
||||
// Sort fragments to make output deterministic.
|
||||
tbb::parallel_sort(fragments.begin(), fragments.end(),
|
||||
@@ -2053,8 +2001,9 @@ void MergedSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
memset(buf + shard_offsets[i], 0, shard_offsets[i + 1] - shard_offsets[i]);
|
||||
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
|
||||
if (SectionFragment<E> &frag = map.values[j]; frag.is_alive)
|
||||
memcpy(buf + frag.offset, map.keys[j], map.key_sizes[j]);
|
||||
if (const char *key = map.get_key(j))
|
||||
if (SectionFragment<E> &frag = map.values[j]; frag.is_alive)
|
||||
memcpy(buf + frag.offset, key, map.key_sizes[j]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2062,7 +2011,7 @@ template <typename E>
|
||||
void MergedSection<E>::print_stats(Context<E> &ctx) {
|
||||
i64 used = 0;
|
||||
for (i64 i = 0; i < map.nbuckets; i++)
|
||||
if (map.keys[i])
|
||||
if (map.get_key(i))
|
||||
used++;
|
||||
|
||||
SyncOut(ctx) << this->name
|
||||
@@ -2312,16 +2261,30 @@ void CopyrelSection<E>::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
assert(!ctx.arg.shared);
|
||||
assert(sym->file->is_dso);
|
||||
|
||||
sym->has_copyrel = true;
|
||||
symbols.push_back(sym);
|
||||
ctx.dynsym->add_symbol(ctx, sym);
|
||||
|
||||
i64 alignment = ((SharedFile<E> *)sym->file)->get_alignment(sym);
|
||||
this->shdr.sh_size = align_to(this->shdr.sh_size, alignment);
|
||||
sym->value = this->shdr.sh_size;
|
||||
SharedFile<E> &file = *(SharedFile<E> *)sym->file;
|
||||
i64 alignment = file.get_alignment(sym);
|
||||
u64 offset = align_to(this->shdr.sh_size, alignment);
|
||||
|
||||
this->shdr.sh_size += sym->esym().st_size;
|
||||
this->shdr.sh_size = offset + sym->esym().st_size;
|
||||
this->shdr.sh_addralign = std::max<i64>(alignment, this->shdr.sh_addralign);
|
||||
|
||||
// We need to create dynamic symbols not only for this particular symbol
|
||||
// but also for its aliases (i.e. other symbols at the same address)
|
||||
// becasue otherwise the aliases are broken apart at runtime.
|
||||
// For example, `environ`, `_environ` and `__environ` in libc.so are
|
||||
// aliases. If one of the symbols is copied by a copy relocation, other
|
||||
// symbols have to refer to the copied place as well.
|
||||
for (Symbol<E> *sym2 : file.find_aliases(sym)) {
|
||||
sym2->add_aux(ctx);
|
||||
sym2->is_imported = true;
|
||||
sym2->is_exported = true;
|
||||
sym2->has_copyrel = true;
|
||||
sym2->is_copyrel_readonly = is_relro;
|
||||
sym2->value = offset;
|
||||
ctx.dynsym->add_symbol(ctx, sym2);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -2339,6 +2302,13 @@ template <typename E>
|
||||
void CopyrelSection<E>::copy_buf(Context<E> &ctx) {
|
||||
if (this->shdr.sh_type == SHT_PROGBITS)
|
||||
memset(ctx.buf + this->shdr.sh_offset, 0, this->shdr.sh_size);
|
||||
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
this->reldyn_offset);
|
||||
|
||||
for (Symbol<E> *sym : symbols)
|
||||
*rel++ = ElfRel<E>(sym->get_addr(ctx), E::R_COPY, sym->get_dynsym_idx(ctx),
|
||||
0);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -2625,12 +2595,13 @@ void NotePropertySection<E>::update_shdr(Context<E> &ctx) {
|
||||
return;
|
||||
|
||||
// Reset to the initial state so that this function is idempotent
|
||||
std::vector<ObjectFile<E> *> files = ctx.objs;
|
||||
std::erase(files, ctx.internal_obj);
|
||||
properties.clear();
|
||||
|
||||
// Obtain the list of keys
|
||||
std::vector<ObjectFile<E> *> files = ctx.objs;
|
||||
std::erase(files, ctx.internal_obj);
|
||||
std::set<u32> keys;
|
||||
|
||||
for (ObjectFile<E> *file : files)
|
||||
for (std::pair<u32, u32> kv : file->gnu_properties)
|
||||
keys.insert(kv.first);
|
||||
@@ -2858,7 +2829,7 @@ void GdbIndexSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u32 mask = symtab_size / 8 - 1;
|
||||
|
||||
for (i64 i = 0; i < map.nbuckets; i++) {
|
||||
if (map.has_key(i)) {
|
||||
if (map.get_key(i)) {
|
||||
u32 hash = map.values[i].hash;
|
||||
u32 step = (hash & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
@@ -2891,13 +2862,13 @@ void GdbIndexSection<E>::copy_buf(Context<E> &ctx) {
|
||||
const i64 shard_size = map.nbuckets / map.NUM_SHARDS;
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)map.NUM_SHARDS, [&](i64 i) {
|
||||
U32<E> *attrs = (U32<E> *)buf;
|
||||
u32 *attrs = (u32 *)buf;
|
||||
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
|
||||
if (map.has_key(j)) {
|
||||
if (map.get_key(j)) {
|
||||
MapEntry &ent = map.values[j];
|
||||
u32 idx = (ent.owner.load()->attrs_offset + ent.attr_offset) / 4;
|
||||
U32<E> *start = attrs + idx + 1;
|
||||
u32 *start = attrs + idx + 1;
|
||||
std::sort(start, start + attrs[idx]);
|
||||
}
|
||||
}
|
||||
@@ -2911,9 +2882,9 @@ void GdbIndexSection<E>::copy_buf(Context<E> &ctx) {
|
||||
// Write pubnames and pubtypes.
|
||||
tbb::parallel_for((i64)0, (i64)map.NUM_SHARDS, [&](i64 i) {
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
|
||||
if (map.has_key(j)) {
|
||||
if (const char *key = map.get_key(j)) {
|
||||
ObjectFile<E> &file = *map.values[j].owner;
|
||||
std::string_view name{map.keys[j], map.key_sizes[j]};
|
||||
std::string_view name{key, map.key_sizes[j]};
|
||||
write_string(buf + file.names_offset + map.values[j].name_offset, name);
|
||||
}
|
||||
}
|
||||
@@ -3083,42 +3054,46 @@ void RelocSection<E>::update_shdr(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void RelocSection<E>::copy_buf(Context<E> &ctx) {
|
||||
auto write = [&](ElfRel<E> &out, InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
memset(&out, 0, sizeof(out));
|
||||
out.r_offset = isec.output_section->shdr.sh_addr + isec.offset + rel.r_offset;
|
||||
out.r_type = rel.r_type;
|
||||
i64 symidx = 0;
|
||||
i64 addend = 0;
|
||||
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
|
||||
if (sym.esym().st_type == STT_SECTION) {
|
||||
i64 addend;
|
||||
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
out.r_sym = frag->output_section.shndx;
|
||||
symidx = frag->output_section.shndx;
|
||||
addend = frag->offset + sym.value + get_addend(isec, rel);
|
||||
} else {
|
||||
InputSection<E> *target = sym.get_input_section();
|
||||
OutputSection<E> *osec = target->output_section;
|
||||
out.r_sym = osec->shndx;
|
||||
addend = get_addend(isec, rel) + target->offset;
|
||||
}
|
||||
|
||||
if constexpr (E::is_rela) {
|
||||
out.r_addend = addend;
|
||||
} else if (ctx.arg.relocatable) {
|
||||
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
|
||||
write_addend(base + rel.r_offset, addend, rel);
|
||||
if (OutputSection<E> *osec = target->output_section) {
|
||||
symidx = osec->shndx;
|
||||
addend = get_addend(isec, rel) + target->offset;
|
||||
} else if (isec.name() == ".eh_frame") {
|
||||
symidx = ctx.eh_frame->shndx;
|
||||
addend = get_addend(isec, rel);
|
||||
} else {
|
||||
// This is usually a dead debug section referring a
|
||||
// COMDAT-eliminated section.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sym.sym_idx)
|
||||
out.r_sym = sym.get_output_sym_idx(ctx);
|
||||
|
||||
if constexpr (E::is_rela)
|
||||
out.r_addend = get_addend(isec, rel);
|
||||
symidx = sym.get_output_sym_idx(ctx);
|
||||
addend = get_addend(isec, rel);
|
||||
}
|
||||
|
||||
if constexpr (is_alpha<E>)
|
||||
if (rel.r_type == R_ALPHA_GPDISP || rel.r_type == R_ALPHA_LITUSE)
|
||||
out.r_addend = rel.r_addend;
|
||||
addend = rel.r_addend;
|
||||
|
||||
i64 r_offset = isec.output_section->shdr.sh_addr + isec.offset + rel.r_offset;
|
||||
out = ElfRel<E>(r_offset, rel.r_type, symidx, addend);
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
|
||||
write_addend(base + rel.r_offset, addend, rel);
|
||||
}
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)output_section.members.size(), [&](i64 i) {
|
||||
|
||||
+255
-128
@@ -113,29 +113,28 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.note_package = push(new NotePackageSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if (ctx.arg.is_static) {
|
||||
if constexpr (is_s390x<E>)
|
||||
ctx.extra.tls_get_offset = push(new S390XTlsGetOffsetSection);
|
||||
|
||||
if constexpr (is_sparc<E>)
|
||||
ctx.extra.tls_get_addr = push(new SparcTlsGetAddrSection);
|
||||
}
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
ctx.extra.opd = push(new PPC64OpdSection);
|
||||
|
||||
if constexpr (is_sparc<E>) {
|
||||
if (ctx.arg.is_static)
|
||||
ctx.extra.tls_get_addr_sec = push(new SparcTlsGetAddrSection);
|
||||
ctx.extra.tls_get_addr_sym = get_symbol(ctx, "__tls_get_addr");
|
||||
}
|
||||
|
||||
if constexpr (is_alpha<E>)
|
||||
ctx.extra.got = push(new AlphaGotSection);
|
||||
|
||||
if constexpr (is_mips<E>)
|
||||
ctx.extra.got = push(new MipsGotSection<E>);
|
||||
|
||||
// If .dynamic exists, .dynsym and .dynstr must exist as well
|
||||
// since .dynamic refers them.
|
||||
if (ctx.dynamic) {
|
||||
ctx.dynstr->keep();
|
||||
ctx.dynsym->keep();
|
||||
}
|
||||
|
||||
ctx.tls_get_addr = get_symbol(ctx, "__tls_get_addr");
|
||||
ctx.tls_get_offset = get_symbol(ctx, "__tls_get_offset");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -225,11 +224,16 @@ void do_resolve_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "eliminate_comdats");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
file->resolve_comdat_groups();
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
update_minimum(ref.group->owner, file->priority);
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
file->eliminate_duplicate_comdat_groups();
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
if (ref.group->owner != file->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (file->sections[i])
|
||||
file->sections[i]->kill();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -273,12 +277,10 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
// Redo name resolution from scratch.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = !file->is_in_lib;
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = !file->is_needed;
|
||||
});
|
||||
|
||||
// Remove IR object files.
|
||||
@@ -292,6 +294,29 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// .eh_frame sections are parsed and regenerated by the linker for the purpose
|
||||
// of deduplication and garbage collection. As such, the input sections should
|
||||
// not be copied over.
|
||||
// However, in very rare cases (e.g. GCC CRT compiled with LTO) we might need
|
||||
// to resolve cross-object .eh_frame section references (they only point to
|
||||
// begin or end and don't depend on the actual section contents).
|
||||
// Therefore, the sections are "killed" after symbol resolution as a separate
|
||||
// pass.
|
||||
template <typename E>
|
||||
void kill_eh_frame_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "kill_eh_frame_sections");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->sections.size(); i++) {
|
||||
if (std::unique_ptr<InputSection<E>> &isec = file->sections[i]) {
|
||||
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
|
||||
isec->is_alive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void resolve_section_pieces(Context<E> &ctx) {
|
||||
Timer t(ctx, "resolve_section_pieces");
|
||||
@@ -331,24 +356,13 @@ void add_comment_string(Context<E> &ctx, std::string str) {
|
||||
|
||||
std::string_view buf = save_string(ctx, str);
|
||||
std::string_view data(buf.data(), buf.size() + 1);
|
||||
SectionFragment<E> *frag = sec->insert(data, hash_string(data), 0);
|
||||
frag->is_alive = true;
|
||||
sec->insert(ctx, data, hash_string(data), 0);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void compute_merged_section_sizes(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_merged_section_sizes");
|
||||
|
||||
// Mark section fragments referenced by live objects.
|
||||
if (!ctx.arg.gc_sections) {
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
|
||||
if (m)
|
||||
for (SectionFragment<E> *frag : m->fragments)
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
// Add an identification string to .comment.
|
||||
if (!ctx.arg.oformat_binary)
|
||||
add_comment_string(ctx, mold_version);
|
||||
@@ -357,7 +371,6 @@ void compute_merged_section_sizes(Context<E> &ctx) {
|
||||
if (char *env = getenv("MOLD_DEBUG"); env && env[0])
|
||||
add_comment_string(ctx, "mold command line: " + get_cmdline_args(ctx));
|
||||
|
||||
Timer t2(ctx, "MergedSection assign_offsets");
|
||||
tbb::parallel_for_each(ctx.merged_sections,
|
||||
[&](std::unique_ptr<MergedSection<E>> &sec) {
|
||||
sec->assign_offsets(ctx);
|
||||
@@ -443,7 +456,7 @@ get_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
|
||||
static std::string_view prefixes[] = {
|
||||
".text.", ".data.rel.ro.", ".data.", ".rodata.", ".bss.rel.ro.", ".bss.",
|
||||
".init_array.", ".fini_array.", ".tbss.", ".tdata.", ".gcc_except_table.",
|
||||
".ctors.", ".dtors.", ".gnu.warning.",
|
||||
".ctors.", ".dtors.", ".gnu.warning.", ".openbsd.randomdata.",
|
||||
};
|
||||
|
||||
for (std::string_view prefix : prefixes) {
|
||||
@@ -479,7 +492,7 @@ template <typename E>
|
||||
void create_output_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_output_sections");
|
||||
|
||||
struct Cmp {
|
||||
struct Hash {
|
||||
size_t operator()(const OutputSectionKey &k) const {
|
||||
u64 h = hash_string(k.name);
|
||||
h = combine_hash(h, std::hash<u64>{}(k.type));
|
||||
@@ -488,34 +501,53 @@ void create_output_sections(Context<E> &ctx) {
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<OutputSectionKey, OutputSection<E> *, Cmp> map;
|
||||
std::unordered_map<OutputSectionKey, OutputSection<E> *, Hash> map;
|
||||
std::shared_mutex mu;
|
||||
|
||||
// Instantiate output sections
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
// Make a per-thread cache of the main map to avoid lock contention.
|
||||
// It makes a noticeable difference if we have millions of input sections.
|
||||
decltype(map) cache;
|
||||
{
|
||||
std::shared_lock lock(mu);
|
||||
cache = map;
|
||||
}
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (!isec || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
OutputSectionKey key = get_output_section_key(ctx, *isec);
|
||||
|
||||
{
|
||||
std::shared_lock lock(mu);
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) {
|
||||
isec->output_section = it->second;
|
||||
continue;
|
||||
}
|
||||
if (auto it = cache.find(key); it != cache.end()) {
|
||||
isec->output_section = it->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputSection<E>> osec =
|
||||
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
|
||||
std::unique_lock lock(mu);
|
||||
auto get_or_insert = [&] {
|
||||
{
|
||||
std::shared_lock lock(mu);
|
||||
if (auto it = map.find(key); it != map.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto [it, inserted] = map.insert({key, osec.get()});
|
||||
isec->output_section = it->second;
|
||||
if (inserted)
|
||||
ctx.osec_pool.emplace_back(std::move(osec));
|
||||
std::unique_ptr<OutputSection<E>> osec =
|
||||
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
|
||||
|
||||
std::unique_lock lock(mu);
|
||||
auto [it, inserted] = map.insert({key, osec.get()});
|
||||
OutputSection<E> *ret = it->second;
|
||||
lock.unlock();
|
||||
|
||||
if (inserted)
|
||||
ctx.osec_pool.emplace_back(std::move(osec));
|
||||
return ret;
|
||||
};
|
||||
|
||||
OutputSection<E> *osec = get_or_insert();
|
||||
isec->output_section = osec;
|
||||
cache.insert({key, osec});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -615,7 +647,7 @@ void create_internal_file(Context<E> &ctx) {
|
||||
add(get_symbol(ctx, ord.name));
|
||||
|
||||
obj->elf_syms = ctx.internal_esyms;
|
||||
obj->symvers.resize(ctx.internal_esyms.size() - 1);
|
||||
obj->has_symver.resize(ctx.internal_esyms.size() - 1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -628,7 +660,7 @@ get_start_stop_name(Context<E> &ctx, Chunk<E> &chunk) {
|
||||
if (ctx.arg.start_stop) {
|
||||
auto isalnum = [](char c) {
|
||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
|
||||
('0' <= c && c <= '9');
|
||||
('0' <= c && c <= '9');
|
||||
};
|
||||
|
||||
std::string s{chunk.name};
|
||||
@@ -715,6 +747,9 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
if constexpr (is_ppc32<E>)
|
||||
ctx.extra._SDA_BASE_ = add("_SDA_BASE_");
|
||||
|
||||
if constexpr (is_mips<E>)
|
||||
ctx._gp = add("_gp");
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
|
||||
add(save_string(ctx, "__start_" + *name));
|
||||
@@ -728,7 +763,7 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
obj.elf_syms = ctx.internal_esyms;
|
||||
obj.symvers.resize(ctx.internal_esyms.size() - 1);
|
||||
obj.has_symver.resize(ctx.internal_esyms.size() - 1);
|
||||
|
||||
obj.resolve_symbols(ctx);
|
||||
|
||||
@@ -765,19 +800,20 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void check_cet_errors(Context<E> &ctx) {
|
||||
bool warning = (ctx.arg.z_cet_report == CET_REPORT_WARNING);
|
||||
assert(warning || (ctx.arg.z_cet_report == CET_REPORT_ERROR));
|
||||
assert(warning || ctx.arg.z_cet_report == CET_REPORT_ERROR);
|
||||
|
||||
auto has_feature = [](ObjectFile<E> *file, u32 feature) {
|
||||
return std::any_of(file->gnu_properties.begin(), file->gnu_properties.end(),
|
||||
[&](auto kv) {
|
||||
return kv.first == GNU_PROPERTY_X86_FEATURE_1_AND
|
||||
&& (kv.second & feature);
|
||||
});
|
||||
[&](std::pair<u32, u32> kv) {
|
||||
return kv.first == GNU_PROPERTY_X86_FEATURE_1_AND &&
|
||||
(kv.second & feature);
|
||||
});
|
||||
};
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file == ctx.internal_obj)
|
||||
continue;
|
||||
|
||||
if (!has_feature(file, GNU_PROPERTY_X86_FEATURE_1_IBT)) {
|
||||
if (warning)
|
||||
Warn(ctx) << *file << ": -cet-report=warning: "
|
||||
@@ -803,37 +839,14 @@ void print_dependencies(Context<E> &ctx) {
|
||||
SyncOut(ctx) <<
|
||||
R"(# This is an output of the mold linker's --print-dependencies option.
|
||||
#
|
||||
# Each line consists of three fields, <file1>, <file2> and <symbol>
|
||||
# separated by tab characters. It indicates that <file1> depends on
|
||||
# <file2> to use <symbol>.)";
|
||||
|
||||
auto print = [&](InputFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (esym.is_undef() && sym.file && sym.file != file)
|
||||
SyncOut(ctx) << *file << "\t" << *sym.file << "\t" << sym;
|
||||
}
|
||||
};
|
||||
|
||||
for (InputFile<E> *file : ctx.objs)
|
||||
print(file);
|
||||
for (InputFile<E> *file : ctx.dsos)
|
||||
print(file);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void print_dependencies_full(Context<E> &ctx) {
|
||||
SyncOut(ctx) <<
|
||||
R"(# This is an output of the mold linker's --print-dependencies=full option.
|
||||
#
|
||||
# Each line consists of 4 fields, <section1>, <section2>, <symbol-type> and
|
||||
# <symbol>, separated by tab characters. It indicates that <section1> depends
|
||||
# on <section2> to use <symbol>. <symbol-type> is either "u" or "w" for
|
||||
# regular undefined or weak undefined, respectively.
|
||||
#
|
||||
# If you want to obtain dependency information per function granularity,
|
||||
# compile source files with the -ffunction-sections compiler flag.)";
|
||||
# compile source files with the -ffunction-sections compiler flag.
|
||||
)";
|
||||
|
||||
auto println = [&](auto &src, Symbol<E> &sym, ElfSym<E> &esym) {
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
@@ -1011,8 +1024,12 @@ void sort_init_fini(Context<E> &ctx) {
|
||||
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_REVERSE)
|
||||
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)});
|
||||
|
||||
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
|
||||
return get_priority(a) < get_priority(b);
|
||||
return map[a] < map[b];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1050,8 +1067,12 @@ void sort_ctor_dtor(Context<E> &ctx) {
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_REVERSE)
|
||||
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)});
|
||||
|
||||
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
|
||||
return get_priority(a) < get_priority(b);
|
||||
return map[a] < map[b];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1210,11 +1231,95 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
osec->shdr.sh_addralign = std::max<u32>(osec->shdr.sh_addralign, align);
|
||||
}
|
||||
|
||||
// Find all unresolved symbols and attach them to the most appropriate files.
|
||||
// Note that even a symbol that will be reported as an undefined symbol will
|
||||
// get an owner file in this function. Such symbol will be reported by
|
||||
// ObjectFile<E>::scan_relocations().
|
||||
template <typename E>
|
||||
void claim_unresolved_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "claim_unresolved_symbols");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->claim_unresolved_symbols(ctx);
|
||||
if (!file->is_alive)
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (!esym.is_undef())
|
||||
continue;
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (sym.file)
|
||||
if (!sym.esym().is_undef() || sym.file->priority <= file->priority)
|
||||
continue;
|
||||
|
||||
// If a symbol name is in the form of "foo@version", search for
|
||||
// symbol "foo" and check if the symbol has version "version".
|
||||
if (file->has_symver.get(i - file->first_global)) {
|
||||
std::string_view str = file->symbol_strtab.data() + esym.st_name;
|
||||
i64 pos = str.find('@');
|
||||
assert(pos != str.npos);
|
||||
|
||||
std::string_view name = str.substr(0, pos);
|
||||
std::string_view ver = str.substr(pos + 1);
|
||||
|
||||
Symbol<E> *sym2 = get_symbol(ctx, name);
|
||||
if (sym2->file && sym2->file->is_dso && sym2->get_version() == ver) {
|
||||
file->symbols[i] = sym2;
|
||||
sym2->is_imported = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto claim = [&](bool is_imported) {
|
||||
if (sym.is_traced)
|
||||
SyncOut(ctx) << "trace-symbol: " << *file << ": unresolved"
|
||||
<< (esym.is_weak() ? " weak" : "")
|
||||
<< " symbol " << sym;
|
||||
|
||||
sym.file = file;
|
||||
sym.origin = 0;
|
||||
sym.value = 0;
|
||||
sym.sym_idx = i;
|
||||
sym.is_weak = false;
|
||||
sym.is_imported = is_imported;
|
||||
sym.is_exported = false;
|
||||
sym.ver_idx = is_imported ? 0 : ctx.default_version;
|
||||
};
|
||||
|
||||
if (esym.is_undef_weak()) {
|
||||
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
|
||||
ctx.arg.z_dynamic_undefined_weak) {
|
||||
// Global weak undefined symbols are promoted to dynamic symbols
|
||||
// when linking a DSO unless `-z nodynamic_undefined_weak` was given.
|
||||
claim(true);
|
||||
} else {
|
||||
// Otherwise, weak undefs are converted to absolute symbols with value 0.
|
||||
claim(false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Traditionally, remaining undefined symbols cause a link failure
|
||||
// only when we are creating an executable. Undefined symbols in
|
||||
// shared objects are promoted to dynamic symbols, so that they'll
|
||||
// get another chance to be resolved at run-time. You can change the
|
||||
// behavior by passing `-z defs` to the linker.
|
||||
//
|
||||
// Even if `-z defs` is given, weak undefined symbols are still
|
||||
// promoted to dynamic symbols for compatibility with other linkers.
|
||||
// Some major programs, notably Firefox, depend on the behavior
|
||||
// (they use this loophole to export symbols from libxul.so).
|
||||
if (ctx.arg.shared && sym.visibility != STV_HIDDEN && !ctx.arg.z_defs) {
|
||||
claim(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert remaining undefined symbols to absolute symbols with value 0.
|
||||
claim(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1247,17 +1352,9 @@ void scan_relocations(Context<E> &ctx) {
|
||||
std::vector<Symbol<E> *> syms = flatten(vec);
|
||||
ctx.symbol_aux.reserve(syms.size());
|
||||
|
||||
auto add_aux = [&](Symbol<E> *sym) {
|
||||
if (sym->aux_idx == -1) {
|
||||
i64 sz = ctx.symbol_aux.size();
|
||||
sym->aux_idx = sz;
|
||||
ctx.symbol_aux.resize(sz + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Assign offsets in additional tables for each dynamic symbol.
|
||||
for (Symbol<E> *sym : syms) {
|
||||
add_aux(sym);
|
||||
sym->add_aux(ctx);
|
||||
|
||||
if (sym->is_imported || sym->is_exported)
|
||||
ctx.dynsym->add_symbol(ctx, sym);
|
||||
@@ -1272,7 +1369,7 @@ void scan_relocations(Context<E> &ctx) {
|
||||
sym->is_exported = true;
|
||||
|
||||
// We can't use .plt.got for a canonical PLT because otherwise
|
||||
// .plt.got and .got would refer each other, resulting in an
|
||||
// .plt.got and .got would refer to each other, resulting in an
|
||||
// infinite loop at runtime.
|
||||
ctx.plt->add_symbol(ctx, sym);
|
||||
} else if (sym->flags & NEEDS_PLT) {
|
||||
@@ -1292,31 +1389,10 @@ void scan_relocations(Context<E> &ctx) {
|
||||
ctx.got->add_tlsdesc_symbol(ctx, sym);
|
||||
|
||||
if (sym->flags & NEEDS_COPYREL) {
|
||||
assert(sym->file->is_dso);
|
||||
SharedFile<E> *file = (SharedFile<E> *)sym->file;
|
||||
sym->copyrel_readonly = file->is_readonly(ctx, sym);
|
||||
|
||||
if (sym->copyrel_readonly)
|
||||
if (((SharedFile<E> *)sym->file)->is_readonly(sym))
|
||||
ctx.copyrel_relro->add_symbol(ctx, sym);
|
||||
else
|
||||
ctx.copyrel->add_symbol(ctx, sym);
|
||||
|
||||
// If a symbol needs copyrel, it is considered both imported
|
||||
// and exported.
|
||||
assert(sym->is_imported);
|
||||
sym->is_exported = true;
|
||||
|
||||
// Aliases of this symbol are also copied so that they will be
|
||||
// resolved to the same address at runtime.
|
||||
for (Symbol<E> *alias : file->find_aliases(sym)) {
|
||||
add_aux(alias);
|
||||
alias->is_imported = true;
|
||||
alias->is_exported = true;
|
||||
alias->has_copyrel = true;
|
||||
alias->value = sym->value;
|
||||
alias->copyrel_readonly = sym->copyrel_readonly;
|
||||
ctx.dynsym->add_symbol(ctx, alias);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
@@ -1336,6 +1412,36 @@ void scan_relocations(Context<E> &ctx) {
|
||||
Warn(ctx) << "creating a DT_TEXTREL in an output file";
|
||||
}
|
||||
|
||||
// Report all undefined symbols, grouped by symbol.
|
||||
template <typename E>
|
||||
void report_undef_errors(Context<E> &ctx) {
|
||||
constexpr i64 max_errors = 3;
|
||||
|
||||
for (auto &pair : ctx.undef_errors) {
|
||||
std::string_view sym_name = pair.first;
|
||||
std::span<std::string> errors = pair.second;
|
||||
|
||||
if (ctx.arg.demangle)
|
||||
sym_name = demangle(sym_name);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "undefined symbol: " << sym_name << "\n";
|
||||
|
||||
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
|
||||
ss << errors[i];
|
||||
|
||||
if (errors.size() > max_errors)
|
||||
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
Error(ctx) << ss.str();
|
||||
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
Warn(ctx) << ss.str();
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_reloc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_reloc_sections");
|
||||
@@ -1376,6 +1482,10 @@ void copy_chunks(Context<E> &ctx) {
|
||||
copy(*chunk);
|
||||
});
|
||||
|
||||
// Undefined symbols in SHF_ALLOC sections are found by scan_relocations(),
|
||||
// but those in non-SHF_ALLOC sections cannot be found until we copy section
|
||||
// contents. So we need to call this function again to report possible
|
||||
// undefined errors.
|
||||
report_undef_errors(ctx);
|
||||
|
||||
if constexpr (is_arm32<E>)
|
||||
@@ -1492,17 +1602,23 @@ void parse_symbol_version(Context<E> &ctx) {
|
||||
verdefs[ctx.arg.version_definitions[i]] = i + VER_NDX_LAST_RESERVED + 1;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->elf_syms.size() - file->first_global; i++) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
// Match VERSION part of symbol foo@VERSION with version definitions.
|
||||
// The symbols' VERSION parts are in file->symvers.
|
||||
if (!file->symvers[i])
|
||||
if (!file->has_symver.get(i - file->first_global))
|
||||
continue;
|
||||
|
||||
Symbol<E> *sym = file->symbols[i + file->first_global];
|
||||
Symbol<E> *sym = file->symbols[i];
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
|
||||
std::string_view ver = file->symvers[i];
|
||||
std::string_view ver;
|
||||
|
||||
if (file->is_lto_obj) {
|
||||
ver = file->lto_symbol_versions[i - file->first_global];
|
||||
} else {
|
||||
const char *name = file->symbol_strtab.data() + file->elf_syms[i].st_name;
|
||||
ver = strchr(name, '@') + 1;
|
||||
}
|
||||
|
||||
bool is_default = false;
|
||||
if (ver.starts_with('@')) {
|
||||
@@ -1526,7 +1642,8 @@ void parse_symbol_version(Context<E> &ctx) {
|
||||
// versioned symbol. Likewise, if `foo@VERSION` and `foo@@VERSION` are
|
||||
// defined, the default one takes precedence.
|
||||
Symbol<E> *sym2 = get_symbol(ctx, sym->name());
|
||||
if (sym2->file == file && !file->symvers[sym2->sym_idx - file->first_global])
|
||||
if (sym2->file == file &&
|
||||
!file->has_symver.get(sym2->sym_idx - file->first_global))
|
||||
if (sym2->ver_idx == ctx.default_version ||
|
||||
(sym2->ver_idx & ~VERSYM_HIDDEN) == (sym->ver_idx & ~VERSYM_HIDDEN))
|
||||
sym2->ver_idx = VER_NDX_LOCAL;
|
||||
@@ -1544,7 +1661,8 @@ void compute_import_export(Context<E> &ctx) {
|
||||
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) {
|
||||
if (sym->ver_idx != VER_NDX_LOCAL ||
|
||||
!ctx.default_version_from_version_script) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
}
|
||||
@@ -1633,6 +1751,7 @@ void clear_padding(Context<E> &ctx) {
|
||||
// .got
|
||||
// .toc
|
||||
// .alpha_got
|
||||
// .mips_got
|
||||
// <writable RELRO bss>
|
||||
// .relro_padding
|
||||
// <writable non-RELRO data>
|
||||
@@ -1718,6 +1837,8 @@ void sort_output_sections_regular(Context<E> &ctx) {
|
||||
return 2;
|
||||
if (chunk->name == ".alpha_got")
|
||||
return 3;
|
||||
if (chunk->name == ".mips_got")
|
||||
return 4;
|
||||
if (chunk == ctx.relro_padding)
|
||||
return INT_MAX;
|
||||
return 0;
|
||||
@@ -2147,17 +2268,18 @@ i64 set_osec_offsets(Context<E> &ctx) {
|
||||
else
|
||||
set_virtual_addresses_by_order(ctx);
|
||||
|
||||
i64 fileoff = set_file_offsets(ctx);
|
||||
|
||||
// Assigning new offsets may change the contents and the length
|
||||
// of the program header, so repeat it until converge.
|
||||
if (!ctx.phdr)
|
||||
return fileoff;
|
||||
i64 fileoff = set_file_offsets(ctx);
|
||||
|
||||
i64 sz = ctx.phdr->shdr.sh_size;
|
||||
ctx.phdr->update_shdr(ctx);
|
||||
if (sz == ctx.phdr->shdr.sh_size)
|
||||
return fileoff;
|
||||
if (ctx.phdr) {
|
||||
i64 sz = ctx.phdr->shdr.sh_size;
|
||||
ctx.phdr->update_shdr(ctx);
|
||||
if (sz != ctx.phdr->shdr.sh_size)
|
||||
continue;
|
||||
}
|
||||
|
||||
return fileoff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2332,6 +2454,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// MIPS' _gp symbol.
|
||||
if (ctx._gp)
|
||||
start(ctx._gp, ctx.got, 0x7ff0);
|
||||
|
||||
// __start_ and __stop_ symbols
|
||||
for (Chunk<E> *chunk : sections) {
|
||||
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
|
||||
@@ -2509,6 +2635,7 @@ template void create_internal_file(Context<E> &);
|
||||
template void apply_exclude_libs(Context<E> &);
|
||||
template void create_synthetic_sections(Context<E> &);
|
||||
template void resolve_symbols(Context<E> &);
|
||||
template void kill_eh_frame_sections(Context<E> &);
|
||||
template void resolve_section_pieces(Context<E> &);
|
||||
template void convert_common_symbols(Context<E> &);
|
||||
template void compute_merged_section_sizes(Context<E> &);
|
||||
@@ -2516,7 +2643,6 @@ template void create_output_sections(Context<E> &);
|
||||
template void add_synthetic_symbols(Context<E> &);
|
||||
template void check_cet_errors(Context<E> &);
|
||||
template void print_dependencies(Context<E> &);
|
||||
template void print_dependencies_full(Context<E> &);
|
||||
template void write_repro_file(Context<E> &);
|
||||
template void check_duplicate_symbols(Context<E> &);
|
||||
template void check_symbol_types(Context<E> &);
|
||||
@@ -2527,6 +2653,7 @@ template void compute_section_sizes(Context<E> &);
|
||||
template void sort_output_sections(Context<E> &);
|
||||
template void claim_unresolved_symbols(Context<E> &);
|
||||
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 construct_relr(Context<E> &);
|
||||
|
||||
+94
-51
@@ -20,6 +20,8 @@
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
@@ -27,33 +29,36 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Branch reach in bytes.
|
||||
//
|
||||
// ARM64's branch has 26 bits immediate, and it's scaled by 4 because all
|
||||
// instructions are 4 bytes aligned, so it's effectively 28 bits long.
|
||||
// That means the range is [-2^27, 2^27).
|
||||
//
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
// aligned to 2, so it's effectively 25 bits. ARM32's non-Thumb branches
|
||||
// have twice longer range than its Thumb counterparts, but we
|
||||
// conservatively use the Thumb's limitation.
|
||||
//
|
||||
// PPC's branch has 24 bits immediate, and the instructions are aligned
|
||||
// to 4, therefore 26.
|
||||
//
|
||||
// Here is the summary of branch instructions reaches:
|
||||
//
|
||||
// ARM64: PC ± 128 MiB
|
||||
// ARM32: PC ± 16 MiB
|
||||
// PPC: PC ± 32 MiB
|
||||
// Returns a branch reach in bytes for a given target.
|
||||
template <typename E>
|
||||
static constexpr i64 max_distance =
|
||||
1LL << (is_arm64<E> ? 27 : is_arm32<E> ? 24 : 25);
|
||||
static consteval i64 max_distance() {
|
||||
// ARM64's branch has 26 bits immediate. The immediate is padded with
|
||||
// implicit two-bit zeros because all instructions are 4 bytes aligned
|
||||
// and therefore the least two bits are always zero. So the branch
|
||||
// operand is effectively 28 bits long. That means the branch range is
|
||||
// [-2^27, 2^27) or PC ± 128 MiB.
|
||||
if (is_arm64<E>)
|
||||
return 1 << 27;
|
||||
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
// aligned to 2, so it's effectively 25 bits. It's [-2^24, 2^24) or PC ±
|
||||
// 16 MiB.
|
||||
//
|
||||
// ARM32's non-Thumb branches have twice longer range than its Thumb
|
||||
// counterparts, but we conservatively use the Thumb's limitation.
|
||||
if (is_arm32<E>)
|
||||
return 1 << 24;
|
||||
|
||||
// PPC's branch has 24 bits immediate, and the instructions are aligned
|
||||
// to 4, therefore the reach is [-2^25, 2^25) or PC ± 32 MiB.
|
||||
assert(is_ppc<E>);
|
||||
return 1 << 25;
|
||||
}
|
||||
|
||||
// We create thunks for each 12.8/1.6/3.2 MiB code block for
|
||||
// ARM64/ARM32/PPC, respectively.
|
||||
template <typename E>
|
||||
static constexpr i64 batch_size = max_distance<E> / 10;
|
||||
static constexpr i64 batch_size = max_distance<E>() / 10;
|
||||
|
||||
// We assume that a single thunk group is smaller than 100 KiB.
|
||||
static constexpr i64 max_thunk_size = 102400;
|
||||
@@ -67,12 +72,13 @@ static bool needs_thunk_rel(const ElfRel<E> &r) {
|
||||
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_CALL || ty == R_ARM_THM_CALL ||
|
||||
ty == R_ARM_PLT32;
|
||||
} else if constexpr (is_ppc32<E>) {
|
||||
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
|
||||
} else {
|
||||
static_assert(is_ppc64<E>);
|
||||
return ty == R_PPC64_REL24;
|
||||
return ty == R_PPC64_REL24 || ty == R_PPC64_REL24_NOTOC;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +108,19 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
if constexpr (is_arm32<E>) {
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_PLT32 && is_thumb))
|
||||
return false;
|
||||
}
|
||||
|
||||
// PowerPC before Power9 lacks PC-relative load/store instructions.
|
||||
// Functions compiled for Power9 or earlier assume that r2 points to
|
||||
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
|
||||
// We need to a thunk to recompute r2 for interworking.
|
||||
if constexpr (is_ppc64v2<E>) {
|
||||
if (rel.r_type == R_PPC64_REL24 && !sym.esym().preserves_r2())
|
||||
return false;
|
||||
if (rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().uses_toc())
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,7 +130,7 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
i64 A = get_addend(isec, rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -max_distance<E> <= val && val < max_distance<E>;
|
||||
return -max_distance<E>() <= val && val < max_distance<E>();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -184,17 +202,34 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
//
|
||||
// Input sections between B and C are in the current batch.
|
||||
//
|
||||
// A is the input section with the smallest address than can reach
|
||||
// anywhere 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.
|
||||
//
|
||||
// ................................ <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
|
||||
// <-------------> Reachable from the current batch
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
i64 thunk_idx = 0;
|
||||
|
||||
while (b < m.size()) {
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk after D.
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk at D.
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance<E>) {
|
||||
m[b]->offset + max_distance<E>()) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
@@ -211,48 +246,53 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset + max_distance<E> < c_offset)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
while (a < m.size() && m[a]->offset + max_distance<E>() < c_offset)
|
||||
a++;
|
||||
|
||||
// Erase references to out-of-range thunks.
|
||||
while (thunk_idx < osec.thunks.size() &&
|
||||
osec.thunks[thunk_idx]->offset < m[a]->offset)
|
||||
reset_thunk(*osec.thunks[thunk_idx++]);
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
offset = align_to(offset, thunk.alignment);
|
||||
thunk.offset = offset;
|
||||
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
|
||||
RangeExtensionThunk<E> *thunk =
|
||||
new RangeExtensionThunk<E>(osec, osec.thunks.size(), offset);
|
||||
osec.thunks.emplace_back(thunk);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, thunk);
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, *thunk);
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
assert(thunk.size() < max_thunk_size);
|
||||
offset += thunk.size();
|
||||
assert(thunk->size() < max_thunk_size);
|
||||
offset += thunk->size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
sort(thunk->symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->sym_idx} <
|
||||
std::tuple{b->file->priority, b->sym_idx};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
for (i64 i = 0; i < thunk->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *thunk->symbols[i];
|
||||
sym.extra.thunk_idx = thunk->thunk_idx;
|
||||
sym.extra.thunk_sym_idx = i;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
|
||||
[&](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;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx)
|
||||
if (range_extn[i].thunk_idx == thunk->thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
@@ -260,15 +300,18 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
while (thunk_idx < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[thunk_idx++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
|
||||
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
// This file contains helper functions for thread-local storage (TLS).
|
||||
// TLS is probably the most obscure feature the linker has to support,
|
||||
// so I'll explain it in detail in this comment.
|
||||
//
|
||||
// TLS is a per-thread storage. Thread-local variables (TLVs) are in a TLS
|
||||
// so that each thread has its own set of thread-local variables. Taking
|
||||
// an address of a TLV returns a unique value for each thread. For example,
|
||||
// `&foo` for the following code returns different pointer values for
|
||||
// different threads.
|
||||
//
|
||||
// thread_local int foo;
|
||||
//
|
||||
// TLV is a relatively new feature. C for example didn't provide the
|
||||
// official support for it through the keyword `thread_local` until C11.
|
||||
// TLV needs a coordination between the compiler, the linker and the
|
||||
// runtime to work correctly.
|
||||
//
|
||||
// An ELF exectuable or a shared library using TLV contains a "TLS template
|
||||
// image" in the PT_TLS segment. For each newly created thread including the
|
||||
// initial one, the runtime allocates a contiguous memory for an executable
|
||||
// and its depending shared libraries and copies template images there. That
|
||||
// per-thread memory is called the "TLS block". After allocating and
|
||||
// initializing a TLS block, the runtime sets a register to refer to the TLS
|
||||
// block, so that the thread-local variables are accessible relative to the
|
||||
// register.
|
||||
//
|
||||
// The register referring to the per-thread storage is called the Thread
|
||||
// Pointer (TP). TP is part of the thread's context. When the kernel
|
||||
// scheduler switches threads, TP is saved and restored automatically just
|
||||
// like other registers are.
|
||||
//
|
||||
// The TLS template image is read-only. It contains TLVs' initial values
|
||||
// for new threads, and no one writes to it at runtime.
|
||||
//
|
||||
// Now, let's think about how to access a TLV. We need to know the TLV's
|
||||
// address to access it which can be done in several different ways as
|
||||
// follows:
|
||||
//
|
||||
// 1. If we are creating an executable, we know the exact size of the TLS
|
||||
// template image we are creating, and we know where the TP will be
|
||||
// set to after the template is copied to the TLS block. Therefore,
|
||||
// the TP-relative address of a TLV in the main executable is known at
|
||||
// link-time. That means, computing a TLV's address can be as easy as
|
||||
// `add %dst, %tp, <link-time constant>`.
|
||||
//
|
||||
// 2. If we are creating a shared library, we don't exactly know where
|
||||
// its TLS template image will be copied to in terms of the
|
||||
// TP-relative address, because we don't know how large the main
|
||||
// executable's and other libraries' TLS template images are. Only the
|
||||
// runtime knows the exact TP-relative address.
|
||||
//
|
||||
// We can solve the problem with an indirection. Specifically, for
|
||||
// each TLV whose TP-relative address is only known at process startup
|
||||
// time, we create a GOT entry to store its TP-relative address. We
|
||||
// also emit a dynamic relocation to let the runtime to fill the GOT
|
||||
// entry with a TP-relative address.
|
||||
//
|
||||
// Computing a TLV address in this scheme needs at least two machine
|
||||
// instructions in most ISAs; the first instruction loads a value from
|
||||
// the GOT entry, and the second one adds the loaded value to TP.
|
||||
//
|
||||
// 3. Now, think about libraries that are dynamically loaded with dlopen.
|
||||
// The TLS block for such library has to be allocated separately from
|
||||
// the initial TLS block, so we now have two or more discontiguous
|
||||
// TLS blocks. There's no easy formula to compute an address of a TLV
|
||||
// in a separate TLS block.
|
||||
//
|
||||
// The address of a TLV in a separate TLS block can be obtained by
|
||||
// calling the libc-provided function, __tls_get_addr(). The function
|
||||
// takes two arguments; a module ID to identify the ELF file and the
|
||||
// TLV's offset within the ELF file's TLS template image. Accessing a
|
||||
// TLV is sometimes compiled to a function call! The module ID and the
|
||||
// offset are usually stored to GOT as two consecutive words.
|
||||
//
|
||||
// The last access method is the most generic, so the compiler emits such
|
||||
// code by default. But that's the most expensive one, so the linker
|
||||
// rewrites instructions if possible so that 3) is relaxed to 2) or even
|
||||
// to 1).
|
||||
//
|
||||
// 1) is called the Local Exec access model. 2) is Initial Exec, and 3) is
|
||||
// General Dynamic.
|
||||
//
|
||||
// There's another little trick that the compiler can use if it knows two
|
||||
// TLVs are in the same ELF file (usually in the same file as the code is).
|
||||
// In this case, we can call __tls_get_addr() only once with a module ID and
|
||||
// the offset 0 to obtain the base address of the ELF file's TLS block. The
|
||||
// base address obtained this way is sometimes called Dynamic Thread Pointer
|
||||
// or DTP. We can then compute TLVs' addresses by adding their DTP-relative
|
||||
// addresses to DTP. This access model is called the Local Dynamic.
|
||||
//
|
||||
// === TLS Descriptor access model ===
|
||||
//
|
||||
// As described above, there are arguably too many different TLS access
|
||||
// models from the most generic one you can use in any ELF file to the most
|
||||
// efficient one you can use only when building a main executable. Compiling
|
||||
// source code with an appropriate TLS access model is bothersome. To solve
|
||||
// the problem, a new TLS access model was proposed. That is called the TLS
|
||||
// Descriptor (TLSDESC) model.
|
||||
//
|
||||
// For a TLV compiled with TLSDESC, we allocate two consecutive GOT slots
|
||||
// and create a TLSDESC dynamic relocation for them. The dynamic linker
|
||||
// sets a function pointer to the first GOT slot and its argument to the
|
||||
// second slot.
|
||||
//
|
||||
// To access the TLV, we call the function pointer with the argument we
|
||||
// read from the second GOT slot. The function returns the TLV's
|
||||
// TP-relative address.
|
||||
//
|
||||
// The runtime chooses the best access method depending on the situation
|
||||
// and sets a pointer to the most efficient code to the first GOT slot.
|
||||
// For example, if a TLV's TP-relative address is known at process startup
|
||||
// time, the runtime sets that address to the second GOT slot and set a
|
||||
// function that just returns its argument to the first GOT slot.
|
||||
//
|
||||
// With TLSDECS, the compiler can always emit the same code for TLVs
|
||||
// without sacrificing runtime performance.
|
||||
//
|
||||
// TLSDESC is better than the traditional, non-TLSDESC TLS access models.
|
||||
// It's the default on ARM64, but on other targets, TLSDESC is
|
||||
// unfortunately either optional or even not supported at all. So we still
|
||||
// need to support both the traditional TLS models and the TLSDESC model.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static ElfPhdr<E> *get_tls_segment(Context<E> &ctx) {
|
||||
if (ctx.phdr)
|
||||
for (ElfPhdr<E> &phdr : ctx.phdr->phdrs)
|
||||
if (phdr.p_type == PT_TLS)
|
||||
return &phdr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 get_tls_begin(Context<E> &ctx) {
|
||||
if (ElfPhdr<E> *phdr = get_tls_segment(ctx))
|
||||
return phdr->p_vaddr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the TP address which can be used for efficient TLV accesses in
|
||||
// the main executable. TP at runtime refers to a per-process TLS block
|
||||
// whose address is not known at link-time. So the address returned from
|
||||
// this function is the TP if the TLS template image were a TLS block.
|
||||
template <typename E>
|
||||
u64 get_tp_addr(Context<E> &ctx) {
|
||||
ElfPhdr<E> *phdr = get_tls_segment(ctx);
|
||||
if (!phdr)
|
||||
return 0;
|
||||
|
||||
if constexpr (is_x86<E> || is_sparc<E> || is_s390x<E>) {
|
||||
// On x86, SPARC and s390x, TP (%gs on i386, %fs on x86-64, %g7 on SPARC
|
||||
// and %a0/%a1 on s390x) refers to past the end of the TLS block for
|
||||
// historical reasons. TLVs are accessed with negative offsets from TP.
|
||||
return align_to(phdr->p_vaddr + phdr->p_memsz, phdr->p_align);
|
||||
} else if constexpr (is_arm<E> || is_sh4<E> || is_alpha<E>) {
|
||||
// On ARM, SH4 and Alpha, the runtime appends two words at the beginning
|
||||
// of TLV template image when copying TLVs to the TLS block, so we need
|
||||
// to offset it.
|
||||
return align_down(phdr->p_vaddr - sizeof(Word<E>) * 2, phdr->p_align);
|
||||
} else if constexpr (is_ppc<E> || is_m68k<E> || is_mips<E>) {
|
||||
// On PPC and m68k, TP is 0x7000 (28 KiB) past the beginning of the TLV
|
||||
// block to maximize the addressable range for load/store instructions
|
||||
// with 16-bits signed immediates. It's not exactly 0x8000 (32 KiB) off
|
||||
// because there's a small implementation-defined piece of data before
|
||||
// the TLV block, and the runtime wants to access them efficiently too.
|
||||
return phdr->p_vaddr + 0x7000;
|
||||
} else {
|
||||
// RISC-V just uses the beginning of the main executable's TLV block as
|
||||
// TP. RISC-V load/store instructions usually take 12-bits signed
|
||||
// immediates, so the beginning of the TLS block ± 2 KiB is accessible
|
||||
// with a single load/store instruction.
|
||||
static_assert(is_riscv<E>);
|
||||
return phdr->p_vaddr;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the address __tls_get_addr() would return if it's called
|
||||
// with offset 0.
|
||||
template <typename E>
|
||||
u64 get_dtp_addr(Context<E> &ctx) {
|
||||
ElfPhdr<E> *phdr = get_tls_segment(ctx);
|
||||
if (!phdr)
|
||||
return 0;
|
||||
|
||||
if constexpr (is_ppc<E> || is_m68k<E> || is_mips<E>) {
|
||||
// On PPC64 and m68k, R_DTPOFF is resolved to the address 0x8000
|
||||
// (32 KiB) past the start of the TLS block. The bias maximizes the
|
||||
// accessible range for load/store instructions with 16-bits signed
|
||||
// immediates. That is, if the offset were right at the beginning of
|
||||
// the start of the TLS block, the half of addressible space (negative
|
||||
// immediates) would have been wasted.
|
||||
return phdr->p_vaddr + 0x8000;
|
||||
} else if constexpr (is_riscv<E>) {
|
||||
// On RISC-V, the bias is 0x800 as the load/store instructions in the
|
||||
// ISA usually have a 12-bit immediate.
|
||||
return phdr->p_vaddr + 0x800;
|
||||
} else {
|
||||
// On other targets, DTP simply refers to the beginning of the TLS block.
|
||||
return phdr->p_vaddr;
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template u64 get_tls_begin<E>(Context<E> &);
|
||||
template u64 get_tp_addr<E>(Context<E> &);
|
||||
template u64 get_dtp_addr<E>(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
set -e
|
||||
source /etc/os-release
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
@@ -40,6 +40,11 @@ arch-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
|
||||
;;
|
||||
void-*)
|
||||
xbps-install -Sy xbps
|
||||
xbps-install -Sy bash make cmake openssl-devel zlib-devel gcc
|
||||
xbps-install -Sy tar diffutils util-linux
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
|
||||
+11
-1
@@ -1,10 +1,16 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
|
||||
|
||||
function(add_target TRIPLE)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
if(${HOST} MATCHES "arm.*")
|
||||
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "ppc64")
|
||||
set(HOST powerpc64)
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "${HOST}-.*")
|
||||
@@ -61,3 +67,7 @@ add_target(alpha-linux-gnu)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_target(riscv32-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
|
||||
add_target(powerpc64le_power10-linux-gnu)
|
||||
endif()
|
||||
@@ -6,6 +6,8 @@
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = s390x ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[ $MACHINE = mips64el ] && skip
|
||||
[ $MACHINE = mips64 ] && skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
|
||||
.globl foo
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
|
||||
int fn1() { return 42; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc -
|
||||
int fn1();
|
||||
int fn2() { return fn1(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int fn2();
|
||||
int main() { fn2(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -L$t -Wl,--as-needed -lbar -Wl,--allow-shlib-undefined
|
||||
readelf -W --dynamic $t/exe1 > $t/log1
|
||||
! grep -q libfoo $t/log1 || false
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
|
||||
readelf -W --dynamic $t/exe2 > $t/log2
|
||||
grep -q libfoo $t/log2
|
||||
+12
-11
@@ -5,26 +5,27 @@ cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
|
||||
__attribute__((weak)) int fn1();
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
if (fn1)
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
|
||||
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
|
||||
int fn1() { return 42; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.so -shared -fPIC -Wl,-soname,libbar.so -xc -
|
||||
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc -
|
||||
int fn2() { return 42; }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-no-as-needed $t/b.so $t/c.so
|
||||
$CC -o $t/exe1 $t/a.o -Wl,-no-as-needed -L$t -lbar -lfoo
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
|
||||
grep -Fq 'Shared library: [libbar.so]' $t/readelf
|
||||
readelf --dynamic $t/exe1 > $t/log1
|
||||
grep -Fq 'Shared library: [libfoo.so]' $t/log1
|
||||
grep -Fq 'Shared library: [libbar.so]' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-as-needed $t/b.so $t/c.so
|
||||
$CC -o $t/exe2 $t/a.o -Wl,-as-needed -L$t -lbar -lfoo
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
! grep -Fq 'Shared library: [libfoo.so]' $t/readelf || false
|
||||
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
|
||||
readelf --dynamic $t/exe2 > $t/log2
|
||||
! grep -Fq 'Shared library: [libfoo.so]' $t/log2 || false
|
||||
! grep -Fq 'Shared library: [libbar.so]' $t/log2 || false
|
||||
+9
-10
@@ -1,22 +1,22 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/libfoo.so -Wl,--soname,libfoo.so -xc -
|
||||
int foo() { return 3; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/b.so -xc -
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/libbar.so -Wl,--soname,libbar.so -xc -
|
||||
int bar() { return 3; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
|
||||
int foo();
|
||||
int baz() { return foo(); }
|
||||
EOF
|
||||
|
||||
$CC -shared -o $t/c.so $t/c.o $t/a.so
|
||||
$CC -B. -shared -o $t/libbaz.so -Wl,--soname,libbaz.so -L$t $t/a.o -lfoo
|
||||
|
||||
cat <<EOF | $CC -c -o $t/d.o -xc -
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
#include <stdio.h>
|
||||
int baz();
|
||||
int main() {
|
||||
@@ -24,10 +24,9 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -Wl,--as-needed $t/c.so $t/b.so $t/a.so
|
||||
$QEMU $t/exe | grep -q '^3$'
|
||||
$CC -B. -o $t/exe $t/b.o -L$t -Wl,--as-needed -lbaz -lbar -lfoo
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
! grep -q /a.so $t/log || false
|
||||
grep -q /c.so $t/log || false
|
||||
! grep -q /b.so $t/log || false
|
||||
grep -q libbaz $t/log || false
|
||||
! grep -q libbar $t/log || false
|
||||
grep -q libfoo $t/log || false
|
||||
+27
-17
@@ -16,23 +16,10 @@ canonical_name() {
|
||||
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
|
||||
if [ -n "$TRIPLE" ]; then
|
||||
MACHINE=$(canonical_name $(echo $TRIPLE | sed 's/-.*//'))
|
||||
else
|
||||
MACHINE=$HOST
|
||||
fi
|
||||
|
||||
# Set tool names
|
||||
if [ -n "$TRIPLE" ]; then
|
||||
CC="${TEST_CC:-$TRIPLE-gcc}"
|
||||
CXX="${TEST_CXX:-$TRIPLE-g++}"
|
||||
GCC="${TEST_GCC:-$TRIPLE-gcc}"
|
||||
GXX="${TEST_GXX:-$TRIPLE-g++}"
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
else
|
||||
if [ "$TRIPLE" = "" ]; then
|
||||
MACHINE=$HOST
|
||||
TESTDIR=out/test/elf/$HOST
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
@@ -41,6 +28,29 @@ else
|
||||
OBJCOPY=objcopy
|
||||
STRIP=strip
|
||||
QEMU=
|
||||
elif [ "$TRIPLE" = powerpc64le_power10-linux-gnu ]; then
|
||||
TRIPLE=powerpc64le-linux-gnu
|
||||
MACHINE=ppc64le
|
||||
TESTDIR=out/test/elf/ppc64le-power10
|
||||
CC="${TEST_CC:-$TRIPLE-gcc} -mcpu=power10"
|
||||
CXX="${TEST_CXX:-$TRIPLE-g++} -mcpu=power10"
|
||||
GCC="${TEST_GCC:-$TRIPLE-gcc} -mcpu=power10"
|
||||
GXX="${TEST_GXX:-$TRIPLE-g++} -mcpu=power10"
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-ppc64le -L /usr/$TRIPLE -cpu power10"
|
||||
else
|
||||
MACHINE=$(canonical_name $(echo $TRIPLE | sed 's/-.*//'))
|
||||
TESTDIR=out/test/elf/$MACHINE
|
||||
CC="${TEST_CC:-$TRIPLE-gcc}"
|
||||
CXX="${TEST_CXX:-$TRIPLE-g++}"
|
||||
GCC="${TEST_GCC:-$TRIPLE-gcc}"
|
||||
GXX="${TEST_GXX:-$TRIPLE-g++}"
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
fi
|
||||
|
||||
# Common functions
|
||||
@@ -77,5 +87,5 @@ trap on_exit EXIT
|
||||
# Print out the startup message
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
t=$TESTDIR/$testname
|
||||
mkdir -p $t
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 ] && skip
|
||||
|
||||
# musl libc does not support init/fini on ARM
|
||||
# https://github.com/rui314/mold/issues/951
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && ldd --help 2>&1 | grep -q musl && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -g -ffunction-sections -
|
||||
#include <iostream>
|
||||
struct Foo {
|
||||
Foo() { std::cout << "Hello world\n"; }
|
||||
};
|
||||
|
||||
Foo x;
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -o $t/b.o -c -fPIC -xc++ -g -ffunction-sections -
|
||||
#include <iostream>
|
||||
struct Foo {
|
||||
Foo() { std::cout << "Hello world\n"; }
|
||||
};
|
||||
|
||||
Foo y;
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CXX -B. -o $t/exe $t/a.o $t/b.o -Wl,-emit-relocs
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
+15
-22
@@ -3,8 +3,11 @@
|
||||
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
static=1
|
||||
test_cflags -static || static=0
|
||||
static=
|
||||
test_cflags -static && static=-static
|
||||
|
||||
# I don't know why, but we need -pthread on m68k
|
||||
static="$static -pthread"
|
||||
|
||||
cat <<EOF > $t/a.cc
|
||||
int main() {
|
||||
@@ -20,15 +23,11 @@ EOF
|
||||
$CXX -c -o $t/b.o $t/a.cc -fPIC
|
||||
$CXX -c -o $t/c.o $t/a.cc -fno-PIC
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe1 $t/b.o -static
|
||||
$QEMU $t/exe1
|
||||
fi
|
||||
$CXX -B. -o $t/exe1 $t/b.o $static
|
||||
$QEMU $t/exe1
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe2 $t/c.o -static
|
||||
$QEMU $t/exe2
|
||||
fi
|
||||
$CXX -B. -o $t/exe2 $t/c.o $static
|
||||
$QEMU $t/exe2
|
||||
|
||||
$CXX -B. -o $t/exe3 $t/b.o -pie
|
||||
$QEMU $t/exe3
|
||||
@@ -39,18 +38,14 @@ $QEMU $t/exe4
|
||||
$CXX -B. -o $t/exe5 $t/b.o -pie -Wl,--gc-sections
|
||||
$QEMU $t/exe5
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe6 $t/c.o -static -Wl,--gc-sections
|
||||
$QEMU $t/exe6
|
||||
fi
|
||||
$CXX -B. -o $t/exe6 $t/c.o $static -Wl,--gc-sections
|
||||
$QEMU $t/exe6
|
||||
|
||||
if [ $MACHINE = x86_64 ]; then
|
||||
$CXX -c -o $t/d.o $t/a.cc -mcmodel=large -fPIC
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe7 $t/d.o -static
|
||||
$QEMU $t/exe7
|
||||
fi
|
||||
$CXX -B. -o $t/exe7 $t/d.o $static
|
||||
$QEMU $t/exe7
|
||||
|
||||
$CXX -B. -o $t/exe8 $t/d.o -pie
|
||||
$QEMU $t/exe8
|
||||
@@ -59,10 +54,8 @@ fi
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = aarch64 ]; then
|
||||
$CXX -c -o $t/e.o $t/a.cc -mcmodel=large -fno-PIC
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe9 $t/e.o -static
|
||||
$QEMU $t/exe9
|
||||
fi
|
||||
$CXX -B. -o $t/exe9 $t/e.o $static
|
||||
$QEMU $t/exe9
|
||||
|
||||
$CXX -B. -o $t/exe10 $t/e.o -no-pie
|
||||
$QEMU $t/exe10
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
# .text is writable on sparc, which is not compatible with --execute-only
|
||||
[ $MACHINE = sparc64 ] && skip
|
||||
|
||||
# GCC emits data to .text for PPC64, so PPC64 is not compatible with -execute-only
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/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 || 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 -gsplit-dwarf
|
||||
$CC -c -o $t/b.o $t/b.c -fPIC -g -ggnu-pubnames -gdwarf-4 -gsplit-dwarf
|
||||
$CC -c -o $t/c.o $t/c.c -fPIC -g -ggnu-pubnames -gdwarf-5
|
||||
$CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gsplit-dwarf
|
||||
|
||||
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
|
||||
readelf -WS $t/e.so 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
cat <<EOF | $CC -c -o $t/f.o -fPIC -g -ggnu-pubnames -gdwarf-5 -gsplit-dwarf -xc - -gz
|
||||
void fn1();
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/e.so $t/f.o -Wl,--gdb-index
|
||||
readelf -WS $t/exe 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
@@ -9,3 +9,9 @@ EOF
|
||||
|
||||
readelf -WS $t/b.so | grep -Fq ' .hash'
|
||||
readelf -WS $t/b.so | grep -Fq ' .gnu.hash'
|
||||
|
||||
./mold -shared -o $t/c.so $t/a.o --hash-style=both --hash-style=none
|
||||
|
||||
readelf -WS $t/c.so > $t/log
|
||||
! grep -Fq ' .hash' $t/log || false
|
||||
! grep -Fq ' .gnu.hash' $t/log || false
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -fno-PIE -o $t/a.o -c -xc -
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
void foo(void);
|
||||
|
||||
int main() {
|
||||
void *handle = dlopen(NULL, RTLD_NOW);
|
||||
Func *p = dlsym(handle, "foo");
|
||||
|
||||
foo();
|
||||
p();
|
||||
printf("%p %p\n", foo, p);
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -o $t/b.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((ifunc("resolve_foo")))
|
||||
void foo(void);
|
||||
|
||||
static void real_foo(void) {
|
||||
printf("foo ");
|
||||
}
|
||||
|
||||
typedef void Func();
|
||||
|
||||
static Func *resolve_foo(void) {
|
||||
return real_foo;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/c.so $t/b.o -shared
|
||||
$CC -B. -o $t/exe $t/a.o $t/c.so -no-pie -ldl
|
||||
$QEMU $t/exe | grep -q 'foo foo'
|
||||
@@ -11,7 +11,7 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
|
||||
cat <<EOF | $CC -fPIC -o $t/b.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((ifunc("resolve_foobar")))
|
||||
@@ -28,5 +28,8 @@ static Func *resolve_foobar(void) {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.so
|
||||
$CC -B. -o $t/c.so $t/b.o -shared
|
||||
readelf -W --dyn-syms $t/c.so | grep -Eq '(IFUNC|<OS specific>: 10).*foobar'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/c.so
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -c -xc -
|
||||
typedef int Fn();
|
||||
|
||||
int foo() __attribute__((ifunc("resolve_foo")));
|
||||
|
||||
int real_foo() { return 3; }
|
||||
|
||||
Fn *resolve_foo(void) {
|
||||
return real_foo;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
typedef int Fn();
|
||||
int foo();
|
||||
Fn *get_foo() { return foo; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef int Fn();
|
||||
Fn *get_foo();
|
||||
|
||||
int main() {
|
||||
Fn *f = get_foo();
|
||||
printf("%d\n", f());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o $t/c.o
|
||||
$QEMU $t/exe | grep -q '^3$'
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
seq 1 100000 | sed 's/.*/int x\0 __attribute__((section(".data.\0")));/g' | \
|
||||
$CC -c -xc -o $t/a.o -
|
||||
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' | \
|
||||
$CC -c -xassembler -o $t/a.o -
|
||||
|
||||
cat <<'EOF' | $CC -c -xc -o $t/b.o -
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
seq 1 100000 | sed 's/.*/int x\0 __attribute__((section(".data.\0")));/g' | \
|
||||
$CC -c -xc -o $t/a.o -
|
||||
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.globl x\0\nx\0: .word 0\n/g' | \
|
||||
$CC -c -xassembler -o $t/a.o -
|
||||
|
||||
./mold --relocatable -o $t/b.o $t/a.o
|
||||
readelf -WS $t/b.o | grep -Fq .data.100000
|
||||
readelf -Ws $t/b.o | grep -Fq 'OBJECT GLOBAL DEFAULT 100000'
|
||||
readelf -Ws $t/b.o | grep -Fq 'GLOBAL DEFAULT 100000'
|
||||
@@ -10,7 +10,5 @@ void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o $t/b.o \
|
||||
-Wl,--print-dependencies=full > $t/log 2> /dev/null
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o $t/b.o -Wl,--print-dependencies > $t/log 2> /dev/null
|
||||
grep -q 'b\.o.*a\.o.*foo$' $t/log
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
# Skip if 32 bits as we use very large addresses in this test.
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = arm32 ] && skip
|
||||
[ $MACHINE = riscv32 ] && skip
|
||||
|
||||
# It looks like SPARC's runtime can't handle PLT if it's too far from GOT.
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
extern char *msg;
|
||||
void hello() { printf("%s\n", msg); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
|
||||
char *msg = "Hello world";
|
||||
void hello();
|
||||
int main() { hello(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc - -g
|
||||
#include <stdio.h>
|
||||
void hello() { printf("Hello world\n"); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc - -g
|
||||
void hello();
|
||||
int main() { hello(); }
|
||||
EOF
|
||||
|
||||
./mold --relocatable -o $t/c.o $t/a.o $t/b.o
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o
|
||||
$QEMU $t/exe
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
seq 1 80000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' | \
|
||||
$CC -c -xassembler -o $t/a.o -
|
||||
|
||||
cat <<'EOF' | $CC -c -xc -o $t/b.o -
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello\n");
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -r -o $t/c.o $t/a.o $t/b.o
|
||||
$CC -B. -o $t/exe $t/c.o
|
||||
$QEMU $t/exe | grep -q Hello
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
set -x
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
# Set `e_flags` to zero to simulate ABI incompatibility
|
||||
dd if=/dev/zero of=$t/a.o bs=4 oflag=seek_bytes seek=48 count=1 conv=notrunc
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
void foo();
|
||||
int main() {
|
||||
foo();
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o $t/b.o 2> $t/log || false
|
||||
grep -q 'cannot link object files with different floating-point ABI' $t/log
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xassembler -
|
||||
.globl foo
|
||||
.weak bar
|
||||
foo:
|
||||
la a0, bar
|
||||
ret
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
#include <stdio.h>
|
||||
long foo();
|
||||
int main() { printf("%ld\n", foo()); }
|
||||
EOF
|
||||
|
||||
$CC -B. -static -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q '^0$'
|
||||
@@ -10,5 +10,8 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -static-pie
|
||||
$CC -B. -o $t/exe1 $t/a.o -static-pie
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -static-pie -Wl,--no-relax
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc - -flto
|
||||
void foo_1() {}
|
||||
__asm__(".symver foo_1, foo@@VER1");
|
||||
EOF
|
||||
|
||||
echo 'VER1 { foo; };' > $t/b.ver
|
||||
$CC -B. -shared -o $t/c.so $t/a.o -Wl,--version-script=$t/b.ver -flto
|
||||
readelf --symbols $t/c.so > $t/log
|
||||
|
||||
grep -Fq 'foo@@VER1' $t/log
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -fPIC -ftls-model=initial-exec -c -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
static _Thread_local int foo = 5;
|
||||
int bar() { return foo; }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,--relax
|
||||
readelf --dynamic $t/b.so | grep -q STATIC_TLS
|
||||
|
||||
$CC -B. -shared -o $t/c.so $t/a.o -Wl,--no-relax
|
||||
readelf --dynamic $t/c.so | grep -q STATIC_TLS
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((tls_model("global-dynamic"))) static _Thread_local int x1 = 1;
|
||||
__attribute__((tls_model("global-dynamic"))) _Thread_local int x2 = 2;
|
||||
__attribute__((tls_model("global-dynamic"))) _Thread_local int x3;
|
||||
|
||||
int foo() {
|
||||
x3 = 3;
|
||||
|
||||
printf("%d %d %d\n", x1, x2, x3);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/b.o -xc -
|
||||
int foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/c.so $t/a.o
|
||||
$CC -B. -o $t/exe1 $t/b.o $t/c.so
|
||||
$QEMU $t/exe1 | grep -q '1 2 3'
|
||||
|
||||
$CC -B. -shared -o $t/d.so $t/a.o -Wl,-no-relax
|
||||
$CC -B. -o $t/exe2 $t/b.o $t/d.so
|
||||
$QEMU $t/exe2 | grep -q '1 2 3'
|
||||
|
||||
$CC -B. -shared -o $t/e.so $t/a.o -Wl,-z,nodlopen
|
||||
$CC -B. -o $t/exe3 $t/b.o $t/e.so
|
||||
$QEMU $t/exe3 | grep -q '1 2 3'
|
||||
|
||||
$CC -B. -shared -o $t/f.so $t/a.o -Wl,-z,nodlopen -Wl,-no-relax
|
||||
$CC -B. -o $t/exe4 $t/b.o $t/f.so
|
||||
$QEMU $t/exe4 | grep -q '1 2 3'
|
||||
@@ -45,4 +45,7 @@ $QEMU $t/exe2 | grep -q '1 2 3 4 5 6'
|
||||
if test_cflags -static; then
|
||||
$CC -B. -o $t/exe3 $t/a.o $t/b.o $t/c.o -static
|
||||
$QEMU $t/exe3 | grep -q '1 2 3 4 5 6'
|
||||
|
||||
$CC -B. -o $t/exe4 $t/a.o $t/b.o $t/c.o -static -Wl,-no-relax
|
||||
$QEMU $t/exe4 | grep -q '1 2 3 4 5 6'
|
||||
fi
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
echo '{ global: bar; local: *; };' > $t/a.ver
|
||||
|
||||
cat <<EOF | $GCC $mtls -fPIC -c -o $t/b.o -xc -
|
||||
__attribute__((tls_model("global-dynamic"))) _Thread_local int foo;
|
||||
|
||||
int bar() {
|
||||
return foo;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/c.so $t/b.o -Wl,--version-script=$t/a.ver \
|
||||
-Wl,--no-relax
|
||||
|
||||
readelf -W --dyn-syms $t/c.so | grep -Eq 'TLS LOCAL DEFAULT .* foo'
|
||||
+4
-4
@@ -22,8 +22,8 @@ cat <<EOF | $GCC -fPIC -ftls-model=local-dynamic -c -o $t/b.o -xc -
|
||||
_Thread_local int foo = 3;
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-relax
|
||||
$QEMU $t/exe | grep -q '3 5 3 5'
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o -Wl,-relax
|
||||
$QEMU $t/exe1 | grep -q '3 5 3 5'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-no-relax
|
||||
$QEMU $t/exe | grep -q '3 5 3 5'
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,-no-relax
|
||||
$QEMU $t/exe2 | grep -q '3 5 3 5'
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc -
|
||||
__attribute__((tls_model("local-exec"))) static _Thread_local int foo = 5;
|
||||
int bar() { return foo; }
|
||||
EOF
|
||||
|
||||
! $CC -B. -shared -o $t/b.so $t/a.o >& $t/log
|
||||
grep -q 'relocation .* against `foo` can not be used when making a shared object; recompile with -fPIC' $t/log
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
if [ $MACHINE = x86_64 ]; then
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = arm ]; then
|
||||
dialect=gnu2
|
||||
elif [ $MACHINE = aarch64 ]; then
|
||||
dialect=desc
|
||||
|
||||
Loaded 100 of 343 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user