Compare commits

..
1 Commits
Author SHA1 Message Date
Rui Ueyama 0c57eca7b0 Support 32-bit PA-RISC 2023-01-05 14:50:32 +08:00
346 changed files with 11974 additions and 65989 deletions

No files matched your search

+4 -13
View File
@@ -26,20 +26,11 @@ 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++-12 ${{ matrix.target }} ..
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ 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
@@ -51,7 +42,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,mips,mipsel}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf {gcc,g++}-10-{mips64,mips64el}-linux-gnuabi64
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
@@ -84,9 +75,9 @@ jobs:
ctest . -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v3
if: failure()
if: always()
with:
name: test-results-gcc
name: test-results
path: |
build/out
build/Testing
-15
View File
@@ -1,15 +0,0 @@
name: linux-packages
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: build
run: ./dist.sh x86_64
- name: upload
uses: actions/upload-artifact@v3
with:
path: mold-*-linux.tar.gz
+26 -44
View File
@@ -44,7 +44,7 @@
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.13)
project(mold VERSION 2.0.0)
project(mold VERSION 1.8.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 -Wl,--gdb-index)
add_link_options(-fuse-ld=mold)
endif()
add_executable(mold)
@@ -65,16 +65,7 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-Wno-sign-compare
-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)
-Wno-unused-function)
endif()
# Build mold with -flto if -DMOLD_LTO=ON
@@ -151,7 +142,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 OPENBSD; NOT P32" OFF)
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
MOLD_USE_MIMALLOC OFF)
@@ -207,16 +198,12 @@ endif()
# We always use Clang to build mold on Windows. MSVC can't compile mold.
if(WIN32)
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(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)
@@ -258,7 +245,7 @@ if(NOT HAVE_FULL_ATOMIC_SUPPORT)
endif()
# Add -pthread
if(NOT APPLE AND NOT MSVC)
if(NOT APPLE AND NOT WIN32)
target_compile_options(mold PRIVATE -pthread)
target_link_options(mold PRIVATE -pthread)
endif()
@@ -268,15 +255,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}/common/update-git-hash.cmake
DEPENDS common/update-git-hash.cmake
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
DEPENDS update-git-hash.cmake
BYPRODUCTS git-hash.cc
VERBATIM)
add_dependencies(mold git_hash)
# Create config.h file
configure_file(common/config.h.in config.h)
configure_file(config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
@@ -289,7 +276,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# on a multicore machine.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
S390X SPARC64 M68K SH4 ALPHA MIPS64LE MIPS64BE)
S390X SPARC64 M68K SH4 ALPHA HPPA32)
list(APPEND MOLD_ELF_TEMPLATE_FILES
elf/cmdline.cc
@@ -298,7 +285,6 @@ 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
@@ -308,7 +294,6 @@ 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)
@@ -350,22 +335,14 @@ endif()
# Add other non-template source files.
target_sources(mold PRIVATE
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
compress.cc
demangle.cc
elf/arch-alpha.cc
elf/arch-arm32.cc
elf/arch-arm64.cc
elf/arch-hppa32.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
@@ -375,8 +352,16 @@ 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)
@@ -437,12 +422,9 @@ 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)
@@ -455,10 +437,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_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_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_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
+15
View File
@@ -0,0 +1,15 @@
{
"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": ""
}
]
}
+44
View File
@@ -0,0 +1,44 @@
# 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.
View File
File renamed without changes.
+1005 -1
View File
File diff suppressed because it is too large. Load diff
-348
View File
@@ -1,348 +0,0 @@
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.
+166 -109
View File
@@ -3,20 +3,20 @@
[![CI](https://github.com/rui314/mold/actions/workflows/ci.yml/badge.svg)](https://github.com/rui314/mold/actions/workflows/ci.yml)
[![build result](https://build.opensuse.org/projects/home:marxin:mold/packages/mold/badge.svg?type=default)](https://build.opensuse.org/package/show/home:marxin:mold/mold)
<i>This repository contains a free version of the mold linker.
If you are looking for a commercial version that supports macOS
<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,
please visit the
[repository of the sold linker](https://github.com/bluewhalesystems/sold).</i>
[repo 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 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.
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.
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.
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.
![Link speed comparison](docs/comparison.png)
@@ -26,46 +26,48 @@ final debuginfo-enabled executables for major large programs on a simulated
| 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 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 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 supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
PowerPC ELFv2, s390x, SPARC64, m68k, SH-4, and DEC Alpha.
RISC-V, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian PowerPC ELFv2,
s390x, SPARC64 and m68k.
mold/macOS is commercial software. For mold/macOS, please visit
https://github.com/bluewhalesystems/sold.
## Why does linking speed matter?
## Why does the speed of linking 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 and combines them into a single executable or 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 to combine them into a single executable
or a shared library file.
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.
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.
## Installation
## Install
Binary packages for the following systems are currently available:
Binary packages for the following systems are currently available.
[![Packaging status](https://repology.org/badge/vertical-allrepos/mold.svg)](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 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).
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.
### Install Dependencies
### Install dependencies
To install build dependencies, run `./install-build-deps.sh` in this
directory. It will detect your Linux distribution and attempt to install the
necessary packages. You may need to run it as root.
directory. It recognizes your Linux distribution and tries to install
necessary packages. You may want to run it as root.
### Compile mold
@@ -73,58 +75,59 @@ necessary packages. You may need to run it as root.
git clone https://github.com/rui314/mold.git
mkdir mold/build
cd mold/build
git checkout v2.0.0
git checkout v1.8.0
../install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
cmake --build . -j $(nproc)
sudo cmake --install .
```
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`.
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`.
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`.
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`.
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.
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.
## How to use
<details><summary>A classic way to use mold</summary>
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.
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.
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`:
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`:
- For Clang: pass `-fuse-ld=mold`
- Clang: pass `-fuse-ld=mold`
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
- 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`.
- 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`.
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 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 haven't installed `ld.mold` to any `$PATH`, you can still pass
`-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`.
`-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.
</details>
@@ -138,19 +141,32 @@ linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
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.
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.
```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, add the above snippet to
If you want to use mold for all projects, put 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>
@@ -162,46 +178,67 @@ 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, `gcc` is used 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
The above example uses `gcc` 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, add the above snippet to
`~/.config/config.nims`.
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")
```
</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 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:
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:
```shell
mold -run make <make-options-if-any>
```
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`.
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>.
</details>
<details><summary>GitHub Actions</summary>
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.
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.
</details>
<details><summary>Verify that you are using mold</summary>
mold leaves its identification string in the `.comment` section of an output
mold leaves its identification string in `.comment` section in an output
file. You can print it out to verify that you are actually using mold.
```shell
@@ -212,50 +249,70 @@ String dump of section '.comment':
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
```
If `mold` is present in the `.comment` section, the file was created by mold.
If `mold` is in `.comment`, the file is 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. However, if you need it, [mold's man page](docs/mold.md)
is available. You can read the same manual by running `man mold`.
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`.
</details>
## Why is mold so fast?
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.
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.
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.
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.
![CPU usage comparison in htop animation](docs/htop.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.
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.
For details, please see the [design notes](docs/design.md).
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.
## Sponsors
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
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:
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:
### 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
+14 -12
View File
@@ -25,7 +25,7 @@
#pragma once
#include "common.h"
#include "mold.h"
#include "filetype.h"
namespace mold {
@@ -76,12 +76,12 @@ struct ArHdr {
}
};
template <typename Context, typename MappedFile>
std::vector<MappedFile *>
read_thin_archive_members(Context &ctx, MappedFile *mf) {
template <typename C>
std::vector<MappedFile<C> *>
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
std::vector<MappedFile<C> *> vec;
std::string_view strtab;
while (data < begin + mf->size) {
@@ -117,18 +117,19 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
std::string path = name.starts_with('/') ?
name : (filepath(mf->name).parent_path() / name).string();
vec.push_back(MappedFile::must_open(ctx, path));
vec.push_back(MappedFile<C>::must_open(ctx, path));
vec.back()->thin_parent = mf;
data = body;
}
return vec;
}
template <typename Context, typename MappedFile>
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
template <typename C>
std::vector<MappedFile<C> *>
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
std::vector<MappedFile<C> *> vec;
std::string_view strtab;
while (begin + mf->size - data >= 2) {
@@ -162,9 +163,10 @@ std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf)
return vec;
}
template <typename Context, typename MappedFile>
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
switch (get_file_type(ctx, mf)) {
template <typename C>
std::vector<MappedFile<C> *>
read_archive_members(C &ctx, MappedFile<C> *mf) {
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
case FileType::AR:
return read_fat_archive_members(ctx, mf);
case FileType::THIN_AR:
+6 -6
View File
@@ -1,14 +1,14 @@
#pragma once
#include "common.h"
#include "mold.h"
namespace mold {
template <typename Context>
template <typename C>
std::vector<std::string_view>
read_response_file(Context &ctx, std::string_view path) {
read_response_file(C &ctx, std::string_view path) {
std::vector<std::string_view> vec;
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
u8 *data = mf->data;
auto read_quoted = [&](i64 i, char quote) {
@@ -63,8 +63,8 @@ read_response_file(Context &ctx, std::string_view path) {
}
// Replace "@path/to/some/text/file" with its file contents.
template <typename Context>
std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
template <typename C>
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
std::vector<std::string_view> vec;
for (i64 i = 0; argv[i]; i++) {
if (argv[i][0] == '@')
+1 -1
View File
@@ -14,7 +14,7 @@
// a little bit. However, if a shard size is large enough, that loss
// is negligible in practice.
#include "common.h"
#include "mold.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
View File
File renamed without changes.
+2 -2
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.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 {
+5 -12
View File
@@ -7,27 +7,20 @@
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
# and librt, as they almost always exist on any Linux systems.
case $# in
0)
arch=$(uname -m)
[[ $arch = arm* ]] && arch=arm
;;
1)
arch="$1"
;;
*)
if [ $# -ne 1 ]; then
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
exit 1
esac
fi
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
arch=$1
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 -i --rm -v "$(pwd):/mold" \
docker run --platform linux/$arch -it --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.

Before

Width:  |  Height:  |  Size: 23 KiB

+1239 -492
View File
File diff suppressed because it is too large. Load diff
-798
View File
@@ -1,798 +0,0 @@
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>.
+33 -21
View File
@@ -166,12 +166,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -208,11 +213,16 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
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";
@@ -221,30 +231,29 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_dyn_absrel(ctx, sym, rel);
break;
case R_ALPHA_LITERAL:
if (rel.r_addend)
if (rel.r_addend) {
sym.flags.fetch_or(NEEDS_ALPHA_GOT, std::memory_order_relaxed);
ctx.extra.got->add_symbol(sym, rel.r_addend);
else
sym.flags |= NEEDS_GOT;
} else {
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
}
break;
case R_ALPHA_SREL32:
scan_pcrel(ctx, sym, rel);
break;
case R_ALPHA_BRSGP:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_ALPHA_TLSGD:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_ALPHA_TLSLDM:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_ALPHA_GOTTPREL:
sym.flags |= NEEDS_GOTTP;
break;
case R_ALPHA_TPRELHI:
case R_ALPHA_TPRELLO:
check_tlsle(ctx, sym, rel);
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_ALPHA_GPREL32:
case R_ALPHA_LITUSE:
@@ -254,6 +263,7 @@ 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;
@@ -263,15 +273,16 @@ 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 is not found in any other targets.
// request which we cannot find in any other targets.
//
// 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.
// 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.
//
// 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 addend is created
// entry for an R_ALPHA_LITERAL reloc with a non-zero addends is created
// not in .got but in .alpha_got.
//
// Since .alpha_got entries are accessed relative to GP, .alpha_got
@@ -293,9 +304,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) const {
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) {
i64 n = 0;
for (const Entry &e : entries)
for (Entry &e : entries)
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
n++;
return n;
@@ -308,8 +319,9 @@ void AlphaGotSection::finalize() {
}
void AlphaGotSection::copy_buf(Context<E> &ctx) {
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
reldyn_offset);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
for (i64 i = 0; i < entries.size(); i++) {
Entry &e = entries[i];
+116 -99
View File
@@ -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,16 +12,14 @@
// 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 interworking thunk code to switch mode.
// linker even has to emit an interworking thunk code to switch mode.
//
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
// byte boundaries. So the least significant bit of a function address
// is always 0.
// byte boundaries.
//
// 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.
// 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.
//
// For example, if a symbol `foo` is of type STT_FUNC and has value
// 0x2001, `foo` is a function using Thumb instructions whose address
@@ -44,6 +42,8 @@ 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,9 +76,8 @@ 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, 23) << 2;
return sign_extend(*(ul32 *)loc & 0x00ff'ffff, 23) << 2;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
@@ -101,7 +100,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
return sign_extend(val, 15);
}
default:
return 0;
unreachable();
}
}
@@ -168,8 +167,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_JUMP24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
@@ -248,14 +247,17 @@ 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);
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;
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;
if (is_jump_reachable(disp))
return disp;
thunks = thunks.subspan(1);
}
unreachable();
};
for (i64 i = 0; i < rels.size(); i++) {
@@ -292,6 +294,9 @@ 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.
@@ -299,9 +304,6 @@ 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) {
@@ -330,12 +332,8 @@ 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: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
case R_ARM_CALL:
case R_ARM_PLT32: {
// 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);
@@ -343,53 +341,45 @@ 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) || T)
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
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);
}
break;
}
case R_ARM_PLT32:
case R_ARM_JUMP24:
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
} else {
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
// 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;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP11:
case R_ARM_THM_JUMP11: {
assert(T);
check(S + A - P, -(1 << 11), 1 << 11);
*(ul16 *)loc &= 0xf800;
*(ul16 *)loc |= bits(S + A - P, 11, 1);
i64 val = S + A - P;
check(val, -(1 << 11), 1 << 11);
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
break;
}
case R_ARM_THM_JUMP19: {
i64 val = S + A - P;
check(val, -(1 << 19), 1 << 19);
@@ -408,20 +398,18 @@ 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
break;
*(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);
}
// 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;
@@ -431,11 +419,12 @@ 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:
check(S + A - P, -(1LL << 30), 1LL << 30);
*(ul32 *)loc &= 0x8000'0000;
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
case R_ARM_PREL31: {
i64 val = S + A - P;
check(val, -(1LL << 30), 1LL << 30);
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
}
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov_imm(loc, (S + A) | T);
break;
@@ -480,7 +469,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_tls_trampoline_addr(P + 8), 25, 2);
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
} else {
// BL -> NOP
*(ul32 *)loc = 0xe320'f000;
@@ -488,7 +477,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_tls_trampoline_addr(P + 4), 4);
u64 val = align_to(get_trampoline_addr(P + 4), 4);
write_thm_b_imm(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else {
@@ -508,12 +497,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -552,13 +546,18 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_ARM_ABS32:
@@ -569,16 +568,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_ARM_THM_CALL:
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_THM_JUMP24:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TARGET2:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_ARM_MOVT_PREL:
case R_ARM_THM_MOVT_PREL:
@@ -586,20 +585,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_ARM_TLS_GD32:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_ARM_TLS_LDM32:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_ARM_TLS_IE32:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_ARM_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
break;
case R_ARM_TLS_LE32:
check_tlsle(ctx, sym, rel);
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
case R_ARM_REL32:
case R_ARM_BASE_PREL:
@@ -611,6 +607,7 @@ 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:
@@ -671,13 +668,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
// likely that it's due to some historical reason.
//
// This function sorts .ARM.exidx records.
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;
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
// .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
@@ -697,11 +688,11 @@ void fixup_arm_exidx_section(Context<E> &ctx) {
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
@@ -728,12 +719,38 @@ void fixup_arm_exidx_section(Context<E> &ctx) {
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) {
if (Chunk<E> *text = find_section(ctx, ".text")) {
osec->shdr.sh_link = text->shndx;
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;
ctx.shdr->copy_buf(ctx);
}
}
+69 -134
View File
@@ -123,24 +123,6 @@ 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);
@@ -176,7 +158,6 @@ 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:
@@ -191,82 +172,57 @@ 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_MOVW_UABS_G0:
check(S + A, 0, 1 << 16);
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
case R_AARCH64_ADD_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
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:
check(S + A, 0, 1LL << 32);
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
case R_AARCH64_MOVW_UABS_G1: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc |= bits(val, 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:
check(S + A, 0, 1LL << 48);
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
case R_AARCH64_MOVW_UABS_G2: {
i64 val = S + A;
check(val, 0, 1LL << 48);
*(ul32 *)loc |= bits(val, 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:
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++;
}
case R_AARCH64_ADR_GOT_PAGE: {
i64 val = page(G + GOT + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
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:
check(S + A - P, -(1LL << 20), 1LL << 20);
write_adr(loc, S + A - P);
case R_AARCH64_ADR_PREL_LO21: {
i64 val = S + A - P;
check(val, -(1LL << 20), 1LL << 20);
write_adr(loc, val);
break;
}
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26: {
if (sym.is_remaining_undef_weak()) {
@@ -282,23 +238,25 @@ 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:
check(S + A - P, -(1LL << 20), 1LL << 20);
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
case R_AARCH64_LD_PREL_LO19: {
i64 val = S + A - P;
check(val, -(1LL << 20), 1LL << 20);
*(ul32 *)loc |= bits(val, 20, 2) << 5;
break;
case R_AARCH64_PREL16:
check(S + A - P, -(1LL << 15), 1LL << 15);
*(ul16 *)loc = S + A - P;
}
case R_AARCH64_PREL16: {
i64 val = S + A - P;
check(val, -(1LL << 15), 1LL << 15);
*(ul16 *)loc = val;
break;
case R_AARCH64_PREL32:
check(S + A - P, -(1LL << 31), 1LL << 32);
*(ul32 *)loc = S + A - P;
}
case R_AARCH64_PREL32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 32);
*(ul32 *)loc = val;
break;
}
case R_AARCH64_PREL64:
*(ul64 *)loc = S + A - P;
break;
@@ -351,9 +309,6 @@ 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;
@@ -413,12 +368,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -464,81 +424,48 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
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 |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
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 |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
case R_AARCH64_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_AARCH64_ADR_PREL_PG_HI21:
scan_pcrel(ctx, sym, rel);
break;
case R_AARCH64_TLSGD_ADR_PAGE21:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
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);
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_ADR_PREL_LO21:
@@ -559,6 +486,14 @@ 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;
+506
View File
@@ -0,0 +1,506 @@
// HP/PA (Hewlett-Packard Precision Architecture), also known as PA-RISC,
// is a RISC ISA developed by HP in the '80s. It was replaced by Itanium
// which was a joint project between Intel and HP in the early 2000s. No
// new HP/PA processors have been created after 2005.
//
// Thread pointer (TP) is stored to the control register cr27, which is an
// alias for tr3.
//
// $ltp (linkage table pointer) is $r19
// $dp is $r27
#include "mold.h"
namespace mold::elf {
using E = HPPA32;
static u64 RND(u64 x) { return (x + 0x1000) & ~(u64)0x1fff; }
static u64 L(u64 x) { return x & 0xffff'f800; }
static u64 R(u64 x) { return x & 0x0000'07ff; }
static u64 LR(u64 x, u64 a) { return L(x + RND(a)); }
static u64 RR(u64 x, u64 a) { return R(x + RND(a)) + a - RND(a); }
static u64 get_gp(Context<E> &ctx) {
if (ctx.arg.pic)
return ctx.gotplt->shdr.sh_addr;
return ctx.extra.global->value;
}
static u32 dis_assemble_17(u32 val) {
return ((val & 0x10000) >> 16) |
((val & 0x0f800) << 5) |
((val & 0x00400) >> 8) |
((val & 0x003ff) << 3);
}
static u32 dis_assemble_21(u32 val) {
return (bits(val, 6, 2) << 16) | (bits(val, 8, 7) << 14) |
(bits(val, 1, 0) << 12) | (bits(val, 19, 9) << 1) |
bit(val, 20);
}
static u32 dis_low_sign_ext(u32 val, u32 len) {
return (bits(val, len - 2, 0) << 1) | bit(val, len - 1);
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
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_PARISC_PCREL32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_PARISC_SEGREL32:
*(ub32 *)loc = val;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
u64 SB = 0;
u64 GP = get_gp(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
#define S sym.get_addr(ctx)
#define A get_addend(loc, rel)
#define P (get_addr() + rel.r_offset)
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
#define GOT ctx.got->shdr.sh_addr
switch (rel.r_type) {
case R_PARISC_DIR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_PARISC_DIR21L:
*(ub32 *)loc |= dis_assemble_21(bits(LR(S, A), 31, 11));
break;
case R_PARISC_DIR14R:
*(ub32 *)loc |= dis_low_sign_ext(RR(S, A), 14);
break;
case R_PARISC_PCREL32: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc = addr + A - P - 8;
break;
}
case R_PARISC_PCREL21L: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc |= dis_assemble_21(bits(addr + A - P - 8, 31, 11));
break;
}
case R_PARISC_PCREL17F: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc |= dis_assemble_17((addr + A - P - 8) >> 2);
break;
}
case R_PARISC_PCREL14R: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc |= dis_low_sign_ext(R(addr - P - 8 + A), 14);
break;
}
case R_PARISC_DPREL21L:
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - GP, A), 31, 11));
break;
case R_PARISC_DPREL14R:
*(ub32 *)loc |= dis_low_sign_ext(RR(S - GP, A), 14);
break;
case R_PARISC_DLTIND21L:
assert(A == 0);
*(ub32 *)loc |= dis_assemble_21(bits(G + GOT - GP, 31, 11));
break;
case R_PARISC_DLTIND14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(G + GOT - GP), 14);
break;
case R_PARISC_SEGREL32:
*(ub32 *)loc = S + A - SB;
break;
case R_PARISC_PLABEL32:
if (sym.is_remaining_undef_weak()) {
*(ub32 *)loc = 0;
} else if (ctx.arg.pic) {
if (sym.is_imported)
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, sym.get_dynsym_idx(ctx), 0);
else
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, 0, sym.get_opd_addr(ctx) + 2);
*(ub32 *)loc = 0;
} else {
*(ub32 *)loc = sym.get_opd_addr(ctx) + 2;
}
break;
case R_PARISC_TPREL21L:
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.tp_addr, A), 31, 11));
break;
case R_PARISC_TPREL14R:
*(ub32 *)loc |= dis_low_sign_ext(RR(S - ctx.tp_addr, A), 14);
break;
case R_PARISC_LTOFF_TP21L:
assert(A == 0);
*(ub32 *)loc |=
dis_assemble_21(bits(LR(sym.get_gottp_addr(ctx) - GP, A), 31, 11));
break;
case R_PARISC_LTOFF_TP14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_gottp_addr(ctx) - GP), 14);
break;
case R_PARISC_TLS_GD21L:
assert(A == 0);
*(ub32 *)loc |=
dis_assemble_21(bits(LR(sym.get_tlsgd_addr(ctx) - GP, A), 31, 11));
break;
case R_PARISC_TLS_GD14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_tlsgd_addr(ctx) - GP), 14);
break;
case R_PARISC_TLS_LDM21L:
assert(A == 0);
*(ub32 *)loc |=
dis_assemble_21(bits(LR(ctx.got->get_tlsld_addr(ctx) - GP, A), 31, 11));
break;
case R_PARISC_TLS_LDM14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(ctx.got->get_tlsld_addr(ctx) - GP), 14);
break;
case R_PARISC_TLS_LDO21L:
assert(A == 0);
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.dtp_addr, A), 31, 11));
break;
case R_PARISC_TLS_LDO14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(S - ctx.dtp_addr), 14);
break;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOT
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? frag_addend : get_addend(loc, rel))
switch (rel.r_type) {
case R_PARISC_DIR32:
case R_PARISC_SEGREL32:
*(ub32 *)loc = S + A;
break;
default:
Error(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
#undef S
#undef A
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
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];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on PA-RISC";
if (sym.is_func()) {
switch (rel.r_type) {
case R_PARISC_PCREL32:
case R_PARISC_PCREL21L:
case R_PARISC_PCREL17F:
case R_PARISC_PCREL14R:
case R_PARISC_PLABEL32:
break;
default:
Error(ctx) << *this << ": " << rel
<< " may not refer to a function symbol " << sym;
}
}
switch (rel.r_type) {
case R_PARISC_DIR32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PARISC_DIR21L:
case R_PARISC_DIR14R:
scan_absrel(ctx, sym, rel);
break;
case R_PARISC_PCREL32:
case R_PARISC_PCREL21L:
case R_PARISC_PCREL17F:
case R_PARISC_PCREL14R:
case R_PARISC_DPREL21L:
case R_PARISC_DPREL14R:
if (sym.is_func() || sym.esym().is_undef_weak())
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
else
scan_pcrel(ctx, sym, rel);
break;
case R_PARISC_DLTIND21L:
case R_PARISC_DLTIND14R:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PARISC_PLABEL32:
if (!sym.is_remaining_undef_weak()) {
if (ctx.arg.pic)
file.num_dynrel++;
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
}
break;
case R_PARISC_LTOFF_TP21L:
case R_PARISC_LTOFF_TP14R:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PARISC_TLS_GD21L:
case R_PARISC_TLS_GD14R:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PARISC_TLS_LDM21L:
case R_PARISC_TLS_LDM14R:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_PARISC_SEGREL32:
case R_PARISC_TPREL21L:
case R_PARISC_TPREL14R:
case R_PARISC_TLS_LDO21L:
case R_PARISC_TLS_LDO14R:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 gp = get_gp(ctx);
static const ub32 opd_pic_entry[] = {
0x2a60'0000, // addil LR'<OFFSET>, r19, r1
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
0x0ec0'1095, // ldw 0(r22), r21
0xeaa0'c000, // bv r0(r21)
0x0ec8'1093, // ldw 4(r22), r19
};
static const ub32 opd_nopic_entry[] = {
0x2b60'0000, // addil LR'<OFFSET>, dp, r1
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
0x0ec0'1095, // ldw 0(r22), r21
0xeaa0'c000, // bv r0(r21)
0x0ec8'1093, // ldw 4(r22), r19
};
static const ub32 local_pic_entry[] = {
0x2a60'0000, // addil L%0, r19, r1
0x3435'0000, // ldo 0(r1), r21
0xeaa0'c000, // bv r0(r21)
0x0800'0240, // nop
0x0800'0240, // nop
};
static const ub32 local_nopic_entry[] = {
0x2b60'0000, // addil L%0, dp, r1
0x3435'0000, // ldo 0(r1), r21
0xeaa0'c000, // bv r0(r21)
0x0800'0240, // nop
0x0800'0240, // nop
};
static_assert(E::thunk_size == sizeof(opd_pic_entry));
static_assert(E::thunk_size == sizeof(opd_nopic_entry));
static_assert(E::thunk_size == sizeof(local_pic_entry));
static_assert(E::thunk_size == sizeof(local_nopic_entry));
for (i64 i = 0; i < symbols.size(); i++) {
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
Symbol<E> &sym = *symbols[i];
i64 val;
if (sym.has_opd(ctx)) {
val = sym.get_opd_addr(ctx) - gp;
if (ctx.arg.pic)
memcpy(loc, opd_pic_entry, E::thunk_size);
else
memcpy(loc, opd_nopic_entry, E::thunk_size);
} else {
assert(!sym.is_imported);
val = sym.get_addr(ctx) - gp;
if (ctx.arg.pic)
memcpy(loc, local_pic_entry, E::thunk_size);
else
memcpy(loc, local_nopic_entry, E::thunk_size);
}
loc[0] |= dis_assemble_21(bits(LR(val, 0), 31, 11));
loc[1] |= dis_low_sign_ext(RR(val, 0), 14);
}
}
void HppaOpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
sym->set_opd_idx(ctx, symbols.size());
symbols.push_back(sym);
}
void HppaOpdSection::update_shdr(Context<E> &ctx) {
if (!symbols.empty())
this->shdr.sh_size = symbols.size() * ENTRY_SIZE + TRAILER_SIZE;
this->shdr.sh_link = ctx.extra.opd->shndx;
}
void HppaOpdSection::copy_buf(Context<E> &ctx) {
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
u64 gp = get_gp(ctx);
memset(buf, 0, this->shdr.sh_size);
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
if (!ctx.arg.pic && !sym.is_imported) {
buf[i * 2] = sym.get_addr(ctx, NO_OPD);
buf[i * 2 + 1] = gp;
}
}
}
void HppaRelOpdSection::update_shdr(Context<E> &ctx) {
this->shdr.sh_link = ctx.extra.opd->shndx;
if (ctx.arg.pic) {
this->shdr.sh_size = ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>);
} else {
this->shdr.sh_size = 0;
for (Symbol<E> *sym : ctx.extra.opd->symbols)
if (sym->is_imported)
this->shdr.sh_size += sizeof(ElfRel<E>);
}
}
void HppaRelOpdSection::copy_buf(Context<E> &ctx) {
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->shdr.sh_offset);
for (Symbol<E> *sym : ctx.extra.opd->symbols) {
if (ctx.arg.pic || sym->is_imported) {
memset(rel, 0, sizeof(*rel));
rel->r_offset = sym->get_opd_addr(ctx);
rel->r_type = R_PARISC_IPLT;
if (sym->is_imported)
rel->r_sym = sym->get_dynsym_idx(ctx);
else
rel->r_addend = sym->get_addr(ctx, NO_OPD);
rel++;
}
}
// This code stub must immediately follow the OPD element that the last
// .rela.plt relocation refers to, because that's how the runtime finds
// this piece of code on process startup.
//
// The code stub loads the _dl_runtime_resolve address to $r20 call the
// function with its GP in $r19. The magic bytes, 0xc0ffee and 0xdeadbeef,
// are used by the runtime to identify this code stub, so they must be
// exactly that bytes.
//
// HPPA's runtime assumes that this stub is immediately followed by .got.
// The files created by mold don't actually satisfy that constraint
// because we don't intermix executable code and data. So, instead, we
// reserve three padding words after the code stub and set
// _GLOBAL_OFFSET_TABLE_ there as if that particular place is the
// beginning of a .got. This trick works because the runtime only uses
// _GLOBAL_OFFSET_TABLE_ to access GOT[1],
//
// Due to the existence of this code stub, .opd must be readable,
// writable and executable. Writable executable segment is bad from the
// security standpoint, but we have no choice other than doing it.
//
// https://sourceware.org/git/?p=glibc.git;f=sysdeps/hppa/dl-machine.h;h=1d5194856601e025c#l223
ub32 insn[] = {
0x0e80'1095, // 1: ldw 0(r20), r21
0xeaa0'c000, // bv r0(r21)
0x0e88'1095, // ldw 4(r20), r21
0xea9f'1fdd, // b,l 1b, r20
0xd680'1c1e, // depwi 0, 31, 2, r20
0x00c0'ffee, // (_dl_runtime_resolve's address)
0xdead'beef, // (%r19 for _dl_runtime_resolve)
0x0000'0000, // (_GLOBAL_OFFSET_TABLE_ is set to here)
0x0000'0000, // (runtime uses this word to identify this ELF module)
0x0000'0000, // (padding)
};
memcpy(ctx.buf + this->shdr.sh_offset +
ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>),
insn, sizeof(insn));
}
} // namespace mold::elf
+125 -107
View File
@@ -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/psabi/blob/main/i386.pdf
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
#include "mold.h"
@@ -42,6 +42,8 @@ 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;
@@ -64,9 +66,8 @@ 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 <>
@@ -140,7 +141,6 @@ 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,9 +150,10 @@ 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 <>
@@ -202,58 +203,6 @@ 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);
@@ -285,25 +234,33 @@ 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:
check(S + A, 0, 1 << 8);
*loc = S + A;
case R_386_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
}
case R_386_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
break;
}
case R_386_32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_386_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
case R_386_PC8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*loc = val;
break;
case R_386_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A - P;
}
case R_386_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
break;
}
case R_386_PC32:
case R_386_PLT32:
*(ul32 *)loc = S + A - P;
@@ -341,7 +298,32 @@ 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(loc, rels[i + 1], S - ctx.tp_addr);
// 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();
}
i++;
}
break;
@@ -349,7 +331,34 @@ 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(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
// 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;
i++;
}
break;
@@ -389,12 +398,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -411,28 +425,36 @@ 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:
check(S + A, 0, 1 << 8);
*loc = S + A;
case R_386_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
}
case R_386_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
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:
check(S + A, -(1 << 7), 1 << 7);
*loc = S + A;
case R_386_PC8: {
i64 val = S + A;
check(val, -(1 << 7), 1 << 7);
*loc = val;
break;
case R_386_PC16:
check(S + A, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A;
}
case R_386_PC16: {
i64 val = S + A;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
break;
}
case R_386_PC32:
*(ul32 *)loc = S + A;
break;
@@ -467,14 +489,19 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
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 |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_386_8:
@@ -491,24 +518,23 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_386_GOT32:
case R_386_GOTPC:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_386_GOT32X: {
// We always want to relax GOT32X because static PIE doesn't
// work without it.
bool do_relax = !sym.is_imported && sym.is_relative() &&
relax_got32x(loc - 2);
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_got32x(loc - 2);
if (!do_relax)
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_386_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_386_TLS_GD:
if (i + 1 == rels.size())
@@ -519,13 +545,10 @@ 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";
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static ||
(ctx.arg.relax && !ctx.arg.shared && !sym.is_imported))
if (relax_tlsgd(ctx, sym))
i++;
else
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_386_TLS_LDM:
if (i + 1 == rels.size())
@@ -536,19 +559,14 @@ 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";
// 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))
if (relax_tlsld(ctx))
i++;
else
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_386_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
break;
case R_386_TLS_LE:
check_tlsle(ctx, sym, rel);
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
+21 -13
View File
@@ -12,7 +12,7 @@
// access instructions and therefore can support position-independent
// code without too much hassle.
//
// https://github.com/rui314/psabi/blob/main/m68k.pdf
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
#include "mold.h"
@@ -221,12 +221,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -257,11 +262,16 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
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";
@@ -284,37 +294,35 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_68K_GOTOFF32:
case R_68K_GOTOFF16:
case R_68K_GOTOFF8:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_68K_PLT32:
case R_68K_PLT16:
case R_68K_PLT8:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_68K_TLS_GD32:
case R_68K_TLS_GD16:
case R_68K_TLS_GD8:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_68K_TLS_LDM32:
case R_68K_TLS_LDM16:
case R_68K_TLS_LDM8:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_68K_TLS_IE32:
case R_68K_TLS_IE16:
case R_68K_TLS_IE8:
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);
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
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;
-470
View File
@@ -1,470 +0,0 @@
#include "mold.h"
namespace mold::elf {
static constexpr i64 BIAS = 0x8000;
template <typename E>
void write_plt_header(Context<E> &ctx, u8 *buf) {}
template <typename E>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const U32<E> insn[] = {
0xdf99'0000, // ld t9, 0(gp)
0x03e0'7825, // move t3, ra
0x0320'f809, // jalr t9
0x6418'0000, // daddiu t8, zero, 0
};
memcpy(buf, insn, sizeof(insn));
*(U32<E> *)buf |= sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx);
*(U32<E> *)(buf + 12) |= sym.get_dynsym_idx(ctx);
}
template <typename E>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <typename E>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_MIPS_64:
*(U64<E> *)loc = val;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <typename E>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
u64 GP = ctx._gp->get_addr(ctx);
u64 GP0 = file.mips_gp0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto write_combined = [&](u64 val) {
if (rel.r_type2 == R_MIPS_64 && rel.r_type3 == R_NONE) {
*(U64<E> *)loc = val;
} else if (rel.r_type2 == R_MIPS_SUB && rel.r_type3 == R_MIPS_HI16) {
*(U32<E> *)loc |= ((-val + BIAS) >> 16) & 0xffff;
} else if (rel.r_type2 == R_MIPS_SUB && rel.r_type3 == R_MIPS_LO16) {
*(U32<E> *)loc |= -val & 0xffff;
} else {
Error(ctx) << *this << ": unsupported relocation combination: "
<< rel_to_string<E>(rel.r_type) << " "
<< rel_to_string<E>(rel.r_type2) << " "
<< rel_to_string<E>(rel.r_type3);
}
};
auto write32 = [&](u64 val) {
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
*(U32<E> *)loc = val;
else
write_combined(val);
};
auto write_hi16 = [&](u64 val) {
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
*(U32<E> *)loc |= ((val + BIAS) >> 16) & 0xffff;
else
write_combined(val);
};
auto write_lo16 = [&](u64 val) {
if (rel.r_type2 == R_NONE && rel.r_type3 == R_NONE)
*(U32<E> *)loc |= val & 0xffff;
else
write_combined(val);
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
switch (rel.r_type) {
case R_MIPS_64:
*(U64<E> *)loc = S + A;
break;
case R_MIPS_GPREL16:
if (sym.is_local(ctx))
write_lo16(S + A + GP0 - GP);
else
write_lo16(S + A - GP);
break;
case R_MIPS_GPREL32:
write32(S + A + GP0 - GP);
break;
case R_MIPS_GOT_DISP:
case R_MIPS_CALL16:
write_lo16(ctx.extra.got->get_got_addr(ctx, sym, A) - GP);
break;
case R_MIPS_GOT_PAGE:
write_lo16(ctx.extra.got->get_gotpage_got_addr(ctx, sym, A) - GP);
break;
case R_MIPS_GOT_OFST:
write_lo16(S + A - ctx.extra.got->get_gotpage_page_addr(ctx, sym, A));
break;
case R_MIPS_JALR:
break;
case R_MIPS_TLS_TPREL_HI16:
write_hi16(S + A - ctx.tp_addr);
break;
case R_MIPS_TLS_TPREL_LO16:
write_lo16(S + A - ctx.tp_addr);
break;
case R_MIPS_TLS_GOTTPREL:
write_lo16(ctx.extra.got->get_gottp_addr(ctx, sym) - GP);
break;
case R_MIPS_TLS_DTPREL_HI16:
write_hi16(S + A - ctx.dtp_addr);
break;
case R_MIPS_TLS_DTPREL_LO16:
write_lo16(S + A - ctx.dtp_addr);
break;
case R_MIPS_TLS_GD:
write_lo16(ctx.extra.got->get_tlsgd_addr(ctx, sym) + A - GP);
break;
case R_MIPS_TLS_LDM:
write_lo16(ctx.extra.got->get_tlsld_addr(ctx) + A - GP);
break;
default:
unreachable();
}
}
}
template <typename E>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : get_addend(loc, rel);
switch (rel.r_type) {
case R_MIPS_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U32<E> *)loc = *val;
else
*(U32<E> *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <typename E>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
switch (rel.r_type) {
case R_MIPS_GOT_DISP:
case R_MIPS_CALL16: {
std::scoped_lock lock(ctx.extra.got->mu);
ctx.extra.got->got_syms.push_back({&sym, rel.r_addend});
break;
}
case R_MIPS_GOT_PAGE: {
std::scoped_lock lock(ctx.extra.got->mu);
ctx.extra.got->gotpage_syms.push_back({&sym, rel.r_addend});
break;
}
case R_MIPS_TLS_GOTTPREL: {
std::scoped_lock lock(ctx.extra.got->mu);
assert(rel.r_addend == 0);
ctx.extra.got->gottp_syms.push_back(&sym);
break;
}
case R_MIPS_TLS_TPREL_HI16:
case R_MIPS_TLS_TPREL_LO16:
check_tlsle(ctx, sym, rel);
break;
case R_MIPS_TLS_GD: {
std::scoped_lock lock(ctx.extra.got->mu);
assert(rel.r_addend == 0);
ctx.extra.got->tlsgd_syms.push_back(&sym);
break;
}
case R_MIPS_TLS_LDM:
ctx.extra.got->needs_tlsld = true;
break;
case R_MIPS_64:
case R_MIPS_GPREL16:
case R_MIPS_GPREL32:
case R_MIPS_GOT_OFST:
case R_MIPS_JALR:
case R_MIPS_TLS_DTPREL_HI16:
case R_MIPS_TLS_DTPREL_LO16:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <typename E>
bool MipsGotSection<E>::SymbolAddend::operator<(const SymbolAddend &other) const {
return std::tuple(sym->file->priority, sym->sym_idx, addend) <
std::tuple(other.sym->file->priority, other.sym->sym_idx, other.addend);
};
template <typename E>
static bool compare(const Symbol<E> *a, const Symbol<E> *b) {
return std::tuple(a->file->priority, a->sym_idx) <
std::tuple(b->file->priority, b->sym_idx);
};
template <typename E>
u64 MipsGotSection<E>::SymbolAddend::get_addr(Context<E> &ctx, i64 flags) const {
return sym->get_addr(ctx, flags) + addend;
}
template <typename E>
u64
MipsGotSection<E>::get_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const {
auto it = std::lower_bound(got_syms.begin(), got_syms.end(),
SymbolAddend{&sym, addend});
assert(it != got_syms.end());
i64 idx = it - got_syms.begin();
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_gotpage_got_addr(Context<E> &ctx, Symbol<E> &sym,
i64 addend) const {
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
SymbolAddend{&sym, addend});
assert(it != gotpage_syms.end());
i64 idx = got_syms.size() + (it - gotpage_syms.begin());
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_gotpage_page_addr(Context<E> &ctx, Symbol<E> &sym,
i64 addend) const {
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
SymbolAddend{&sym, addend});
assert(it != gotpage_syms.end());
return it->get_addr(ctx);
}
template <typename E>
u64 MipsGotSection<E>::get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const {
auto it = std::lower_bound(tlsgd_syms.begin(), tlsgd_syms.end(),
&sym, compare<E>);
assert(it != tlsgd_syms.end());
i64 idx = got_syms.size() + gotpage_syms.size() + (it - tlsgd_syms.begin()) * 2;
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const {
auto it = std::lower_bound(gottp_syms.begin(), gottp_syms.end(),
&sym, compare<E>);
assert(it != gottp_syms.end());
i64 idx = got_syms.size() + gotpage_syms.size() + tlsgd_syms.size() * 2 +
(it - gottp_syms.begin());
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_tlsld_addr(Context<E> &ctx) const {
assert(needs_tlsld);
i64 idx = got_syms.size() + gotpage_syms.size() + tlsgd_syms.size() * 2 +
gottp_syms.size();
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
std::vector<typename MipsGotSection<E>::GotEntry>
MipsGotSection<E>::get_got_entries(Context<E> &ctx) const {
std::vector<GotEntry> entries;
auto add = [&](GotEntry ent) { entries.push_back(ent); };
// Create GOT entries for ordinary symbols
for (const SymbolAddend &ent : got_syms) {
// If a symbol is imported, let the dynamic linker to resolve it.
if (ent.sym->is_imported) {
add({0, E::R_GLOB_DAT, ent.sym});
continue;
}
// If we know an address at link-time, fill that GOT entry now.
// It may need a base relocation, though.
if (ctx.arg.pic && ent.sym->is_relative())
add({ent.get_addr(ctx, NO_PLT), E::R_RELATIVE});
else
add({ent.get_addr(ctx, NO_PLT)});
}
// Create GOT entries for GOT_PAGE and GOT_OFST relocs
for (const SymbolAddend &ent : gotpage_syms) {
if (ctx.arg.pic && ent.sym->is_relative())
add({ent.get_addr(ctx), E::R_RELATIVE});
else
add({ent.get_addr(ctx)});
}
// Create GOT entries for TLVs
for (Symbol<E> *sym : tlsgd_syms) {
if (sym->is_imported) {
// If a symbol is imported, let the dynamic linker to resolve it.
add({0, E::R_DTPMOD, sym});
add({0, E::R_DTPOFF, sym});
} else if (ctx.arg.shared) {
// If we are creating a shared library, we know the TLV's offset
// within the current TLS block. We don't know the module ID though.
add({0, E::R_DTPMOD});
add({sym->get_addr(ctx) - ctx.dtp_addr});
} else {
// If we are creating an executable, we know both the module ID and the
// offset. Module ID 1 indicates the main executable.
add({1});
add({sym->get_addr(ctx) - ctx.dtp_addr});
}
}
for (Symbol<E> *sym : gottp_syms) {
if (sym->is_imported) {
// If we know nothing about the symbol, let the dynamic linker
// to fill the GOT entry.
add({0, E::R_TPOFF, sym});
} else if (ctx.arg.shared) {
// If we know the offset within the current thread vector,
// let the dynamic linker to adjust it.
add({sym->get_addr(ctx) - ctx.tls_begin, E::R_TPOFF});
} else {
// Otherwise, we know the offset from the thread pointer (TP) at
// link-time, so we can fill the GOT entry directly.
add({sym->get_addr(ctx) - ctx.tp_addr});
}
}
if (needs_tlsld) {
if (ctx.arg.shared)
add({0, E::R_DTPMOD});
else
add({1}); // 1 means the main executable
add({0});
}
return entries;
}
template <typename E>
void MipsGotSection<E>::update_shdr(Context<E> &ctx) {
// Finalize got_syms
sort(got_syms);
remove_duplicates(got_syms);
this->shdr.sh_size = got_syms.size() * sizeof(Word<E>);
// Finalize gotpage_syms
sort(gotpage_syms);
remove_duplicates(gotpage_syms);
this->shdr.sh_size += gotpage_syms.size() * sizeof(Word<E>);
// Finalize tlsgd_syms
sort(tlsgd_syms, compare<E>);
remove_duplicates(tlsgd_syms);
this->shdr.sh_size += tlsgd_syms.size() * 2 * sizeof(Word<E>);
// Finalize gottp_syms
sort(gottp_syms, compare<E>);
remove_duplicates(gottp_syms);
this->shdr.sh_size += gottp_syms.size() * sizeof(Word<E>);
if (needs_tlsld)
this->shdr.sh_size += 2 * sizeof(Word<E>);
}
template <typename E>
i64 MipsGotSection<E>::get_reldyn_size(Context<E> &ctx) const {
i64 n = 0;
for (GotEntry &ent : get_got_entries(ctx))
if (ent.r_type != R_NONE)
n++;
return n;
}
template <typename E>
void MipsGotSection<E>::copy_buf(Context<E> &ctx) {
U64<E> *buf = (U64<E> *)(ctx.buf + this->shdr.sh_offset);
memset(buf, 0, this->shdr.sh_size);
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
this->reldyn_offset);
for (i64 i = 0; GotEntry &ent : get_got_entries(ctx)) {
if (ent.r_type != R_NONE)
*dynrel++ = ElfRel<E>(this->shdr.sh_addr + i * sizeof(Word<E>),
ent.r_type,
ent.sym ? ent.sym->get_dynsym_idx(ctx) : 0,
ent.val);
buf[i++] = ent.val;
}
}
#define INSTANTIATE(E) \
template void write_plt_header(Context<E> &, u8 *); \
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
template void \
EhFrameSection<E>::apply_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
template void InputSection<E>::scan_relocations(Context<E> &); \
template class MipsGotSection<E>;
INSTANTIATE(MIPS64LE);
INSTANTIATE(MIPS64BE);
} // namespace mold::elf
+39 -25
View File
@@ -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 came from different
// places in a merged .got2 for two functions that are 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/psabi/blob/main/ppc32.pdf
// https://github.com/rui314/mold/wiki/ppc32-psabi.pdf
#include "mold.h"
@@ -46,11 +46,15 @@ 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 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; }
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
@@ -153,7 +157,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
u64 GOT2 = file.ppc32_got2 ? file.ppc32_got2->get_addr() : 0;
u64 got2 = 0;
if (file.ppc32_got2)
got2 = file.ppc32_got2->output_section->shdr.sh_addr + file.ppc32_got2->offset;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
@@ -195,16 +201,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 - A - GOT2);
*(ub16 *)loc = lo(G + GOT - got2 - A);
break;
case R_PPC_PLT16_HI:
*(ub16 *)loc = hi(G + GOT - A - GOT2);
*(ub16 *)loc = hi(G + GOT - got2 - A);
break;
case R_PPC_PLT16_HA:
*(ub16 *)loc = ha(G + GOT - A - GOT2);
*(ub16 *)loc = ha(G + GOT - got2 - A);
break;
case R_PPC_PLT32:
*(ub32 *)loc = G + GOT - A - GOT2;
*(ub32 *)loc = G + GOT - got2 - A;
break;
case R_PPC_REL14:
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
@@ -293,12 +299,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -330,13 +341,18 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_PPC_ADDR32:
@@ -369,32 +385,30 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC_PLT16_HI:
case R_PPC_PLT16_HA:
case R_PPC_PLT32:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PPC_REL24:
case R_PPC_PLTREL24:
case R_PPC_PLTREL32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_PPC_GOT_TLSGD16:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PPC_GOT_TLSLD16:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_PPC_GOT_TPREL16:
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);
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
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:
+68 -101
View File
@@ -5,15 +5,14 @@
//
// 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 to the entry point of a function but
// instead refers to a data structure so-called "function descriptor".
// pointer doesn't directly refer the entry point of a function but
// instead refers 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 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.
// 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.
//
// PPC generally lacks PC-relative data access instructions. Position-
// independent code sets GOT + 0x8000 to %r2 and access global variables
@@ -26,8 +25,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 to a tuple of an
// address and a %r2 value.
// function. This is why a function pointer refers 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
@@ -40,10 +39,8 @@
// 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 to the address of a function as a
// is used for relocations that refers the address of a function as a
// function pointer.
//
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
#include "mold.h"
@@ -54,11 +51,15 @@ 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 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; }
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
@@ -92,35 +93,9 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
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;
}
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
}
// .plt.got is not necessary on PPC64 because range extension thunks
@@ -194,10 +169,12 @@ 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:
check(S + A - TOC, -(1 << 15), 1 << 15);
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
case R_PPC64_TOC16_DS: {
i64 val = S + A - TOC;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc |= val & 0xfffc;
break;
}
case R_PPC64_TOC16_LO_DS:
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
break;
@@ -217,9 +194,6 @@ 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;
@@ -259,12 +233,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_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_DTPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
break;
@@ -289,12 +263,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -342,45 +321,48 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT | NEEDS_OPD,
std::memory_order_relaxed);
// 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 |= NEEDS_PPC_OPD;
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
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 |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_PPC64_PLT16_HA:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSGD16_HA:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSLD16_HA:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
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:
@@ -393,6 +375,8 @@ 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:
@@ -537,10 +521,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 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.
// 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.
// We need to read .opd contents to find out a function entry point
// address to apply R_PPC64_REL24.
//
@@ -548,9 +532,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 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
// 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
// collector would mark an .opd alive which in turn mark all functions
// thatare referenced by .opd as alive, effectively keeping all
// functions as alive.
@@ -563,11 +547,11 @@ 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 to an input
// .opd section so that they refer to function symbols instead. We then
// mark input .opd sections as dead.
// 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.
//
// After this function, we mark symbols with the NEEDS_PPC_OPD flag if the
// After this function, we mark symbols with the NEEDS_OPD flag if the
// symbol needs an .opd entry. We then create an output .opd just like we
// do for .plt or .got.
void ppc64v1_rewrite_opd(Context<E> &ctx) {
@@ -628,21 +612,21 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
}
// When a function is exported, the dynamic symbol for the function should
// refers to the function's .opd entry. This function marks such symbols
// with NEEDS_PPC_OPD.
// refers the function's .opd entry. This function marks such symbols with
// NEEDS_OPD.
void ppc64v1_scan_symbols(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
if (sym->file == file && sym->is_exported)
if (u32 ty = sym->get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
sym->flags |= NEEDS_PPC_OPD;
sym->flags |= NEEDS_OPD;
});
// Functions referenced by the ELF header also have to have .opd entries.
auto mark = [&](std::string_view name) {
if (!name.empty())
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
sym.flags |= NEEDS_PPC_OPD;
sym.flags |= NEEDS_OPD;
};
mark(ctx.arg.entry);
@@ -656,30 +640,13 @@ 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) {
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
*buf++ = addr;
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
*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);
}
}
}
+82 -175
View File
@@ -1,29 +1,21 @@
// 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 to the original, big-endian PowerPC systems.
// with "ppc64" which refers the original, big-endian PowerPC systems.
//
// PPC64 is a bit tricky to support because PC-relative load/store
// 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].
// 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].
//
// 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
// We can get the program counter by 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 it's too expensive to do if we do this for each load/store.
// , but that'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
@@ -35,9 +27,9 @@
// calls are usually within the same ELF module, so this mechanism is
// efficient.
//
// 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:
// 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:
//
// addis r2, r12, .TOC.@ha
// addi r2, r2, .TOC.@lo + 4;
@@ -54,31 +46,12 @@
// 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://github.com/rui314/psabi/blob/main/ppc64v2.pdf
// https://openpowerfoundation.org/specifications/64bitelfabi/
#include "mold.h"
@@ -86,15 +59,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 prefix34(u64 x) {
return bits(x, 33, 16) | (bits(x, 15, 0) << 32);
}
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; }
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
@@ -171,11 +144,10 @@ 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 == 7)
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
if (val == 0 || val == 1)
return 0;
if (val == 7)
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
return 1 << val;
}
@@ -196,6 +168,13 @@ 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;
@@ -203,9 +182,6 @@ 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")
@@ -223,37 +199,22 @@ 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:
if (sym.has_plt(ctx) || !sym.esym().preserves_r2()) {
i64 val = r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
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;
// 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;
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)
break;
}
case R_PPC64_REL64:
*(ul64 *)loc = S + A - P;
break;
@@ -275,60 +236,38 @@ 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_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_DTPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.dtp_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:
@@ -345,12 +284,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -398,13 +342,18 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_PPC64_ADDR64:
@@ -414,37 +363,21 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
case R_PPC64_GOT_TPREL_PCREL34:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_PPC64_REL24_NOTOC:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
ctx.extra.is_power10 = true;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_PPC64_PLT16_HA:
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
case R_PPC64_GOT_PCREL34:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSGD16_HA:
case R_PPC64_GOT_TLSGD_PCREL34:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSLD16_HA:
case R_PPC64_GOT_TLSLD_PCREL34:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
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:
@@ -455,11 +388,10 @@ 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_PLTCALL_NOTOC:
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:
@@ -468,7 +400,6 @@ 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;
@@ -483,70 +414,46 @@ 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[] = {
0xf841'0018, // std r2, 24(r1)
// Jump to a local entry point
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
};
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
0x6000'0000, // nop
};
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);
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);
}
i64 val = got - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
} else {
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);
}
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);
}
}
}
+184 -178
View File
@@ -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 to past the deleted bytes.
// refer 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,60 +74,79 @@
namespace mold::elf {
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 itype(u32 val) {
return val << 20;
}
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 stype(u32 val) {
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 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 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_utype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
static u32 utype(u32 val) {
// 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.
*(ul32 *)loc |= (val + 0x800) & 0xffff'f000;
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);
}
static void write_jtype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
*(ul32 *)loc |= bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
*(ul32 *)loc |= jtype(val);
}
static void write_cbtype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'000'111'00000'11;
*(ul16 *)loc |= bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
bit(val, 1) << 3 | bit(val, 5) << 2;
*(ul16 *)loc |= cbtype(val);
}
static void write_cjtype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'00000000000'11;
*(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;
*(ul16 *)loc |= cjtype(val);
}
// Returns the rd register of an R/I/U/J-type instruction.
@@ -290,29 +309,6 @@ 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;
@@ -330,14 +326,18 @@ 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:
check(S + A - P, -(1 << 12), 1 << 12);
write_btype(loc, S + A - P);
case R_RISCV_BRANCH: {
i64 val = S + A - P;
check(val, -(1 << 12), 1 << 12);
write_btype(loc, val);
break;
case R_RISCV_JAL:
check(S + A - P, -(1 << 20), 1 << 20);
write_jtype(loc, S + A - P);
}
case R_RISCV_JAL: {
i64 val = S + A - P;
check(val, -(1 << 20), 1 << 20);
write_jtype(loc, val);
break;
}
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
@@ -357,9 +357,6 @@ 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);
@@ -368,82 +365,56 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
case R_RISCV_GOT_HI20:
write_utype(loc, G + GOT + A - P);
*(ul32 *)loc = G + GOT + A - P;
break;
case R_RISCV_TLS_GOT_HI20:
write_utype(loc, sym.get_gottp_addr(ctx) + A - P);
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_RISCV_TLS_GD_HI20:
write_utype(loc, sym.get_tlsgd_addr(ctx) + A - P);
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_RISCV_PCREL_HI20:
write_utype(loc, S + A - P);
break;
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();
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;
}
if (rel.r_type == R_RISCV_PCREL_LO12_I)
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);
}
break;
}
case R_RISCV_LO12_I:
case R_RISCV_LO12_S: {
i64 val = S + A;
if (rel.r_type == R_RISCV_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 (bits(S + A, 31, 12) == 0)
if (sign_extend(val, 11) == val)
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: {
@@ -501,16 +472,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)(loc + i) = 0x0001; // c.nop
break;
}
case R_RISCV_RVC_BRANCH:
check(S + A - P, -(1 << 8), 1 << 8);
write_cbtype(loc, S + A - P);
case R_RISCV_RVC_BRANCH: {
i64 val = S + A - P;
check(val, -(1 << 8), 1 << 8);
write_cbtype(loc, val);
break;
case R_RISCV_RVC_JUMP:
check(S + A - P, -(1 << 11), 1 << 11);
write_cjtype(loc, S + A - P);
}
case R_RISCV_RVC_JUMP: {
i64 val = S + A - P;
check(val, -(1 << 11), 1 << 11);
write_cjtype(loc, val);
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);
@@ -524,23 +499,53 @@ 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_SET_ULEB128:
overwrite_uleb(loc, S + A);
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
// These relocations are handled in the next loop.
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>
@@ -549,12 +554,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -597,7 +607,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);
@@ -611,15 +621,6 @@ 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;
@@ -636,7 +637,8 @@ void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
return;
}
// A relaxed section is copied piece-wise.
// Memory-allocated sections may be relaxed, so copy each segment
// individually.
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 pos = 0;
@@ -665,13 +667,18 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_RISCV_32:
@@ -690,28 +697,22 @@ 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 |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_RISCV_GOT_HI20:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_RISCV_TLS_GOT_HI20:
sym.flags |= NEEDS_GOTTP;
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_RISCV_TLS_GD_HI20:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
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:
@@ -719,6 +720,10 @@ 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:
@@ -736,8 +741,6 @@ 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;
@@ -788,7 +791,7 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
// Handling R_RISCV_ALIGN is mandatory.
//
// R_RISCV_ALIGN refers to NOP instructions. We need to eliminate some
// R_RISCV_ALIGN refers 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) {
@@ -818,14 +821,15 @@ 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 to an AUIPC + JALR instruction pair to
// These relocations refer 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;
i64 rd = get_rd(*(ul32 *)(isec.contents.data() + r.r_offset + 4));
std::string_view contents = isec.contents;
i64 rd = get_rd(*(ul32 *)(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
@@ -841,39 +845,41 @@ 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 to by LO12_I/LO12_S
// The corresponding instructions referred by LO12_I/LO12_S
// relocations will use the zero register instead.
if (bits(sym.get_addr(ctx), 31, 12) == 0)
i64 val = sym.get_addr(ctx);
if (sign_extend(val, 11) == val)
delta += 4;
break;
}
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_ADD:
// These relocations are used to add a high 20-bit value to the
// thread pointer. The following two instructions materializes
// TP + HI20(foo) in %r5, for example.
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:
//
// 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 low 12-bit
// offset like this:
// Then thread-local variable `foo` is accessed with a 12-bit offset
// like this:
//
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
//
// However, if the variable is at TP ±2 KiB, TP + HI20(foo) is the
// same as TP, so we can instead access the thread-local variable
// directly using TP like this:
// However, if the 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:
//
// sw t0,%tprel_lo(foo)(tp)
//
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
sign_extend(val, 11) == val)
i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
if (sign_extend(val, 11) == val)
delta += 4;
break;
}
}
}
isec.extra.r_deltas[rels.size()] = delta;
+158 -107
View File
@@ -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 an x86-64 in a parallel universe.
// just feels like a 64-bit i386 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)
//
// 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.
// 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/rui314/psabi/blob/main/s390x.pdf
// https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
#include "mold.h"
@@ -151,95 +151,134 @@ 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:
check(S + A, 0, 1 << 8);
*loc = S + A;
case R_390_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
case R_390_12:
check(S + A, 0, 1 << 12);
*(ul16 *)loc |= bits(S + A, 11, 0);
}
case R_390_12: {
i64 val = S + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
break;
case R_390_16:
check(S + A, 0, 1 << 16);
*(ub16 *)loc = S + A;
}
case R_390_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
break;
case R_390_20:
check(S + A, 0, 1 << 20);
write_mid20(loc, S + A);
}
case R_390_20: {
i64 val = S + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
break;
}
case R_390_32:
case R_390_PLT32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
case R_390_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_390_PLT64:
*(ub64 *)loc = S + A;
break;
case R_390_PC12DBL:
case R_390_PLT12DBL:
check_dbl(S + A - P, -(1 << 12), 1 << 12);
*(ul16 *)loc |= bits(S + A - P, 12, 1);
case R_390_PLT12DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 12), 1 << 12);
*(ul16 *)loc = (val >> 1) & 0x0fff;
break;
case R_390_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - P;
}
case R_390_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
break;
case R_390_PC32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - P;
}
case R_390_PC32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
break;
}
case R_390_PC64:
*(ub64 *)loc = S + A - P;
break;
case R_390_PC16DBL:
case R_390_PLT16DBL:
check_dbl(S + A - P, -(1 << 16), 1 << 16);
*(ub16 *)loc = (S + A - P) >> 1;
case R_390_PLT16DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 16), 1 << 16);
*(ub16 *)loc = val >> 1;
break;
}
case R_390_PC24DBL:
case R_390_PLT24DBL:
check_dbl(S + A - P, -(1 << 24), 1 << 24);
*(ub32 *)loc |= bits(S + A - P, 24, 1);
case R_390_PLT24DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 24), 1 << 24);
*(ub32 *)loc |= bits(val, 24, 1);
break;
}
case R_390_PC32DBL:
case R_390_PLT32DBL:
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (S + A - P) >> 1;
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;
}
break;
case R_390_GOT12:
case R_390_GOTPLT12:
check(G + A, 0, 1 << 12);
*(ul16 *)loc |= bits(G + A, 11, 0);
case R_390_GOTPLT12: {
i64 val = G + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
break;
}
case R_390_GOT16:
case R_390_GOTPLT16:
check(G + A, 0, 1 << 16);
*(ub16 *)loc = G + A;
case R_390_GOTPLT16: {
i64 val = G + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
break;
}
case R_390_GOT20:
case R_390_GOTPLT20:
check(G + A, 0, 1 << 20);
write_mid20(loc, G + A);
case R_390_GOTPLT20: {
i64 val = G + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
break;
}
case R_390_GOT32:
case R_390_GOTPLT32:
check(G + A, 0, 1LL << 32);
*(ub32 *)loc = G + A;
case R_390_GOTPLT32: {
i64 val = G + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_390_GOT64:
case R_390_GOTPLT64:
*(ub64 *)loc = G + A;
break;
case R_390_GOTOFF16:
case R_390_PLTOFF16:
check(S + A - GOT, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - GOT;
case R_390_PLTOFF16: {
i64 val = S + A - GOT;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
break;
}
case R_390_GOTOFF32:
case R_390_PLTOFF32:
check(S + A - GOT, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - GOT;
case R_390_PLTOFF32: {
i64 val = S + A - GOT;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
break;
}
case R_390_GOTOFF64:
case R_390_PLTOFF64:
*(ub64 *)loc = S + A - GOT;
@@ -247,14 +286,18 @@ 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:
check_dbl(GOT + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (GOT + A - P) >> 1;
case R_390_GOTPCDBL: {
i64 val = GOT + A - P;
check_dbl(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
break;
case R_390_GOTENT:
check(GOT + G + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (GOT + G + A - P) >> 1;
}
case R_390_GOTENT: {
i64 val = GOT + G + A - P;
check(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
break;
}
case R_390_TLS_LE32:
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
@@ -270,30 +313,19 @@ 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)) {
// 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));
if (!sym.has_tlsgd(ctx)) {
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
}
break;
case R_390_TLS_LDM32:
@@ -318,9 +350,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_390_TLS_LDCALL:
if (!ctx.got->has_tlsld(ctx)) {
// nop
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, insn, sizeof(insn));
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
}
break;
default:
@@ -335,12 +366,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -390,13 +426,18 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_390_64:
@@ -432,7 +473,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_GOTPC:
case R_390_GOTPCDBL:
case R_390_GOTENT:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_390_PLT12DBL:
case R_390_PLT16DBL:
@@ -444,38 +485,24 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_PLTOFF32:
case R_390_PLTOFF64:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_390_TLS_GOTIE20:
case R_390_TLS_IEENT:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_390_TLS_GD32:
case R_390_TLS_GD64:
// 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;
}
if (!relax_tlsgd(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_390_TLS_LDM32:
case R_390_TLS_LDM64: {
bool do_relax = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
if (!do_relax)
ctx.needs_tlsld = true;
case R_390_TLS_LDM64:
if (!relax_tlsld(ctx))
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
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:
@@ -487,4 +514,28 @@ 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
+18 -17
View File
@@ -26,13 +26,6 @@
// 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
@@ -271,12 +264,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -307,11 +305,16 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
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";
@@ -323,27 +326,25 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_SH_GOT32:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_SH_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_SH_TLS_GD_32:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_SH_TLS_LD_32:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_SH_TLS_IE_32:
sym.flags |= NEEDS_GOTTP;
break;
case R_SH_TLS_LE_32:
check_tlsle(ctx, sym, rel);
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
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;
+108 -71
View File
@@ -54,7 +54,8 @@
// 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://github.com/rui314/psabi/blob/main/sparc.pdf
// 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
#include "mold.h"
@@ -172,71 +173,97 @@ 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:
check(S + A, 0, 1 << 5);
*(ub32 *)loc |= bits(S + A, 4, 0);
case R_SPARC_5: {
i64 val = S + A;
check(val, 0, 1 << 5);
*(ub32 *)loc |= bits(val, 4, 0);
break;
case R_SPARC_6:
check(S + A, 0, 1 << 6);
*(ub32 *)loc |= bits(S + A, 5, 0);
}
case R_SPARC_6: {
i64 val = S + A;
check(val, 0, 1 << 6);
*(ub32 *)loc |= bits(val, 5, 0);
break;
case R_SPARC_7:
check(S + A, 0, 1 << 7);
*(ub32 *)loc |= bits(S + A, 6, 0);
}
case R_SPARC_7: {
i64 val = S + A;
check(val, 0, 1 << 7);
*(ub32 *)loc |= bits(val, 6, 0);
break;
case R_SPARC_8:
check(S + A, 0, 1 << 8);
*(u8 *)loc = S + A;
}
case R_SPARC_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*(u8 *)loc = val;
break;
case R_SPARC_10:
check(S + A, 0, 1 << 10);
*(ub32 *)loc |= bits(S + A, 9, 0);
}
case R_SPARC_10: {
i64 val = S + A;
check(val, 0, 1 << 10);
*(ub32 *)loc |= bits(val, 9, 0);
break;
}
case R_SPARC_LO10:
case R_SPARC_LOPLT10:
*(ub32 *)loc |= bits(S + A, 9, 0);
break;
case R_SPARC_11:
check(S + A, 0, 1 << 11);
*(ub32 *)loc |= bits(S + A, 10, 0);
case R_SPARC_11: {
i64 val = S + A;
check(val, 0, 1 << 11);
*(ub32 *)loc |= bits(val, 10, 0);
break;
case R_SPARC_13:
check(S + A, 0, 1 << 13);
*(ub32 *)loc |= bits(S + A, 12, 0);
}
case R_SPARC_13: {
i64 val = S + A;
check(val, 0, 1 << 13);
*(ub32 *)loc |= bits(val, 12, 0);
break;
}
case R_SPARC_16:
case R_SPARC_UA16:
check(S + A, 0, 1 << 16);
*(ub16 *)loc = S + A;
case R_SPARC_UA16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
break;
case R_SPARC_22:
check(S + A, 0, 1 << 22);
*(ub32 *)loc |= bits(S + A, 21, 0);
}
case R_SPARC_22: {
i64 val = S + A;
check(val, 0, 1 << 22);
*(ub32 *)loc |= bits(val, 21, 0);
break;
}
case R_SPARC_32:
case R_SPARC_UA32:
case R_SPARC_PLT32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
case R_SPARC_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_SPARC_PLT64:
case R_SPARC_UA64:
case R_SPARC_REGISTER:
*(ub64 *)loc = S + A;
break;
case R_SPARC_DISP8:
check(S + A - P, -(1 << 7), 1 << 7);
*(u8 *)loc = S + A - P;
case R_SPARC_DISP8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*(u8 *)loc = val;
break;
case R_SPARC_DISP16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - P;
}
case R_SPARC_DISP16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
break;
}
case R_SPARC_DISP32:
case R_SPARC_PCPLT32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - P;
case R_SPARC_PCPLT32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
break;
}
case R_SPARC_DISP64:
*(ub64 *)loc = S + A - P;
break;
@@ -246,19 +273,25 @@ 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:
check(S + A - P, -(1 << 20), 1 << 20);
*(ub32 *)loc |= bits(S + A - P, 20, 2);
case R_SPARC_WDISP19: {
i64 val = S + A - P;
check(val, -(1 << 20), 1 << 20);
*(ub32 *)loc |= bits(val, 20, 2);
break;
case R_SPARC_WDISP22:
check(S + A - P, -(1 << 23), 1 << 23);
*(ub32 *)loc |= bits(S + A - P, 23, 2);
}
case R_SPARC_WDISP22: {
i64 val = S + A - P;
check(val, -(1 << 23), 1 << 23);
*(ub32 *)loc |= bits(val, 23, 2);
break;
}
case R_SPARC_WDISP30:
case R_SPARC_WPLT30:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(S + A - P, 31, 2);
case R_SPARC_WPLT30: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(val, 31, 2);
break;
}
case R_SPARC_HI22:
case R_SPARC_HIPLT22:
case R_SPARC_LM22:
@@ -369,16 +402,12 @@ 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: {
u64 addr;
case R_SPARC_TLS_LDM_CALL:
if (ctx.arg.is_static)
addr = ctx.extra.tls_get_addr_sec->shdr.sh_addr;
*(ub32 *)loc |= bits(ctx.extra.tls_get_addr->shdr.sh_addr + A - P, 31, 2);
else
addr = ctx.extra.tls_get_addr_sym->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + 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;
@@ -425,12 +454,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -482,13 +516,18 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_SPARC_64:
@@ -532,17 +571,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SPARC_PCPLT10:
case R_SPARC_PLT64:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_SPARC_GOT13:
case R_SPARC_GOT10:
case R_SPARC_GOT22:
case R_SPARC_GOTDATA_HIX22:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_SPARC_GOTDATA_OP_HIX22:
if (sym.is_imported)
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_SPARC_DISP16:
case R_SPARC_DISP32:
@@ -558,22 +597,18 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_SPARC_TLS_GD_HI22:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_SPARC_TLS_LDM_HI22:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_SPARC_TLS_IE_HI22:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (!ctx.arg.is_static && ctx.extra.tls_get_addr_sym->is_imported)
ctx.extra.tls_get_addr_sym->flags |= NEEDS_PLT;
break;
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
check_tlsle(ctx, sym, rel);
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
ctx.tls_get_addr->flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_SPARC_GOTDATA_OP_LOX10:
case R_SPARC_GOTDATA_OP:
@@ -589,6 +624,8 @@ 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:
+202 -222
View File
@@ -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/psabi/blob/main/x86-64.pdf
// https://github.com/rui314/psabi/blob/main/i386.pdf
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
#include "mold.h"
@@ -187,142 +187,6 @@ 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().
@@ -367,14 +231,18 @@ 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:
check(S + A, 0, 1 << 8);
*loc = S + A;
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
case R_X86_64_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
break;
}
case R_X86_64_32:
write32(S + A);
break;
@@ -384,14 +252,18 @@ 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:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
case R_X86_64_PC8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*loc = val;
break;
case R_X86_64_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A - P;
}
case R_X86_64_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
break;
}
case R_X86_64_PC32:
case R_X86_64_PLT32:
write32s(S + A - P);
@@ -422,43 +294,79 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc = G + GOTPLT + A - P;
break;
case R_X86_64_GOTPCRELX:
// We always want to relax GOTPCRELX relocs even if --no-relax
// was given because some static PIE runtime code depends on these
// relaxations.
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
u32 insn = relax_gotpcrelx(loc - 2);
i64 val = S + A - P;
if (insn && (i32)val == val) {
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = val;
break;
}
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
}
write32s(G + GOTPLT + A - P);
break;
case R_X86_64_REX_GOTPCRELX:
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
u32 insn = relax_rex_gotpcrelx(loc - 3);
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;
}
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
}
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(loc, rels[i + 1], S - ctx.tp_addr);
// 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();
}
i++;
}
break;
@@ -466,7 +374,64 @@ 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(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
// 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);
i++;
}
break;
@@ -543,12 +508,17 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -574,14 +544,18 @@ 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:
check(S + A, 0, 1 << 8);
*loc = S + A;
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
case R_X86_64_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
break;
}
case R_X86_64_32:
write32(S + A);
break;
@@ -606,15 +580,6 @@ 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;
@@ -644,14 +609,19 @@ 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 || record_undef_error(ctx, rel))
if (rel.r_type == R_NONE)
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 |= NEEDS_GOT | NEEDS_PLT;
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_X86_64_8:
@@ -675,16 +645,34 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_GOTPC64:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCREL64:
case R_X86_64_GOTPCRELX:
case R_X86_64_REX_GOTPCRELX:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
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 |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
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";
@@ -697,20 +685,13 @@ 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 (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().
if (relax_tlsgd(ctx, sym))
i++;
} 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;
}
else
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
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";
@@ -723,21 +704,22 @@ 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";
// 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))
if (relax_tlsld(ctx))
i++;
else
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
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 |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
}
case R_X86_64_GOTPC32_TLSDESC: {
@@ -749,16 +731,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
<< " against an invalid code sequence";
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
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:
+13 -28
View File
@@ -1,5 +1,5 @@
#include "mold.h"
#include "../common/cmdline.h"
#include "../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,none]
--hash-style [sysv,gnu,both]
Set hash style
--icf=[all,safe,none] Fold identical code
--no-icf
@@ -191,15 +191,14 @@ 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
-z notext
-z textoff
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha)";
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha elf32-hppa-linux
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha hppalinux)";
static std::vector<std::string> add_dashes(std::string name) {
// Single-letter option
@@ -521,15 +520,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
<< " aarch64linux\n armelf_linux_eabi\n elf64lriscv\n"
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
<< " elf64_sparc\n m68kelf\n shlelf_linux\n elf64alpha";
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
<< " elf64alpha\n hppalinux";
version_shown = true;
} else if (read_flag("mips32") || read_flag("mips32r2") ||
read_flag("mips32r3") || read_flag("mips32r4") ||
read_flag("mips32r5") || read_flag("mips32r6") ||
read_flag("mips64") || read_flag("mips64r2") ||
read_flag("mips64r3") || read_flag("mips64r4") ||
read_flag("mips64r5") || read_flag("mips64r6")) {
// Ignore useless MIPS-specific flags
} else if (read_arg("m")) {
if (arg == "elf_x86_64") {
ctx.arg.emulation = X86_64::target_name;
@@ -563,10 +556,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.emulation = SH4::target_name;
} else if (arg == "elf64alpha") {
ctx.arg.emulation = ALPHA::target_name;
} else if (arg == "elf64ltsmip") {
ctx.arg.emulation = MIPS64LE::target_name;
} else if (arg == "elf64btsmip") {
ctx.arg.emulation = MIPS64BE::target_name;
} else if (arg == "hppalinux") {
ctx.arg.emulation = HPPA32::target_name;
} else {
Fatal(ctx) << "unknown -m argument: " << arg;
}
@@ -594,7 +585,9 @@ 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 = true;
ctx.arg.print_dependencies = 1;
} else if (read_flag("print-dependencies=full")) {
ctx.arg.print_dependencies = 2;
} 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")) {
@@ -662,7 +655,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("undefined") || read_arg("u")) {
} else if (read_arg("u") || read_arg("undefined")) {
ctx.arg.undefined.push_back(arg);
} else if (read_arg("require-defined")) {
ctx.arg.require_defined.push_back(arg);
@@ -680,9 +673,6 @@ 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;
}
@@ -704,8 +694,7 @@ 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") ||
read_flag("nopie")) {
} else if (read_flag("no-pie") || read_flag("no-pic-executable")) {
ctx.arg.pic = false;
ctx.arg.pie = false;
} else if (read_flag("relax")) {
@@ -878,8 +867,6 @@ 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")) {
@@ -1062,8 +1049,6 @@ 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
View File
@@ -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() == 0xffff'ffff)
if (*(U32<E> *)data.data() == 0xffffffff)
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>
u64 DebugInfoReader<E>::read(u64 form) {
inline u64 DebugInfoReader<E>::read(u64 form) {
switch (form) {
case DW_FORM_flag_present:
return 0;
+122 -89
View File
@@ -403,9 +403,6 @@ 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) + ")";
}
@@ -605,13 +602,9 @@ 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";
@@ -919,91 +912,131 @@ std::string rel_to_string<ALPHA>(u32 r_type) {
}
template <>
std::string rel_to_string<MIPS64LE>(u32 r_type) {
std::string rel_to_string<HPPA32>(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";
case R_PARISC_NONE: return "R_PARISC_NONE";
case R_PARISC_DIR32: return "R_PARISC_DIR32";
case R_PARISC_DIR21L: return "R_PARISC_DIR21L";
case R_PARISC_DIR17R: return "R_PARISC_DIR17R";
case R_PARISC_DIR17F: return "R_PARISC_DIR17F";
case R_PARISC_DIR14R: return "R_PARISC_DIR14R";
case R_PARISC_DIR14F: return "R_PARISC_DIR14F";
case R_PARISC_PCREL12F: return "R_PARISC_PCREL12F";
case R_PARISC_PCREL32: return "R_PARISC_PCREL32";
case R_PARISC_PCREL21L: return "R_PARISC_PCREL21L";
case R_PARISC_PCREL17R: return "R_PARISC_PCREL17R";
case R_PARISC_PCREL17F: return "R_PARISC_PCREL17F";
case R_PARISC_PCREL17C: return "R_PARISC_PCREL17C";
case R_PARISC_PCREL14R: return "R_PARISC_PCREL14R";
case R_PARISC_PCREL14F: return "R_PARISC_PCREL14F";
case R_PARISC_DPREL21L: return "R_PARISC_DPREL21L";
case R_PARISC_DPREL14WR: return "R_PARISC_DPREL14WR";
case R_PARISC_DPREL14DR: return "R_PARISC_DPREL14DR";
case R_PARISC_DPREL14R: return "R_PARISC_DPREL14R";
case R_PARISC_DPREL14F: return "R_PARISC_DPREL14F";
case R_PARISC_DLTREL21L: return "R_PARISC_DLTREL21L";
case R_PARISC_DLTREL14R: return "R_PARISC_DLTREL14R";
case R_PARISC_DLTREL14F: return "R_PARISC_DLTREL14F";
case R_PARISC_DLTIND21L: return "R_PARISC_DLTIND21L";
case R_PARISC_DLTIND14R: return "R_PARISC_DLTIND14R";
case R_PARISC_DLTIND14F: return "R_PARISC_DLTIND14F";
case R_PARISC_SETBASE: return "R_PARISC_SETBASE";
case R_PARISC_SECREL32: return "R_PARISC_SECREL32";
case R_PARISC_BASEREL21L: return "R_PARISC_BASEREL21L";
case R_PARISC_BASEREL17R: return "R_PARISC_BASEREL17R";
case R_PARISC_BASEREL17F: return "R_PARISC_BASEREL17F";
case R_PARISC_BASEREL14R: return "R_PARISC_BASEREL14R";
case R_PARISC_BASEREL14F: return "R_PARISC_BASEREL14F";
case R_PARISC_SEGBASE: return "R_PARISC_SEGBASE";
case R_PARISC_SEGREL32: return "R_PARISC_SEGREL32";
case R_PARISC_PLTOFF21L: return "R_PARISC_PLTOFF21L";
case R_PARISC_PLTOFF14R: return "R_PARISC_PLTOFF14R";
case R_PARISC_PLTOFF14F: return "R_PARISC_PLTOFF14F";
case R_PARISC_LTOFF_FPTR32: return "R_PARISC_LTOFF_FPTR32";
case R_PARISC_LTOFF_FPTR21L: return "R_PARISC_LTOFF_FPTR21L";
case R_PARISC_LTOFF_FPTR14R: return "R_PARISC_LTOFF_FPTR14R";
case R_PARISC_FPTR64: return "R_PARISC_FPTR64";
case R_PARISC_PLABEL32: return "R_PARISC_PLABEL32";
case R_PARISC_PLABEL21L: return "R_PARISC_PLABEL21L";
case R_PARISC_PLABEL14R: return "R_PARISC_PLABEL14R";
case R_PARISC_PCREL64: return "R_PARISC_PCREL64";
case R_PARISC_PCREL22C: return "R_PARISC_PCREL22C";
case R_PARISC_PCREL22F: return "R_PARISC_PCREL22F";
case R_PARISC_PCREL14WR: return "R_PARISC_PCREL14WR";
case R_PARISC_PCREL14DR: return "R_PARISC_PCREL14DR";
case R_PARISC_PCREL16F: return "R_PARISC_PCREL16F";
case R_PARISC_PCREL16WF: return "R_PARISC_PCREL16WF";
case R_PARISC_PCREL16DF: return "R_PARISC_PCREL16DF";
case R_PARISC_DIR64: return "R_PARISC_DIR64";
case R_PARISC_DIR14WR: return "R_PARISC_DIR14WR";
case R_PARISC_DIR14DR: return "R_PARISC_DIR14DR";
case R_PARISC_DIR16F: return "R_PARISC_DIR16F";
case R_PARISC_DIR16WF: return "R_PARISC_DIR16WF";
case R_PARISC_DIR16DF: return "R_PARISC_DIR16DF";
case R_PARISC_GPREL64: return "R_PARISC_GPREL64";
case R_PARISC_DLTREL14WR: return "R_PARISC_DLTREL14WR";
case R_PARISC_DLTREL14DR: return "R_PARISC_DLTREL14DR";
case R_PARISC_GPREL16F: return "R_PARISC_GPREL16F";
case R_PARISC_GPREL16WF: return "R_PARISC_GPREL16WF";
case R_PARISC_GPREL16DF: return "R_PARISC_GPREL16DF";
case R_PARISC_LTOFF64: return "R_PARISC_LTOFF64";
case R_PARISC_DLTIND14WR: return "R_PARISC_DLTIND14WR";
case R_PARISC_DLTIND14DR: return "R_PARISC_DLTIND14DR";
case R_PARISC_LTOFF16F: return "R_PARISC_LTOFF16F";
case R_PARISC_LTOFF16WF: return "R_PARISC_LTOFF16WF";
case R_PARISC_LTOFF16DF: return "R_PARISC_LTOFF16DF";
case R_PARISC_SECREL64: return "R_PARISC_SECREL64";
case R_PARISC_BASEREL14WR: return "R_PARISC_BASEREL14WR";
case R_PARISC_BASEREL14DR: return "R_PARISC_BASEREL14DR";
case R_PARISC_SEGREL64: return "R_PARISC_SEGREL64";
case R_PARISC_PLTOFF14WR: return "R_PARISC_PLTOFF14WR";
case R_PARISC_PLTOFF14DR: return "R_PARISC_PLTOFF14DR";
case R_PARISC_PLTOFF16F: return "R_PARISC_PLTOFF16F";
case R_PARISC_PLTOFF16WF: return "R_PARISC_PLTOFF16WF";
case R_PARISC_PLTOFF16DF: return "R_PARISC_PLTOFF16DF";
case R_PARISC_LTOFF_FPTR64: return "R_PARISC_LTOFF_FPTR64";
case R_PARISC_LTOFF_FPTR14WR: return "R_PARISC_LTOFF_FPTR14WR";
case R_PARISC_LTOFF_FPTR14DR: return "R_PARISC_LTOFF_FPTR14DR";
case R_PARISC_LTOFF_FPTR16F: return "R_PARISC_LTOFF_FPTR16F";
case R_PARISC_LTOFF_FPTR16WF: return "R_PARISC_LTOFF_FPTR16WF";
case R_PARISC_LTOFF_FPTR16DF: return "R_PARISC_LTOFF_FPTR16DF";
case R_PARISC_COPY: return "R_PARISC_COPY";
case R_PARISC_IPLT: return "R_PARISC_IPLT";
case R_PARISC_EPLT: return "R_PARISC_EPLT";
case R_PARISC_TPREL32: return "R_PARISC_TPREL32";
case R_PARISC_TPREL21L: return "R_PARISC_TPREL21L";
case R_PARISC_TPREL14R: return "R_PARISC_TPREL14R";
case R_PARISC_LTOFF_TP21L: return "R_PARISC_LTOFF_TP21L";
case R_PARISC_LTOFF_TP14R: return "R_PARISC_LTOFF_TP14R";
case R_PARISC_LTOFF_TP14F: return "R_PARISC_LTOFF_TP14F";
case R_PARISC_TPREL64: return "R_PARISC_TPREL64";
case R_PARISC_TPREL14WR: return "R_PARISC_TPREL14WR";
case R_PARISC_TPREL14DR: return "R_PARISC_TPREL14DR";
case R_PARISC_TPREL16F: return "R_PARISC_TPREL16F";
case R_PARISC_TPREL16WF: return "R_PARISC_TPREL16WF";
case R_PARISC_TPREL16DF: return "R_PARISC_TPREL16DF";
case R_PARISC_LTOFF_TP64: return "R_PARISC_LTOFF_TP64";
case R_PARISC_LTOFF_TP14WR: return "R_PARISC_LTOFF_TP14WR";
case R_PARISC_LTOFF_TP14DR: return "R_PARISC_LTOFF_TP14DR";
case R_PARISC_LTOFF_TP16F: return "R_PARISC_LTOFF_TP16F";
case R_PARISC_LTOFF_TP16WF: return "R_PARISC_LTOFF_TP16WF";
case R_PARISC_LTOFF_TP16DF: return "R_PARISC_LTOFF_TP16DF";
case R_PARISC_GNU_VTENTRY: return "R_PARISC_GNU_VTENTRY";
case R_PARISC_GNU_VTINHERIT: return "R_PARISC_GNU_VTINHERIT";
case R_PARISC_TLS_GD21L: return "R_PARISC_TLS_GD21L";
case R_PARISC_TLS_GD14R: return "R_PARISC_TLS_GD14R";
case R_PARISC_TLS_GDCALL: return "R_PARISC_TLS_GDCALL";
case R_PARISC_TLS_LDM21L: return "R_PARISC_TLS_LDM21L";
case R_PARISC_TLS_LDM14R: return "R_PARISC_TLS_LDM14R";
case R_PARISC_TLS_LDMCALL: return "R_PARISC_TLS_LDMCALL";
case R_PARISC_TLS_LDO21L: return "R_PARISC_TLS_LDO21L";
case R_PARISC_TLS_LDO14R: return "R_PARISC_TLS_LDO14R";
case R_PARISC_TLS_DTPMOD32: return "R_PARISC_TLS_DTPMOD32";
case R_PARISC_TLS_DTPMOD64: return "R_PARISC_TLS_DTPMOD64";
case R_PARISC_TLS_DTPOFF32: return "R_PARISC_TLS_DTPOFF32";
case R_PARISC_TLS_DTPOFF64: return "R_PARISC_TLS_DTPOFF64";
}
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
+165 -201
View File
@@ -1,7 +1,8 @@
#pragma once
#include "../common/integers.h"
#include "../integers.h"
#include <cstdint>
#include <ostream>
#include <string>
#include <type_traits>
@@ -24,8 +25,7 @@ struct SPARC64;
struct M68K;
struct SH4;
struct ALPHA;
struct MIPS64LE;
struct MIPS64BE;
struct HPPA32;
template <typename E> struct ElfSym;
template <typename E> struct ElfShdr;
@@ -102,7 +102,6 @@ enum : u32 {
SHF_TLS = 0x400,
SHF_COMPRESSED = 0x800,
SHF_GNU_RETAIN = 0x200000,
SHF_MIPS_GPREL = 0x10000000,
SHF_EXCLUDE = 0x80000000,
};
@@ -120,6 +119,7 @@ enum : u32 {
STT_TLS = 6,
STT_GNU_IFUNC = 10,
STT_SPARC_REGISTER = 13,
STT_HPPA_MILLI = 13,
};
template <typename E>
@@ -139,6 +139,10 @@ inline std::string stt_to_string(u32 st_type) {
if (st_type == STT_SPARC_REGISTER)
return "STT_SPARC_REGISTER";
if constexpr (std::is_same_v<E, HPPA32>)
if (st_type == STT_HPPA_MILLI)
return "STT_HPPA_MILLI";
return "unknown st_type (" + std::to_string(st_type) + ")";
}
@@ -184,7 +188,6 @@ enum : u32 {
PT_GNU_EH_FRAME = 0x6474e550,
PT_GNU_STACK = 0x6474e551,
PT_GNU_RELRO = 0x6474e552,
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
PT_ARM_EXIDX = 0x70000001,
};
@@ -220,7 +223,7 @@ enum : u32 {
EM_NONE = 0,
EM_386 = 3,
EM_68K = 4,
EM_MIPS = 8,
EM_PARISC = 15,
EM_PPC = 20,
EM_PPC64 = 21,
EM_S390X = 22,
@@ -241,6 +244,11 @@ enum : u32 {
EI_ABIVERSION = 8,
};
enum : u32 {
ELFOSABI_NONE = 0,
ELFOSABI_GNU = 3,
};
enum : u32 {
DT_NULL = 0,
DT_NEEDED = 1,
@@ -289,13 +297,6 @@ 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,
};
@@ -768,9 +769,6 @@ 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 {
@@ -947,13 +945,9 @@ 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,
@@ -1236,82 +1230,127 @@ enum : u32 {
};
enum : u32 {
R_MIPS_NONE = 0,
R_MIPS_16 = 1,
R_MIPS_32 = 2,
R_MIPS_REL32 = 3,
R_MIPS_26 = 4,
R_MIPS_HI16 = 5,
R_MIPS_LO16 = 6,
R_MIPS_GPREL16 = 7,
R_MIPS_LITERAL = 8,
R_MIPS_GOT16 = 9,
R_MIPS_PC16 = 10,
R_MIPS_CALL16 = 11,
R_MIPS_GPREL32 = 12,
R_MIPS_UNUSED1 = 13,
R_MIPS_UNUSED2 = 14,
R_MIPS_UNUSED3 = 15,
R_MIPS_SHIFT5 = 16,
R_MIPS_SHIFT6 = 17,
R_MIPS_64 = 18,
R_MIPS_GOT_DISP = 19,
R_MIPS_GOT_PAGE = 20,
R_MIPS_GOT_OFST = 21,
R_MIPS_GOT_HI16 = 22,
R_MIPS_GOT_LO16 = 23,
R_MIPS_SUB = 24,
R_MIPS_INSERT_A = 25,
R_MIPS_INSERT_B = 26,
R_MIPS_DELETE = 27,
R_MIPS_HIGHER = 28,
R_MIPS_HIGHEST = 29,
R_MIPS_CALL_HI16 = 30,
R_MIPS_CALL_LO16 = 31,
R_MIPS_SCN_DISP = 32,
R_MIPS_REL16 = 33,
R_MIPS_ADD_IMMEDIATE = 34,
R_MIPS_PJUMP = 35,
R_MIPS_RELGOT = 36,
R_MIPS_JALR = 37,
R_MIPS_TLS_DTPMOD32 = 38,
R_MIPS_TLS_DTPREL32 = 39,
R_MIPS_TLS_DTPMOD64 = 40,
R_MIPS_TLS_DTPREL64 = 41,
R_MIPS_TLS_GD = 42,
R_MIPS_TLS_LDM = 43,
R_MIPS_TLS_DTPREL_HI16 = 44,
R_MIPS_TLS_DTPREL_LO16 = 45,
R_MIPS_TLS_GOTTPREL = 46,
R_MIPS_TLS_TPREL32 = 47,
R_MIPS_TLS_TPREL64 = 48,
R_MIPS_TLS_TPREL_HI16 = 49,
R_MIPS_TLS_TPREL_LO16 = 50,
R_MIPS_GLOB_DAT = 51,
R_MIPS_PC21_S2 = 60,
R_MIPS_PC26_S2 = 61,
R_MIPS_PC18_S3 = 62,
R_MIPS_PC19_S2 = 63,
R_MIPS_PCHI16 = 64,
R_MIPS_PCLO16 = 65,
R_MIPS16_26 = 100,
R_MIPS16_GPREL = 101,
R_MIPS16_GOT16 = 102,
R_MIPS16_CALL16 = 103,
R_MIPS16_HI16 = 104,
R_MIPS16_LO16 = 105,
R_MIPS16_TLS_GD = 106,
R_MIPS16_TLS_LDM = 107,
R_MIPS16_TLS_DTPREL_HI16 = 108,
R_MIPS16_TLS_DTPREL_LO16 = 109,
R_MIPS16_TLS_GOTTPREL = 110,
R_MIPS16_TLS_TPREL_HI16 = 111,
R_MIPS16_TLS_TPREL_LO16 = 112,
R_MIPS_COPY = 126,
R_MIPS_JUMP_SLOT = 127,
R_MIPS_NUM = 218,
R_MIPS_PC32 = 248,
R_MIPS_EH = 249,
R_PARISC_NONE = 0,
R_PARISC_DIR32 = 1,
R_PARISC_DIR21L = 2,
R_PARISC_DIR17R = 3,
R_PARISC_DIR17F = 4,
R_PARISC_DIR14R = 6,
R_PARISC_DIR14F = 7,
R_PARISC_PCREL12F = 8,
R_PARISC_PCREL32 = 9,
R_PARISC_PCREL21L = 10,
R_PARISC_PCREL17R = 11,
R_PARISC_PCREL17F = 12,
R_PARISC_PCREL17C = 13,
R_PARISC_PCREL14R = 14,
R_PARISC_PCREL14F = 15,
R_PARISC_DPREL21L = 18,
R_PARISC_DPREL14WR = 19,
R_PARISC_DPREL14DR = 20,
R_PARISC_DPREL14R = 22,
R_PARISC_DPREL14F = 23,
R_PARISC_DLTREL21L = 26,
R_PARISC_DLTREL14R = 30,
R_PARISC_DLTREL14F = 31,
R_PARISC_DLTIND21L = 34,
R_PARISC_DLTIND14R = 38,
R_PARISC_DLTIND14F = 39,
R_PARISC_SETBASE = 40,
R_PARISC_SECREL32 = 41,
R_PARISC_BASEREL21L = 42,
R_PARISC_BASEREL17R = 43,
R_PARISC_BASEREL17F = 44,
R_PARISC_BASEREL14R = 46,
R_PARISC_BASEREL14F = 47,
R_PARISC_SEGBASE = 48,
R_PARISC_SEGREL32 = 49,
R_PARISC_PLTOFF21L = 50,
R_PARISC_PLTOFF14R = 54,
R_PARISC_PLTOFF14F = 55,
R_PARISC_LTOFF_FPTR32 = 57,
R_PARISC_LTOFF_FPTR21L = 58,
R_PARISC_LTOFF_FPTR14R = 62,
R_PARISC_FPTR64 = 64,
R_PARISC_PLABEL32 = 65,
R_PARISC_PLABEL21L = 66,
R_PARISC_PLABEL14R = 70,
R_PARISC_PCREL64 = 72,
R_PARISC_PCREL22C = 73,
R_PARISC_PCREL22F = 74,
R_PARISC_PCREL14WR = 75,
R_PARISC_PCREL14DR = 76,
R_PARISC_PCREL16F = 77,
R_PARISC_PCREL16WF = 78,
R_PARISC_PCREL16DF = 79,
R_PARISC_DIR64 = 80,
R_PARISC_DIR14WR = 83,
R_PARISC_DIR14DR = 84,
R_PARISC_DIR16F = 85,
R_PARISC_DIR16WF = 86,
R_PARISC_DIR16DF = 87,
R_PARISC_GPREL64 = 88,
R_PARISC_DLTREL14WR = 91,
R_PARISC_DLTREL14DR = 92,
R_PARISC_GPREL16F = 93,
R_PARISC_GPREL16WF = 94,
R_PARISC_GPREL16DF = 95,
R_PARISC_LTOFF64 = 96,
R_PARISC_DLTIND14WR = 99,
R_PARISC_DLTIND14DR = 100,
R_PARISC_LTOFF16F = 101,
R_PARISC_LTOFF16WF = 102,
R_PARISC_LTOFF16DF = 103,
R_PARISC_SECREL64 = 104,
R_PARISC_BASEREL14WR = 107,
R_PARISC_BASEREL14DR = 108,
R_PARISC_SEGREL64 = 112,
R_PARISC_PLTOFF14WR = 115,
R_PARISC_PLTOFF14DR = 116,
R_PARISC_PLTOFF16F = 117,
R_PARISC_PLTOFF16WF = 118,
R_PARISC_PLTOFF16DF = 119,
R_PARISC_LTOFF_FPTR64 = 120,
R_PARISC_LTOFF_FPTR14WR = 123,
R_PARISC_LTOFF_FPTR14DR = 124,
R_PARISC_LTOFF_FPTR16F = 125,
R_PARISC_LTOFF_FPTR16WF = 126,
R_PARISC_LTOFF_FPTR16DF = 127,
R_PARISC_COPY = 128,
R_PARISC_IPLT = 129,
R_PARISC_EPLT = 130,
R_PARISC_TPREL32 = 153,
R_PARISC_TPREL21L = 154,
R_PARISC_TPREL14R = 158,
R_PARISC_LTOFF_TP21L = 162,
R_PARISC_LTOFF_TP14R = 166,
R_PARISC_LTOFF_TP14F = 167,
R_PARISC_TPREL64 = 216,
R_PARISC_TPREL14WR = 219,
R_PARISC_TPREL14DR = 220,
R_PARISC_TPREL16F = 221,
R_PARISC_TPREL16WF = 222,
R_PARISC_TPREL16DF = 223,
R_PARISC_LTOFF_TP64 = 224,
R_PARISC_LTOFF_TP14WR = 227,
R_PARISC_LTOFF_TP14DR = 228,
R_PARISC_LTOFF_TP16F = 229,
R_PARISC_LTOFF_TP16WF = 230,
R_PARISC_LTOFF_TP16DF = 231,
R_PARISC_GNU_VTENTRY = 232,
R_PARISC_GNU_VTINHERIT = 233,
R_PARISC_TLS_GD21L = 234,
R_PARISC_TLS_GD14R = 235,
R_PARISC_TLS_GDCALL = 236,
R_PARISC_TLS_LDM21L = 237,
R_PARISC_TLS_LDM14R = 238,
R_PARISC_TLS_LDMCALL = 239,
R_PARISC_TLS_LDO21L = 240,
R_PARISC_TLS_LDO14R = 241,
R_PARISC_TLS_DTPMOD32 = 242,
R_PARISC_TLS_DTPMOD64 = 243,
R_PARISC_TLS_DTPOFF32 = 244,
R_PARISC_TLS_DTPOFF64 = 245,
};
//
@@ -1550,8 +1589,14 @@ 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 ElfRel always takes r_addend as a constructor argument.
// If it's REL, the argument will simply be ignored.
// 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.
template <typename E> requires E::is_le && E::is_rela
struct ElfRel<E> {
ElfRel() = default;
@@ -1673,9 +1718,6 @@ 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__
@@ -1738,72 +1780,10 @@ 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; };
@@ -1826,8 +1806,7 @@ 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_hppa32 = std::is_same_v<E, HPPA32>;
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>;
@@ -1837,8 +1816,7 @@ template <typename E> static constexpr bool is_riscv = is_rv64<E> || is_rv32<E>;
template <typename E> static constexpr bool is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
template <typename E> static constexpr bool is_ppc = is_ppc64<E> || is_ppc32<E>;
template <typename E> static constexpr bool is_sparc = is_sparc64<E>;
template <typename E> static constexpr bool is_mips64 = is_mips64le<E> || is_mips64be<E>;
template <typename E> static constexpr bool is_mips = is_mips64<E>;
template <typename E> static constexpr bool is_hppa = is_hppa32<E>;
struct X86_64 {
static constexpr std::string_view target_name = "x86_64";
@@ -2056,6 +2034,7 @@ 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;
@@ -2155,6 +2134,7 @@ 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;
@@ -2176,6 +2156,7 @@ 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;
@@ -2197,51 +2178,34 @@ 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;
struct HPPA32 {
static constexpr std::string_view target_name = "hppa32";
static constexpr bool is_64 = false;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_MIPS;
static constexpr u32 e_machine = EM_PARISC;
static constexpr u32 plt_hdr_size = 0;
static constexpr u32 plt_size = 16;
static constexpr u32 plt_size = 0;
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 20;
static constexpr u32 R_COPY = R_MIPS_COPY;
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
static constexpr u32 R_ABS = R_MIPS_64;
static constexpr u32 R_RELATIVE = R_MIPS_REL32;
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
static constexpr u32 R_COPY = R_PARISC_COPY;
static constexpr u32 R_GLOB_DAT = R_PARISC_DIR32;
static constexpr u32 R_JUMP_SLOT = R_PARISC_IPLT;
static constexpr u32 R_ABS = R_PARISC_DIR32;
static constexpr u32 R_RELATIVE = R_PARISC_DIR32;
static constexpr u32 R_IRELATIVE = R_NONE;
static constexpr u32 R_DTPOFF = R_PARISC_TLS_DTPOFF32;
static constexpr u32 R_TPOFF = R_PARISC_TPREL32;
static constexpr u32 R_DTPMOD = R_PARISC_TLS_DTPMOD32;
};
} // namespace mold::elf
+29 -11
View File
@@ -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 && !isec->is_visited.test_and_set();
return isec && isec->is_alive && fast_mark(isec->is_visited);
}
template <typename E>
@@ -53,18 +53,19 @@ 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 = true;
frag->is_alive.store(true, std::memory_order_relaxed);
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 (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());
}
if (depth < 3)
visit(ctx, sym.get_input_section(), feeder, depth + 1);
else
feeder.add(sym.get_input_section());
}
}
@@ -81,7 +82,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 = true;
frag->is_alive.store(true, std::memory_order_relaxed);
else
enqueue_section(sym->get_input_section());
}
@@ -93,10 +94,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;
@@ -162,10 +163,27 @@ 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);
+11 -1
View File
@@ -65,7 +65,7 @@
// conditions.
#include "mold.h"
#include "../common/sha.h"
#include "../sha.h"
#include <array>
#include <cstdio>
@@ -90,6 +90,16 @@ 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");
+199 -91
View File
@@ -42,12 +42,6 @@ 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)
@@ -56,20 +50,21 @@ 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 (__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);
}
}
for (Symbol<E> *sym : get_global_syms())
if (sym->file == this)
sym->clear();
}
// Find the source filename. It should be listed in symtab as STT_FILE.
@@ -140,26 +135,6 @@ 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
@@ -180,7 +155,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[get_shndx(esym)].sh_name;
this->elf_sections[esym.st_shndx].sh_name;
} else {
signature = this->symbol_strtab.data() + esym.st_name;
}
@@ -206,8 +181,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
comdat_groups.push_back({group, (u32)i, entries.subspan(1)});
break;
}
case SHT_SYMTAB:
case SHT_SYMTAB_SHNDX:
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, shdr);
break;
case SHT_SYMTAB:
case SHT_STRTAB:
case SHT_REL:
case SHT_RELA:
@@ -221,11 +198,8 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// area in GNU linkers. We ignore that section because silently
// making the stack area executable is too dangerous. Tell our
// users about the difference if that matters.
//
// MIPS object files don't contain .note.GNU-stack for some reason,
// so ignore this error on MIPS.
if (name == ".note.GNU-stack" && !ctx.arg.relocatable) {
if ((shdr.sh_flags & SHF_EXECINSTR) && !is_mips<E>) {
if (shdr.sh_flags & SHF_EXECINSTR) {
if (!ctx.arg.z_execstack && !ctx.arg.z_execstack_if_needed)
Warn(ctx) << *this << ": this file may cause a segmentation"
" fault because it requires an executable stack. See"
@@ -280,10 +254,6 @@ 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();
@@ -349,6 +319,7 @@ 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;
}
}
}
@@ -519,7 +490,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;
has_symver.resize(num_globals);
symvers.resize(num_globals);
for (i64 i = 0; i < this->first_global; i++)
this->symbols[i] = &this->local_syms[i];
@@ -534,13 +505,14 @@ 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);
std::string_view ver = name.substr(pos + 1);
name = name.substr(0, pos);
if (ver != "@" && ver != "@@") {
if (ver.starts_with("@@"))
if (!ver.empty() && ver != "@") {
if (ver.starts_with('@'))
key = name;
has_symver.set(i - this->first_global);
if (!esym.is_undef())
symvers[i - this->first_global] = ver.data();
}
}
@@ -603,7 +575,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.relsec_idx != -1)
if (!sec.is_alive || sec.sh_size == 0 || sec.relsec_idx != -1)
return nullptr;
const ElfShdr<E> &shdr = sec.shdr();
@@ -615,9 +587,6 @@ 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);
@@ -739,7 +708,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(ctx, m->strings[i], m->hashes[i],
m->fragments.push_back(m->parent->insert(m->strings[i], m->hashes[i],
m->p2align));
// Shrink vectors that we will never use again to reclaim memory.
@@ -757,7 +726,7 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
continue;
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[get_shndx(esym)];
if (!m || m->fragments.empty())
if (!m)
continue;
SectionFragment<E> *frag;
@@ -864,9 +833,6 @@ 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);
@@ -888,22 +854,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_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;
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;
}
if (file->is_dso || is_lazy) {
if (esym.st_bind == STB_WEAK)
return 2;
return 1;
};
return (get_sym_rank() << 24) + file->priority;
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;
}
template <typename E>
@@ -977,6 +943,8 @@ 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;
}
}
}
@@ -1006,7 +974,7 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
continue;
bool keep = esym.is_undef() || (esym.is_common() && !sym.esym().is_common());
if (keep && !sym.file->is_alive.test_and_set()) {
if (keep && fast_mark(sym.file->is_alive)) {
feeder(sym.file);
if (sym.is_traced)
@@ -1016,6 +984,135 @@ 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
@@ -1103,6 +1200,8 @@ 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));
}
@@ -1190,7 +1289,8 @@ 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;
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
symtab_idx * 4);
symtab_base[symtab_idx++] = to_output_esym(ctx, sym, strtab_off, xindex);
strtab_off += write_string(strtab_base + strtab_off, sym.name());
@@ -1381,6 +1481,8 @@ 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;
}
}
}
@@ -1396,7 +1498,8 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
if (sym.is_traced)
print_trace_symbol(ctx, *this, esym, sym);
if (esym.is_undef() && sym.file && !sym.file->is_alive.test_and_set()) {
if (esym.is_undef() && sym.file && !sym.file->is_dso &&
fast_mark(sym.file->is_alive)) {
feeder(sym.file);
if (sym.is_traced)
@@ -1407,7 +1510,7 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
}
template <typename E>
std::span<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
assert(sym->file == this);
std::call_once(init_aliases, [&] {
@@ -1423,12 +1526,16 @@ std::span<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
});
});
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;
});
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; }
};
return {&*begin, &*end};
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;
}
// Infer an alignment of a DSO symbol. An alignment of a symbol in other
@@ -1448,12 +1555,13 @@ i64 SharedFile<E>::get_alignment(Symbol<E> *sym) {
}
template <typename E>
bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
bool SharedFile<E>::is_readonly(Context<E> &ctx, Symbol<E> *sym) {
ElfPhdr<E> *phdr = this->get_phdr();
u64 val = sym->esym().st_value;
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)
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)
return true;
return false;
}
@@ -1493,8 +1601,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;
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
(this->global_symtab_idx + i) * 4);
*symtab++ = to_output_esym(ctx, sym, strtab_off, xindex);
strtab_off += write_string(strtab + strtab_off, sym.name());
+51 -73
View File
@@ -57,10 +57,7 @@ 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.
//
// SH-4 stores addends to sections despite being RELA, which is a
// special (and buggy) case.
if constexpr (!E::is_rela || is_sh4<E>)
if constexpr (!E::is_rela)
uncompress(ctx);
}
@@ -209,7 +206,7 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
break;
case IFUNC:
dynrel();
ctx.num_ifunc_dynrels++;
ctx.num_ifunc_dynrels.fetch_add(1, std::memory_order_relaxed);
break;
default:
unreachable();
@@ -309,15 +306,6 @@ 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,
@@ -361,16 +349,13 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
case DYNREL:
apply_dynrel();
break;
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();
}
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;
break;
}
default:
unreachable();
}
@@ -400,10 +385,11 @@ 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) {
@@ -416,7 +402,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) const {
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
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) {
@@ -429,69 +415,61 @@ std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) con
return "";
}
// Test if the symbol a given relocation refers to has already been resolved.
// If not, record that error and returns true.
// Record an undefined symbol error which will be displayed all at
// once by report_undef_errors().
template <typename E>
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;
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 << ")";
Symbol<E> &sym = *file.symbols[rel.r_sym];
const ElfSym<E> &esym = file.elf_syms[rel.r_sym];
// 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;
}
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);
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 << "undefined symbol: " << sym_name << "\n";
ss << ">>> " << file;
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
ss << ":(" << func << ")";
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
ss << errors[i];
typename decltype(ctx.undef_errors)::accessor acc;
ctx.undef_errors.insert(acc, {sym.name(), {}});
acc->second.push_back(ss.str());
};
if (errors.size() > max_errors)
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
// 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 (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
Error(ctx) << ss.str();
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
Warn(ctx) << ss.str();
}
// 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;
ctx.checkpoint();
}
using E = MOLD_TARGET;
template struct CieRecord<E>;
template class InputSection<E>;
template void report_undef_errors(Context<E> &);
} // namespace mold::elf
-49
View File
@@ -1,49 +0,0 @@
#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
+13 -30
View File
@@ -641,34 +641,14 @@ 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 (i64 i = 0; i < plugin_symbols.size(); i++) {
PluginSymbol &psym = plugin_symbols[i];
for (PluginSymbol &psym : plugin_symbols) {
esyms->push_back(to_elf_sym<E>(psym));
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->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
}
obj->elf_syms = *esyms;
obj->has_symver.resize(esyms->size());
obj->symvers.resize(esyms->size());
plugin_symbols.clear();
return obj;
}
@@ -686,13 +666,16 @@ 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) {
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;
}
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;
}
}
});
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "../common/integers.h"
#include "../integers.h"
namespace mold {
+20 -31
View File
@@ -1,7 +1,7 @@
#include "mold.h"
#include "../common/archive-file.h"
#include "../common/cmdline.h"
#include "../common/output-file.h"
#include "../archive-file.h"
#include "../cmdline.h"
#include "../output-file.h"
#include <cstring>
#include <functional>
@@ -70,28 +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 "";
case EM_PARISC:
return HPPA32::target_name;
default:
return "";
}
};
switch (get_file_type(ctx, mf)) {
bool opt_plugin = !ctx.arg.plugin.empty();
switch (get_file_type(mf, opt_plugin)) {
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(ctx, child) == FileType::ELF_OBJ)
if (get_file_type(child, opt_plugin) == 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(ctx, child) == FileType::ELF_OBJ)
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
return get_elf_type(child->data);
return "";
case FileType::TEXT:
@@ -165,7 +165,9 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
if (ctx.visited.contains(mf->name))
return;
switch (get_file_type(ctx, mf)) {
bool opt_plugin = !ctx.arg.plugin.empty();
FileType type = get_file_type(mf, opt_plugin);
switch (type) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, mf, ""));
return;
@@ -176,7 +178,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(ctx, child)) {
switch (get_file_type(child, opt_plugin)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
break;
@@ -185,10 +187,6 @@ 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;
}
@@ -375,10 +373,8 @@ 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);
if (target == HPPA32::target_name)
return elf_main<HPPA32>(argc, argv);
unreachable();
}
@@ -424,8 +420,6 @@ 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);
@@ -474,9 +468,6 @@ 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);
@@ -559,8 +550,10 @@ 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)
if (ctx.arg.print_dependencies == 1)
print_dependencies(ctx);
else if (ctx.arg.print_dependencies == 2)
print_dependencies_full(ctx);
// Handle -repro
if (ctx.arg.repro)
@@ -740,8 +733,6 @@ int elf_main(int argc, char **argv) {
if (on_complete)
on_complete();
release_global_lock(ctx);
if (ctx.arg.quick_exit)
_exit(0);
@@ -754,7 +745,6 @@ 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
@@ -773,8 +763,7 @@ 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 **);
extern template int elf_main<HPPA32>(int, char **);
int main(int argc, char **argv) {
return elf_main<X86_64>(argc, argv);
+1 -2
View File
@@ -61,8 +61,7 @@ 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.mold");
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
}
int execvpe(const char *file, char *const *argv, char *const *envp) {
+167 -187
View File
@@ -1,7 +1,7 @@
#pragma once
#include "elf.h"
#include "../common/common.h"
#include "../mold.h"
#include <atomic>
#include <bitset>
@@ -60,15 +60,18 @@ std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
template <typename E>
struct SectionFragment {
SectionFragment(MergedSection<E> *sec, bool is_alive)
: output_section(*sec), is_alive(is_alive) {}
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()) {}
u64 get_addr(Context<E> &ctx) const;
MergedSection<E> &output_section;
u32 offset = -1;
Atomic<u8> p2align = 0;
Atomic<bool> is_alive = false;
std::atomic_uint8_t p2align = 0;
std::atomic_bool is_alive = false;
};
// Additional class members for dynamic symbols. Because most symbols
@@ -87,8 +90,8 @@ struct SymbolAux {
u32 djb_hash = 0;
};
template <>
struct SymbolAux<PPC64V1> : SymbolAux<X86_64> {
template <typename E> requires is_ppc64v1<E> || is_hppa<E>
struct SymbolAux<E> : SymbolAux<X86_64> {
i32 opd_idx = -1;
};
@@ -102,9 +105,7 @@ class RangeExtensionThunk {};
template <typename E> requires needs_thunk<E>
class RangeExtensionThunk<E> {
public:
RangeExtensionThunk(OutputSection<E> &osec, i64 thunk_idx, i64 offset)
: output_section(osec), thunk_idx(thunk_idx), offset(offset) {}
RangeExtensionThunk(OutputSection<E> &osec) : output_section(osec) {}
i64 size() const { return E::thunk_hdr_size + symbols.size() * E::thunk_size; }
void copy_buf(Context<E> &ctx);
@@ -116,15 +117,15 @@ public:
static constexpr i64 alignment = 4;
OutputSection<E> &output_section;
i64 thunk_idx;
i64 offset;
i32 thunk_idx = -1;
i64 offset = -1;
std::mutex mu;
std::vector<Symbol<E> *> symbols;
};
struct RangeExtensionRef {
i16 thunk_idx = -1;
i16 sym_idx = -1;
i32 thunk_idx = -1;
i32 sym_idx = -1;
};
template <typename E>
@@ -201,6 +202,20 @@ 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;
@@ -209,7 +224,7 @@ struct FdeRecord {
u32 output_offset = -1;
u32 rel_idx = -1;
u16 cie_idx = -1;
Atomic<bool> is_alive = true;
std::atomic_bool is_alive = true;
};
// A struct to hold target-dependent input section members.
@@ -247,11 +262,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) const;
bool is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) const;
std::string_view get_func_name(Context<E> &ctx, i64 offset);
bool is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel);
bool is_killed_by_icf() const;
bool record_undef_error(Context<E> &ctx, const ElfRel<E> &rel);
void record_undef_error(Context<E> &ctx, const ElfRel<E> &rel);
ObjectFile<E> &file;
OutputSection<E> *output_section = nullptr;
@@ -277,7 +292,7 @@ public:
bool uncompressed : 1 = false;
// For garbage collection
Atomic<bool> is_visited = false;
std::atomic_bool is_visited = false;
// For ICF
//
@@ -297,8 +312,6 @@ 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);
@@ -310,18 +323,14 @@ private:
std::pair<SectionFragment<E> *, i64>
get_fragment(Context<E> &ctx, const ElfRel<E> &rel);
u64 has_thunk(i64 idx);
u64 get_thunk_addr(i64 idx);
std::optional<u64> get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag);
};
//
// tls.cc
//
template<typename E> u64 get_tls_begin(Context<E> &);
template<typename E> u64 get_tp_addr(Context<E> &);
template<typename E> u64 get_dtp_addr(Context<E> &);
template <typename E>
void report_undef_errors(Context<E> &ctx);
//
// output-chunks.cc
@@ -357,7 +366,6 @@ 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) {}
@@ -366,7 +374,7 @@ public:
virtual u8 *get_uncompressed_data() { return nullptr; }
std::string_view name;
ElfShdr<E> shdr = { .sh_addralign = 1 };
ElfShdr<E> shdr = {};
i64 shndx = 0;
// Some synethetic sections add local symbols to the output.
@@ -381,11 +389,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
@@ -484,10 +492,9 @@ public:
this->shdr.sh_addralign = sizeof(Word<E>);
// We always create a .got so that _GLOBAL_OFFSET_TABLE_ has
// something to point to. s390x/MIPS psABIs define GOT[1] as a
// 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>);
// 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>);
}
void add_got_symbol(Context<E> &ctx, Symbol<E> *sym);
@@ -498,16 +505,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 override;
i64 get_reldyn_size(Context<E> &ctx) const;
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);
@@ -604,6 +611,7 @@ public:
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
void sort(Context<E> &ctx);
};
@@ -772,9 +780,7 @@ public:
static MergedSection<E> *
get_instance(Context<E> &ctx, std::string_view name, u64 type, u64 flags);
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
u64 hash, i64 p2align);
SectionFragment<E> *insert(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;
@@ -850,7 +856,6 @@ 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;
@@ -1147,7 +1152,7 @@ public:
std::string_view get_string(Context<E> &ctx, i64 idx);
ElfEhdr<E> &get_ehdr() { return *(ElfEhdr<E> *)mf->data; }
std::span<ElfPhdr<E>> get_phdrs();
ElfPhdr<E> *get_phdr() { return (ElfPhdr<E> *)(mf->data + get_ehdr().e_phoff); }
ElfShdr<E> *find_section(i64 type);
@@ -1170,7 +1175,7 @@ public:
std::string filename;
bool is_dso = false;
u32 priority;
Atomic<bool> is_alive = false;
std::atomic_bool is_alive = false;
std::string_view shstrtab;
std::string_view symbol_strtab;
@@ -1206,7 +1211,10 @@ 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);
@@ -1222,7 +1230,7 @@ public:
std::vector<ElfShdr<E>> elf_sections2;
std::vector<CieRecord<E>> cies;
std::vector<FdeRecord<E>> fdes;
BitVector has_symver;
std::vector<const char *> symvers;
std::vector<ComdatGroupRef<E>> comdat_groups;
bool exclude_libs = false;
std::map<u32, u32> gnu_properties;
@@ -1255,15 +1263,9 @@ 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);
@@ -1292,9 +1294,9 @@ public:
void parse(Context<E> &ctx);
void resolve_symbols(Context<E> &ctx) override;
std::span<Symbol<E> *> find_aliases(Symbol<E> *sym);
std::vector<Symbol<E> *> find_aliases(Symbol<E> *sym);
i64 get_alignment(Symbol<E> *sym);
bool is_readonly(Symbol<E> *sym);
bool is_readonly(Context<E> &ctx, Symbol<E> *sym);
void mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) override;
@@ -1390,13 +1392,6 @@ 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
//
@@ -1413,7 +1408,6 @@ 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> &);
@@ -1421,6 +1415,8 @@ 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> &);
@@ -1433,7 +1429,6 @@ 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> &);
@@ -1478,7 +1473,6 @@ 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;
@@ -1486,6 +1480,23 @@ 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
//
@@ -1519,7 +1530,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) const override;
i64 get_reldyn_size(Context<ALPHA> &ctx);
void copy_buf(Context<ALPHA> &ctx) override;
struct Entry {
@@ -1528,60 +1539,48 @@ public:
i64 addend;
};
i64 reldyn_offset = 0;
private:
std::vector<Entry> entries;
std::mutex mu;
};
//
// arch-mips64.cc
// arch-hppa.cc
//
template <typename E>
class MipsGotSection : public Chunk<E> {
class HppaOpdSection : public Chunk<HPPA32> {
public:
MipsGotSection() {
this->name = ".mips_got";
HppaOpdSection() {
this->name = ".opd";
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_MIPS_GPREL;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR;
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 add_symbol(Context<HPPA32> &ctx, Symbol<HPPA32> *sym);
void update_shdr(Context<HPPA32> &ctx) override;
void copy_buf(Context<HPPA32> &ctx) override;
void update_shdr(Context<E> &ctx) override;
i64 get_reldyn_size(Context<E> &ctx) const override;
void copy_buf(Context<E> &ctx) override;
static constexpr i64 ENTRY_SIZE = sizeof(Word<HPPA32>) * 2;
static constexpr i64 TRAILER_SIZE = sizeof(Word<HPPA32>) * 9;
struct GotEntry {
u64 val = 0;
i64 r_type = R_NONE;
Symbol<E> *sym = nullptr;
};
std::vector<Symbol<HPPA32> *> symbols;
};
struct SymbolAddend {
bool operator==(const SymbolAddend &) const = default;
bool operator<(const SymbolAddend &) const;
u64 get_addr(Context<E> &ctx, i64 flags = 0) const;
class HppaRelOpdSection : public Chunk<HPPA32> {
public:
HppaRelOpdSection() {
this->name = ".rela.opd";
this->shdr.sh_type = SHT_RELA;
this->shdr.sh_flags = SHF_ALLOC;
this->shdr.sh_entsize = sizeof(ElfRel<HPPA32>);
this->shdr.sh_addralign = sizeof(Word<HPPA32>);
}
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;
void update_shdr(Context<HPPA32> &ctx) override;
void copy_buf(Context<HPPA32> &ctx) override;
};
//
@@ -1636,7 +1635,7 @@ struct SectionOrder {
u64 value = 0;
};
// Target-specific context members
// Target-specific context clss
template <typename E> struct ContextExtras {};
template <> struct ContextExtras<PPC32> {
@@ -1650,21 +1649,24 @@ 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_sec = nullptr;
Symbol<SPARC64> *tls_get_addr_sym = nullptr;
SparcTlsGetAddrSection *tls_get_addr = nullptr;
};
template <> struct ContextExtras<S390X> {
S390XTlsGetOffsetSection *tls_get_offset = nullptr;
};
template <> struct ContextExtras<ALPHA> {
AlphaGotSection *got = nullptr;
};
template <typename E> requires is_mips<E>
struct ContextExtras<E> {
MipsGotSection<E> *got = nullptr;
template <> struct ContextExtras<HPPA32> {
HppaOpdSection *opd = nullptr;
HppaRelOpdSection *relopd = nullptr;
Symbol<HPPA32> *global = nullptr;
};
// Context represents a context object for each invocation of the linker.
@@ -1694,11 +1696,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;
@@ -1709,7 +1711,7 @@ struct Context {
bool fork = true;
bool gc_sections = false;
bool gdb_index = false;
bool hash_style_gnu = !is_mips<E>;
bool hash_style_gnu = true;
bool hash_style_sysv = true;
bool icf = false;
bool icf_all = false;
@@ -1723,7 +1725,6 @@ 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;
@@ -1738,7 +1739,6 @@ 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;
@@ -1750,7 +1750,7 @@ struct Context {
bool z_dlopen = true;
bool z_dump = true;
bool z_dynamic_undefined_weak = true;
bool z_execstack = false;
bool z_execstack = is_hppa<E>;
bool z_execstack_if_needed = false;
bool z_ibt = false;
bool z_initfirst = false;
@@ -1762,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;
@@ -1775,7 +1775,7 @@ struct Context {
std::string dependency_file;
std::string directory;
std::string dynamic_linker;
std::string entry = is_mips<E> ? "__start" : "_start";
std::string entry = "_start";
std::string fini = "_fini";
std::string init = "_init";
std::string output = "a.out";
@@ -1805,11 +1805,8 @@ 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;
@@ -1858,6 +1855,7 @@ 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;
@@ -1900,6 +1898,10 @@ 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;
@@ -1936,7 +1938,6 @@ struct Context {
Symbol<E> *_edata = nullptr;
Symbol<E> *_end = nullptr;
Symbol<E> *_etext = nullptr;
Symbol<E> *_gp = nullptr;
Symbol<E> *edata = nullptr;
Symbol<E> *end = nullptr;
Symbol<E> *etext = nullptr;
@@ -1974,7 +1975,8 @@ enum {
NEEDS_TLSGD = 1 << 4,
NEEDS_COPYREL = 1 << 5,
NEEDS_TLSDESC = 1 << 6,
NEEDS_PPC_OPD = 1 << 7, // for PPCv1
NEEDS_OPD = 1 << 7, // for PPCv1 or HPPA
NEEDS_ALPHA_GOT = 1 << 7, // for Alpha
};
// A struct to hold target-dependent symbol members.
@@ -1984,8 +1986,8 @@ struct SymbolExtras {};
template <typename E> requires needs_thunk<E>
struct SymbolExtras<E> {
// For range extension thunks
i16 thunk_idx = -1;
i16 thunk_sym_idx = -1;
i32 thunk_idx = -1;
i32 thunk_sym_idx = -1;
};
// Flags for Symbol<E>::get_addr()
@@ -2046,6 +2048,7 @@ public:
bool is_absolute() const;
bool is_relative() const { return !is_absolute(); }
bool is_local(Context<E> &ctx) const;
bool is_func() const { return get_type() == STT_FUNC; }
bool is_ifunc() const { return get_type() == STT_GNU_IFUNC; }
bool is_remaining_undef_weak() const;
@@ -2064,7 +2067,7 @@ public:
std::string_view get_version() const;
i64 get_output_sym_idx(Context<E> &ctx) const;
const ElfSym<E> &esym() const;
void add_aux(Context<E> &ctx);
void clear();
// 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.
@@ -2101,10 +2104,10 @@ public:
u16 ver_idx = 0;
// `flags` has NEEDS_ flags.
Atomic<u8> flags = 0;
std::atomic_uint8_t flags = 0;
tbb::spin_mutex mu;
Atomic<u8> visibility = STV_DEFAULT;
std::atomic_uint8_t visibility = STV_DEFAULT;
bool is_weak : 1 = false;
bool write_to_symtab : 1 = false; // for --strip-all and the like
@@ -2210,10 +2213,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, `is_copyrel_readonly` is set to true so that the copied data
// region, `copyrel_readonly` is set to true so that the copied data
// will become read-only at run-time.
bool has_copyrel : 1 = false;
bool is_copyrel_readonly : 1 = false;
bool copyrel_readonly : 1 = false;
// For LTO. True if the symbol is referenced by a regular object (as
// opposed to IR object).
@@ -2324,9 +2327,6 @@ 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())
@@ -2363,6 +2363,13 @@ InputSection<E>::get_fragment(Context<E> &ctx, const ElfRel<E> &rel) {
return {nullptr, 0};
}
template <typename E>
u64 InputSection<E>::has_thunk(i64 idx) {
if constexpr (needs_thunk<E>)
return extra.range_extn[idx].thunk_idx != -1;
unreachable();
}
template <typename E>
u64 InputSection<E>::get_thunk_addr(i64 idx) {
if constexpr (needs_thunk<E>) {
@@ -2412,8 +2419,7 @@ 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) const {
inline bool InputSection<E>::is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) {
return ctx.arg.pack_dyn_relocs_relr &&
!(shdr().sh_flags & SHF_EXECINSTR) &&
(shdr().sh_addralign % sizeof(Word<E>)) == 0 &&
@@ -2491,24 +2497,6 @@ 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()) {
@@ -2524,7 +2512,7 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
}
if (has_copyrel) {
return is_copyrel_readonly
return copyrel_readonly
? ctx.copyrel_relro->shdr.sh_addr + value
: ctx.copyrel->shdr.sh_addr + value;
}
@@ -2551,17 +2539,11 @@ 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 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)
if (name() == "__EH_FRAME_BEGIN__" || name() == "__EH_FRAME_LIST__" ||
name() == ".eh_frame_seg" || esym().st_type == STT_SECTION)
return ctx.eh_frame->shdr.sh_addr;
if (name().starts_with("__FRAME_END__") ||
name().starts_with("__EH_FRAME_LIST_END__"))
if (name() == "__FRAME_END__" || name() == "__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
@@ -2570,7 +2552,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 to .eh_frame is not supported: "
Fatal(ctx) << "symbol referring .eh_frame is not supported: "
<< *this << " " << *file;
}
@@ -2615,25 +2597,10 @@ 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 + to_plt_offset<E>(idx);
return ctx.plt->shdr.sh_addr + E::plt_hdr_size + idx * E::plt_size;
return ctx.pltgot->shdr.sh_addr + get_pltgot_idx(ctx) * E::pltgot_size;
}
@@ -2641,7 +2608,7 @@ template <typename E>
inline u64 Symbol<E>::get_opd_addr(Context<E> &ctx) const {
assert(get_opd_idx(ctx) != -1);
return ctx.extra.opd->shdr.sh_addr +
get_opd_idx(ctx) * PPC64OpdSection::ENTRY_SIZE;
get_opd_idx(ctx) * ctx.extra.opd->ENTRY_SIZE;
}
template <typename E>
@@ -2862,12 +2829,15 @@ inline std::string_view Symbol<E>::name() const {
}
template <typename E>
inline void Symbol<E>::add_aux(Context<E> &ctx) {
if (aux_idx == -1) {
i64 sz = ctx.symbol_aux.size();
aux_idx = sz;
ctx.symbol_aux.resize(sz + 1);
}
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 bool is_c_identifier(std::string_view s) {
@@ -2890,6 +2860,16 @@ 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
+276 -220
View File
@@ -1,5 +1,5 @@
#include "mold.h"
#include "../common/sha.h"
#include "../sha.h"
#include <cctype>
#include <set>
@@ -36,6 +36,24 @@ static u32 djb_hash(std::string_view name) {
return h;
}
template <typename E>
static u64 get_osabi() {
// The EI_OSABI field in the ELF header specified what ABI variant the
// output file is using. In practice, it is either GNU or NONE.
//
// Technically, if we use GNU extensions such as GNU ifunc, we should
// set it to GNU and otherwise NONE. That being said, GNU extensions are
// so prevalent that no runtime bothers to check the value. Likewise, we
// don't bother to change the value of EI_OSABI depending on the feature
// that the output file depends on.
//
// HPPA's objdump refuses to disassemble an executable unless it is
// ELFOSABI_GNU, so we unconditionally set it to GNU for HPPA.
if constexpr (is_hppa<E>)
return ELFOSABI_GNU;
return ELFOSABI_NONE;
}
template <typename E>
u64 get_eflags(Context<E> &ctx) {
if constexpr (is_arm32<E>)
@@ -49,24 +67,18 @@ u64 get_eflags(Context<E> &ctx) {
return 0;
u32 ret = objs[0]->get_ehdr().e_flags;
for (i64 i = 1; i < objs.size(); i++) {
u32 flags = objs[i]->get_ehdr().e_flags;
if (flags & EF_RISCV_RVC)
for (i64 i = 1; i < objs.size(); i++)
if (objs[i]->get_ehdr().e_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;
}
if constexpr (is_ppc64v2<E>)
return 2;
if constexpr (is_hppa<E>)
return 0x210;
return 0;
}
@@ -84,8 +96,9 @@ void OutputEhdr<E>::copy_buf(Context<E> &ctx) {
sym->file && !sym->file->is_dso)
return sym->get_addr(ctx);
if (OutputSection<E> *osec = find_section(ctx, ".text"))
return osec->shdr.sh_addr;
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->name == ".text")
return chunk->shdr.sh_addr;
return 0;
};
@@ -93,6 +106,7 @@ void OutputEhdr<E>::copy_buf(Context<E> &ctx) {
hdr.e_ident[EI_CLASS] = E::is_64 ? ELFCLASS64 : ELFCLASS32;
hdr.e_ident[EI_DATA] = E::is_le ? ELFDATA2LSB : ELFDATA2MSB;
hdr.e_ident[EI_VERSION] = EV_CURRENT;
hdr.e_ident[EI_OSABI] = get_osabi<E>();
hdr.e_machine = E::e_machine;
hdr.e_version = EV_CURRENT;
hdr.e_entry = get_entry_addr();
@@ -202,6 +216,80 @@ 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> || is_hppa<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;
@@ -213,8 +301,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;
if (chunk->shdr.sh_type != SHT_NOBITS)
phdr.p_filesz = chunk->shdr.sh_size;
phdr.p_filesz =
(chunk->shdr.sh_type == SHT_NOBITS) ? 0 : (u64)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;
@@ -223,7 +311,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;
};
@@ -305,11 +393,15 @@ 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) {
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++]);
}
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());
}
// Add PT_DYNAMIC
@@ -322,14 +414,11 @@ 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_memsz = ctx.arg.z_stack_size;
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_align = 1;
vec.push_back(phdr);
// Create a PT_GNU_RELRO.
if (ctx.arg.z_relro) {
@@ -344,15 +433,15 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
}
}
// 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);
// 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;
}
}
}
// Set p_paddr if --physical-image-base was given. --physical-image-base
// is typically used in embedded programming to specify the base address
@@ -407,10 +496,6 @@ 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>
@@ -430,11 +515,22 @@ void InterpSection<E>::copy_buf(Context<E> &ctx) {
template <typename E>
void RelDynSection<E>::update_shdr(Context<E> &ctx) {
i64 offset = 0;
this->shdr.sh_link = ctx.dynsym->shndx;
for (Chunk<E> *chunk : ctx.chunks) {
chunk->reldyn_offset = offset;
offset += chunk->get_reldyn_size(ctx) * sizeof(ElfRel<E>);
// .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 (ObjectFile<E> *file : ctx.objs) {
@@ -443,7 +539,29 @@ void RelDynSection<E>::update_shdr(Context<E> &ctx) {
}
this->shdr.sh_size = offset;
this->shdr.sh_link = ctx.dynsym->shndx;
}
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);
}
}
}
}
template <typename E>
@@ -454,12 +572,11 @@ void RelDynSection<E>::sort(Context<E> &ctx) {
ElfRel<E> *end = (ElfRel<E> *)((u8 *)begin + this->shdr.sh_size);
auto get_rank = [](u32 r_type) {
if (r_type == E::R_RELATIVE)
return 0;
if constexpr (supports_ifunc<E>)
if (r_type == E::R_IRELATIVE)
return 2;
return 1;
switch (r_type) {
case E::R_RELATIVE: return 0;
case E::R_IRELATIVE: return 2;
default: return 1;
}
};
// This is the reason why we sort dynamic relocations. Quote from
@@ -605,7 +722,7 @@ void SymtabSection<E>::update_shdr(Context<E> &ctx) {
// Section symbols
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->shndx)
if (chunk->shndx && (chunk->shdr.sh_flags & SHF_ALLOC))
nsyms++;
// Linker-synthesized symbols
@@ -646,19 +763,12 @@ void SymtabSection<E>::copy_buf(Context<E> &ctx) {
// Create section symbols
for (Chunk<E> *chunk : ctx.chunks) {
if (chunk->shndx) {
if (chunk->shndx && (chunk->shdr.sh_flags & SHF_ALLOC)) {
ElfSym<E> &sym = buf[chunk->shndx];
memset(&sym, 0, sizeof(sym));
sym.st_type = STT_SECTION;
sym.st_value = chunk->shdr.sh_addr;
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;
}
sym.st_shndx = chunk->shndx;
}
}
@@ -714,10 +824,18 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
define(DT_RELRENT, ctx.relrdyn->shdr.sh_entsize);
}
if (ctx.relplt->shdr.sh_size) {
define(DT_JMPREL, ctx.relplt->shdr.sh_addr);
define(DT_PLTRELSZ, ctx.relplt->shdr.sh_size);
define(DT_PLTREL, E::is_rela ? DT_RELA : DT_REL);
if constexpr (is_hppa<E>) {
if (ctx.extra.relopd->shdr.sh_size) {
define(DT_JMPREL, ctx.extra.relopd->shdr.sh_addr);
define(DT_PLTRELSZ, ctx.extra.relopd->shdr.sh_size);
define(DT_PLTREL, DT_RELA);
}
} else {
if (ctx.relplt->shdr.sh_size) {
define(DT_JMPREL, ctx.relplt->shdr.sh_addr);
define(DT_PLTRELSZ, ctx.relplt->shdr.sh_size);
define(DT_PLTREL, E::is_rela ? DT_RELA : DT_REL);
}
}
if constexpr (is_sparc<E>) {
@@ -726,8 +844,6 @@ 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);
@@ -743,19 +859,26 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
define(DT_STRSZ, ctx.dynstr->shdr.sh_size);
}
if (find_section(ctx, SHT_INIT_ARRAY)) {
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)) {
define(DT_INIT_ARRAY, ctx.__init_array_start->value);
define(DT_INIT_ARRAYSZ,
ctx.__init_array_end->value - ctx.__init_array_start->value);
}
if (find_section(ctx, SHT_PREINIT_ARRAY)) {
if (has_output_section(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 (find_section(ctx, SHT_FINI_ARRAY)) {
if (has_output_section(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);
@@ -818,7 +941,7 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
if (ctx.arg.z_interpose)
flags1 |= DF_1_INTERPOSE;
if (!ctx.got->gottp_syms.empty())
if (ctx.has_gottp_rel)
flags |= DF_STATIC_TLS;
if (ctx.has_textrel)
flags |= DF_TEXTREL;
@@ -838,16 +961,6 @@ 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)
@@ -886,18 +999,16 @@ 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 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;
}
// 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);
}
memset(loc, 0, size);
};
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
@@ -1097,6 +1208,7 @@ 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>
@@ -1105,9 +1217,7 @@ 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);
if (sym != ctx._TLS_MODULE_BASE_)
ctx.dynsym->add_symbol(ctx, sym);
ctx.dynsym->add_symbol(ctx, sym);
}
template <typename E>
@@ -1166,11 +1276,9 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
}
// IFUNC always needs to be fixed up by the dynamic linker.
if constexpr (supports_ifunc<E>) {
if (sym->is_ifunc()) {
entries.push_back({idx, sym->get_addr(ctx, NO_PLT), E::R_IRELATIVE});
continue;
}
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.
@@ -1185,25 +1293,13 @@ 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 a symbol is imported, let the dynamic linker to resolve it.
if (sym->is_imported) {
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 {
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>) {
@@ -1240,10 +1336,10 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
}
if (ctx.got->tlsld_idx != -1) {
if (ctx.arg.shared)
entries.push_back({ctx.got->tlsld_idx, 0, E::R_DTPMOD});
else
if (ctx.arg.is_static)
entries.push_back({ctx.got->tlsld_idx, 1}); // 1 means the main executable
else
entries.push_back({ctx.got->tlsld_idx, 0, E::R_DTPMOD});
}
return entries;
@@ -1255,7 +1351,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;
return n * sizeof(ElfRel<E>);
}
// Fill .got and .rel.dyn.
@@ -1277,13 +1373,7 @@ 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;
// 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);
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset);
for (GotEntry<E> &ent : get_got_entries(ctx)) {
if (ent.is_relr(ctx) || ent.r_type == R_NONE) {
@@ -1425,7 +1515,7 @@ void PltSection<E>::update_shdr(Context<E> &ctx) {
if (symbols.empty())
this->shdr.sh_size = 0;
else
this->shdr.sh_size = to_plt_offset<E>(symbols.size());
this->shdr.sh_size = E::plt_hdr_size + symbols.size() * E::plt_size;
}
template <typename E>
@@ -1434,7 +1524,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 + to_plt_offset<E>(i), *symbols[i]);
write_plt_entry(ctx, buf + E::plt_hdr_size + i * E::plt_size, *symbols[i]);
}
template <typename E>
@@ -1539,15 +1629,17 @@ template <typename E>
void RelPltSection<E>::copy_buf(Context<E> &ctx) {
ElfRel<E> *buf = (ElfRel<E> *)(ctx.buf + this->shdr.sh_offset);
for (Symbol<E> *sym : ctx.plt->symbols) {
for (i64 i = 0; i < ctx.plt->symbols.size(); i++) {
Symbol<E> &sym = *ctx.plt->symbols[i];
// 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++ = 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[i] = ElfRel<E>(addr, E::R_JUMP_SLOT, sym.get_dynsym_idx(ctx), 0);
}
}
@@ -1558,7 +1650,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.get_type();
esym.st_type = sym.esym().st_type;
esym.st_size = sym.esym().st_size;
if (sym.is_local(ctx))
@@ -1597,7 +1689,7 @@ ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
u32 shndx = 0;
if (sym.has_copyrel) {
shndx = sym.is_copyrel_readonly ? ctx.copyrel_relro->shndx : ctx.copyrel->shndx;
shndx = sym.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;
@@ -1699,13 +1791,10 @@ void DynsymSection<E>::finalize(Context<E> &ctx) {
// Compute .dynstr size
ctx.dynstr->dynsym_offset = ctx.dynstr->shdr.sh_size;
tbb::enumerable_thread_specific<i64> size;
tbb::parallel_for((i64)1, (i64)symbols.size(), [&](i64 i) {
for (i64 i = 1; i < symbols.size(); i++) {
symbols[i]->set_dynsym_idx(ctx, i);
size.local() += symbols[i]->name().size() + 1;
});
ctx.dynstr->shdr.sh_size += size.combine(std::plus());
ctx.dynstr->shdr.sh_size += symbols[i]->name().size() + 1;
}
// ELF's symbol table sh_info holds the offset of the first global symbol.
this->shdr.sh_info = first_global - symbols.begin();
@@ -1902,22 +1991,17 @@ MergedSection<E>::get_instance(Context<E> &ctx, std::string_view name,
template <typename E>
SectionFragment<E> *
MergedSection<E>::insert(Context<E> &ctx, std::string_view data, u64 hash,
i64 p2align) {
MergedSection<E>::insert(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, is_alive));
std::tie(frag, inserted) = map.insert(data, hash, SectionFragment(this));
assert(frag);
update_maximum(frag->p2align, p2align);
return frag;
}
@@ -1940,9 +2024,8 @@ 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 (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});
if (SectionFragment<E> &frag = map.values[j]; frag.is_alive)
fragments.push_back({{map.keys[j], map.key_sizes[j]}, &frag});
// Sort fragments to make output deterministic.
tbb::parallel_sort(fragments.begin(), fragments.end(),
@@ -2001,9 +2084,8 @@ 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 (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]);
if (SectionFragment<E> &frag = map.values[j]; frag.is_alive)
memcpy(buf + frag.offset, map.keys[j], map.key_sizes[j]);
});
}
@@ -2011,7 +2093,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.get_key(i))
if (map.keys[i])
used++;
SyncOut(ctx) << this->name
@@ -2261,30 +2343,16 @@ 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);
SharedFile<E> &file = *(SharedFile<E> *)sym->file;
i64 alignment = file.get_alignment(sym);
u64 offset = align_to(this->shdr.sh_size, alignment);
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;
this->shdr.sh_size = offset + sym->esym().st_size;
this->shdr.sh_size += 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>
@@ -2302,13 +2370,6 @@ 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>
@@ -2595,13 +2656,12 @@ 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);
@@ -2829,7 +2889,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.get_key(i)) {
if (map.has_key(i)) {
u32 hash = map.values[i].hash;
u32 step = (hash & mask) | 1;
u32 j = hash & mask;
@@ -2862,13 +2922,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 *attrs = (u32 *)buf;
U32<E> *attrs = (U32<E> *)buf;
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
if (map.get_key(j)) {
if (map.has_key(j)) {
MapEntry &ent = map.values[j];
u32 idx = (ent.owner.load()->attrs_offset + ent.attr_offset) / 4;
u32 *start = attrs + idx + 1;
U32<E> *start = attrs + idx + 1;
std::sort(start, start + attrs[idx]);
}
}
@@ -2882,9 +2942,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 (const char *key = map.get_key(j)) {
if (map.has_key(j)) {
ObjectFile<E> &file = *map.values[j].owner;
std::string_view name{key, map.key_sizes[j]};
std::string_view name{map.keys[j], map.key_sizes[j]};
write_string(buf + file.names_offset + map.values[j].name_offset, name);
}
}
@@ -3054,46 +3114,42 @@ 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) {
i64 symidx = 0;
i64 addend = 0;
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;
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()) {
symidx = frag->output_section.shndx;
out.r_sym = 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 (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.
}
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);
}
} else {
if (sym.sym_idx)
symidx = sym.get_output_sym_idx(ctx);
addend = get_addend(isec, rel);
out.r_sym = sym.get_output_sym_idx(ctx);
if constexpr (E::is_rela)
out.r_addend = get_addend(isec, rel);
}
if constexpr (is_alpha<E>)
if (rel.r_type == R_ALPHA_GPDISP || rel.r_type == R_ALPHA_LITUSE)
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);
}
out.r_addend = rel.r_addend;
};
tbb::parallel_for((i64)0, (i64)output_section.members.size(), [&](i64 i) {
+217 -297
View File
@@ -113,21 +113,24 @@ 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 constexpr (is_hppa<E>) {
ctx.extra.opd = push(new HppaOpdSection);
ctx.extra.relopd = push(new HppaRelOpdSection);
}
// If .dynamic exists, .dynsym and .dynstr must exist as well
// since .dynamic refers them.
@@ -135,6 +138,9 @@ void create_synthetic_sections(Context<E> &ctx) {
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>
@@ -224,16 +230,11 @@ void do_resolve_symbols(Context<E> &ctx) {
Timer t(ctx, "eliminate_comdats");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (ComdatGroupRef<E> &ref : file->comdat_groups)
update_minimum(ref.group->owner, file->priority);
file->resolve_comdat_groups();
});
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
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();
file->eliminate_duplicate_comdat_groups();
});
}
@@ -277,10 +278,12 @@ 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.
@@ -294,29 +297,6 @@ 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");
@@ -356,13 +336,24 @@ 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);
sec->insert(ctx, data, hash_string(data), 0);
SectionFragment<E> *frag = sec->insert(data, hash_string(data), 0);
frag->is_alive = true;
}
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);
@@ -371,6 +362,7 @@ 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);
@@ -391,21 +383,6 @@ static std::vector<std::span<T>> split(std::vector<T> &input, i64 unit) {
return vec;
}
template <typename E>
static u64 canonicalize_type(std::string_view name, u64 type) {
if (type == SHT_PROGBITS) {
if (name == ".init_array" || name.starts_with(".init_array."))
return SHT_INIT_ARRAY;
if (name == ".fini_array" || name.starts_with(".fini_array."))
return SHT_FINI_ARRAY;
}
if constexpr (is_x86_64<E>)
if (type == SHT_X86_64_UNWIND)
return SHT_PROGBITS;
return type;
}
struct OutputSectionKey {
std::string_view name;
u64 type;
@@ -456,7 +433,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.", ".openbsd.randomdata.",
".ctors.", ".dtors.", ".gnu.warning.",
};
for (std::string_view prefix : prefixes) {
@@ -473,18 +450,41 @@ static OutputSectionKey
get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
const ElfShdr<E> &shdr = isec.shdr();
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
u64 type = canonicalize_type<E>(name, shdr.sh_type);
u64 flags = shdr.sh_flags & ~(u64)SHF_COMPRESSED;
if (!ctx.arg.relocatable)
flags &= ~(u64)SHF_GROUP & ~(u64)SHF_GNU_RETAIN;
auto get_type = [&]() -> u32 {
if (shdr.sh_type == SHT_PROGBITS) {
if (name == ".init_array" || name.starts_with(".init_array."))
return SHT_INIT_ARRAY;
if (name == ".fini_array" || name.starts_with(".fini_array."))
return SHT_FINI_ARRAY;
}
// .init_array is usually writable. We don't want to create multiple
// .init_array output sections, so make it always writable.
// So is .fini_array.
if (type == SHT_INIT_ARRAY || type == SHT_FINI_ARRAY)
flags |= SHF_WRITE;
return {name, type, flags};
if constexpr (is_x86_64<E>)
if (shdr.sh_type == SHT_X86_64_UNWIND)
return SHT_PROGBITS;
return shdr.sh_type;
};
u32 type = get_type();
auto get_flags = [&]() -> u32 {
if constexpr (is_hppa<E>)
if (name == ".PARISC.unwind")
return SHF_ALLOC | SHF_INFO_LINK;
u32 flags = shdr.sh_flags & ~(u64)SHF_COMPRESSED;
if (!ctx.arg.relocatable)
flags &= ~(u64)SHF_GROUP & ~(u64)SHF_GNU_RETAIN;
// .init_array is usually writable. We don't want to create multiple
// .init_array output sections, so make it always writable.
// So is .fini_array.
if (type == SHT_INIT_ARRAY || type == SHT_FINI_ARRAY)
flags |= SHF_WRITE;
return flags;
};
return {name, type, get_flags()};
}
// Create output sections for input sections.
@@ -492,7 +492,7 @@ template <typename E>
void create_output_sections(Context<E> &ctx) {
Timer t(ctx, "create_output_sections");
struct Hash {
struct Cmp {
size_t operator()(const OutputSectionKey &k) const {
u64 h = hash_string(k.name);
h = combine_hash(h, std::hash<u64>{}(k.type));
@@ -501,53 +501,34 @@ void create_output_sections(Context<E> &ctx) {
}
};
std::unordered_map<OutputSectionKey, OutputSection<E> *, Hash> map;
std::unordered_map<OutputSectionKey, OutputSection<E> *, Cmp> 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);
if (auto it = cache.find(key); it != cache.end()) {
isec->output_section = it->second;
continue;
{
std::shared_lock lock(mu);
auto it = map.find(key);
if (it != map.end()) {
isec->output_section = it->second;
continue;
}
}
auto get_or_insert = [&] {
{
std::shared_lock lock(mu);
if (auto it = map.find(key); it != map.end())
return it->second;
}
std::unique_ptr<OutputSection<E>> osec =
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
std::unique_lock lock(mu);
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});
auto [it, inserted] = map.insert({key, osec.get()});
isec->output_section = it->second;
if (inserted)
ctx.osec_pool.emplace_back(std::move(osec));
}
});
@@ -594,7 +575,7 @@ void create_internal_file(Context<E> &ctx) {
obj->is_alive = true;
obj->priority = 1;
auto add = [&](Symbol<E> *sym) {
auto add = [&](Symbol<E> *sym, u32 visibility = STV_DEFAULT) {
obj->symbols.push_back(sym);
// An actual value will be set to a linker-synthesized symbol by
@@ -608,7 +589,7 @@ void create_internal_file(Context<E> &ctx) {
esym.st_type = STT_NOTYPE;
esym.st_shndx = SHN_ABS;
esym.st_bind = STB_GLOBAL;
esym.st_visibility = STV_DEFAULT;
esym.st_visibility = visibility;
ctx.internal_esyms.push_back(esym);
};
@@ -641,13 +622,33 @@ void create_internal_file(Context<E> &ctx) {
}
}
// Add _GLOBAL_OFFSET_TABLE_
//
// We watn to do this before resolve_symbols() because on HP/PA, the
// symbol may also be defined by shared libraries. We always want to
// make the undefined symbol to it to refer to the synthesized one,
// so the symbol needs to be available before resolve_symbols().
{
Symbol<E> *sym = get_symbol(ctx, "_GLOBAL_OFFSET_TABLE_");
ctx._GLOBAL_OFFSET_TABLE_ = sym;
if constexpr (is_hppa<E>) {
add(sym);
sym->is_exported = true;
if (ctx.arg.shared)
sym->is_imported = true;
} else {
add(sym, STV_HIDDEN);
}
}
// Add --section-order symbols
for (SectionOrder &ord : ctx.arg.section_order)
if (ord.type == SectionOrder::SYMBOL)
add(get_symbol(ctx, ord.name));
obj->elf_syms = ctx.internal_esyms;
obj->has_symver.resize(ctx.internal_esyms.size() - 1);
obj->symvers.resize(ctx.internal_esyms.size() - 1);
}
template <typename E>
@@ -660,7 +661,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};
@@ -704,7 +705,6 @@ void add_synthetic_symbols(Context<E> &ctx) {
ctx.__preinit_array_start = add("__preinit_array_start");
ctx.__preinit_array_end = add("__preinit_array_end");
ctx._DYNAMIC = add("_DYNAMIC");
ctx._GLOBAL_OFFSET_TABLE_ = add("_GLOBAL_OFFSET_TABLE_");
ctx._PROCEDURE_LINKAGE_TABLE_ = add("_PROCEDURE_LINKAGE_TABLE_");
ctx.__bss_start = add("__bss_start");
ctx._end = add("_end");
@@ -747,8 +747,8 @@ 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");
if constexpr (is_hppa<E>)
ctx.extra.global = add("$global$");
for (Chunk<E> *chunk : ctx.chunks) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
@@ -763,7 +763,7 @@ void add_synthetic_symbols(Context<E> &ctx) {
}
obj.elf_syms = ctx.internal_esyms;
obj.has_symver.resize(ctx.internal_esyms.size() - 1);
obj.symvers.resize(ctx.internal_esyms.size() - 1);
obj.resolve_symbols(ctx);
@@ -800,20 +800,19 @@ 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(),
[&](std::pair<u32, u32> kv) {
return kv.first == GNU_PROPERTY_X86_FEATURE_1_AND &&
(kv.second & feature);
});
[&](auto 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: "
@@ -839,14 +838,37 @@ 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())
@@ -975,6 +997,17 @@ template <typename E>
void check_symbol_types(Context<E> &ctx) {
Timer t(ctx, "check_symbol_types");
auto canonicalize = [](u32 ty) -> u32 {
if (ty == STT_GNU_IFUNC)
return STT_FUNC;
if constexpr (is_hppa<E>)
if (ty == STT_HPPA_MILLI)
return STT_FUNC;
return ty;
};
auto check = [&](InputFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
@@ -983,13 +1016,8 @@ void check_symbol_types(Context<E> &ctx) {
if (!sym.file)
continue;
u32 x = sym.esym().st_type;
if (x == STT_GNU_IFUNC)
x = STT_FUNC;
u32 y = esym.st_type;
if (y == STT_GNU_IFUNC)
y = STT_FUNC;
u32 x = canonicalize(sym.esym().st_type);
u32 y = canonicalize(esym.st_type);
if (x != STT_NOTYPE && y != STT_NOTYPE && x != y)
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
@@ -1024,12 +1052,8 @@ 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 map[a] < map[b];
return get_priority(a) < get_priority(b);
});
}
}
@@ -1067,12 +1091,8 @@ 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 map[a] < map[b];
return get_priority(a) < get_priority(b);
});
}
}
@@ -1231,95 +1251,11 @@ 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) {
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);
}
file->claim_unresolved_symbols(ctx);
});
}
@@ -1352,9 +1288,17 @@ 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) {
sym->add_aux(ctx);
add_aux(sym);
if (sym->is_imported || sym->is_exported)
ctx.dynsym->add_symbol(ctx, sym);
@@ -1369,7 +1313,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 to each other, resulting in an
// .plt.got and .got would refer each other, resulting in an
// infinite loop at runtime.
ctx.plt->add_symbol(ctx, sym);
} else if (sym->flags & NEEDS_PLT) {
@@ -1389,14 +1333,35 @@ void scan_relocations(Context<E> &ctx) {
ctx.got->add_tlsdesc_symbol(ctx, sym);
if (sym->flags & NEEDS_COPYREL) {
if (((SharedFile<E> *)sym->file)->is_readonly(sym))
assert(sym->file->is_dso);
SharedFile<E> *file = (SharedFile<E> *)sym->file;
sym->copyrel_readonly = file->is_readonly(ctx, sym);
if (sym->copyrel_readonly)
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>)
if (sym->flags & NEEDS_PPC_OPD)
if constexpr (is_ppc64v1<E> || is_hppa<E>)
if (sym->flags & NEEDS_OPD)
ctx.extra.opd->add_symbol(ctx, sym);
sym->flags = 0;
@@ -1412,36 +1377,6 @@ 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");
@@ -1482,10 +1417,6 @@ 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>)
@@ -1602,23 +1533,17 @@ 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 = file->first_global; i < file->elf_syms.size(); i++) {
for (i64 i = 0; i < file->elf_syms.size() - file->first_global; i++) {
// Match VERSION part of symbol foo@VERSION with version definitions.
if (!file->has_symver.get(i - file->first_global))
// The symbols' VERSION parts are in file->symvers.
if (!file->symvers[i])
continue;
Symbol<E> *sym = file->symbols[i];
Symbol<E> *sym = file->symbols[i + file->first_global];
if (sym->file != file)
continue;
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;
}
std::string_view ver = file->symvers[i];
bool is_default = false;
if (ver.starts_with('@')) {
@@ -1642,8 +1567,7 @@ 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->has_symver.get(sym2->sym_idx - file->first_global))
if (sym2->file == file && !file->symvers[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;
@@ -1661,8 +1585,7 @@ 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;
}
@@ -1751,7 +1674,6 @@ void clear_padding(Context<E> &ctx) {
// .got
// .toc
// .alpha_got
// .mips_got
// <writable RELRO bss>
// .relro_padding
// <writable non-RELRO data>
@@ -1837,8 +1759,6 @@ 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;
@@ -2268,18 +2188,17 @@ i64 set_osec_offsets(Context<E> &ctx) {
else
set_virtual_addresses_by_order(ctx);
// Assigning new offsets may change the contents and the length
// of the program header, so repeat it until converge.
i64 fileoff = set_file_offsets(ctx);
if (ctx.phdr) {
i64 sz = ctx.phdr->shdr.sh_size;
ctx.phdr->update_shdr(ctx);
if (sz != ctx.phdr->shdr.sh_size)
continue;
}
// Assigning new offsets may change the contents and the length
// of the program header, so repeat it until converge.
if (!ctx.phdr)
return fileoff;
return fileoff;
i64 sz = ctx.phdr->shdr.sh_size;
ctx.phdr->update_shdr(ctx);
if (sz == ctx.phdr->shdr.sh_size)
return fileoff;
}
}
@@ -2308,10 +2227,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
};
auto stop = [](Symbol<E> *sym, auto &chunk) {
auto stop = [](Symbol<E> *sym, auto &chunk, i64 bias = 0) {
if (sym && chunk) {
sym->set_output_section(chunk);
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size;
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size + bias;
}
};
@@ -2401,13 +2320,19 @@ void fix_synthetic_symbols(Context<E> &ctx) {
// _DYNAMIC
start(ctx._DYNAMIC, ctx.dynamic);
// _GLOBAL_OFFSET_TABLE_. I don't know why, but for the sake of
// compatibility with existing code, it must be set to the beginning of
// .got.plt instead of .got only on i386 and x86-64.
if constexpr (is_x86<E>)
// _GLOBAL_OFFSET_TABLE_
if constexpr (is_x86<E>) {
// I don't know why, but for the sake of compatibility with existing
// code, it must be set to the beginning of .got.plt instead of .got
// only on i386 and x86-64.
start(ctx._GLOBAL_OFFSET_TABLE_, ctx.gotplt);
else
} else if constexpr (is_hppa<E>) {
i64 offset = ctx.extra.opd->shdr.sh_size - sizeof(Word<E>) * 2;
start(ctx._GLOBAL_OFFSET_TABLE_, ctx.extra.opd, offset);
start(ctx.extra.global, ctx.extra.opd, offset);
} else {
start(ctx._GLOBAL_OFFSET_TABLE_, ctx.got);
}
// _PROCEDURE_LINKAGE_TABLE_. We need this on SPARC.
start(ctx._PROCEDURE_LINKAGE_TABLE_, ctx.plt);
@@ -2454,10 +2379,6 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
}
// MIPS' _gp symbol.
if (ctx._gp)
start(ctx._gp, ctx.got, 0x7ff0);
// __start_ and __stop_ symbols
for (Chunk<E> *chunk : sections) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
@@ -2635,7 +2556,6 @@ 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> &);
@@ -2643,6 +2563,7 @@ 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> &);
@@ -2653,7 +2574,6 @@ 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> &);
+62 -88
View File
@@ -20,8 +20,6 @@
// 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>
@@ -29,30 +27,37 @@
namespace mold::elf {
// Returns a branch reach in bytes for a given target.
// 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
template <typename E>
static consteval i64 max_distance() {
// ARM64's branch has 26 bits immediate. The immediate is padded with
// implicit two-bit zeros because all instructions are 4 bytes aligned
// and therefore the least two bits are always zero. So the branch
// operand is effectively 28 bits long. That means the branch range is
// [-2^27, 2^27) or PC ± 128 MiB.
if (is_arm64<E>)
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;
if constexpr (is_arm64<E>) {
return 1LL << 27;
} else if constexpr (is_arm32<E>) {
return 1LL << 24;
} else if constexpr (is_ppc<E>) {
return 1LL << 25;
} else {
static_assert(is_hppa<E>);
return 1LL << 18;
}
}
// We create thunks for each 12.8/1.6/3.2 MiB code block for
@@ -72,13 +77,14 @@ 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_PLT32;
ty == R_ARM_CALL || ty == R_ARM_THM_CALL;
} else if constexpr (is_ppc32<E>) {
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
} else if constexpr (is_ppc64<E>) {
return ty == R_PPC64_REL24;
} else {
static_assert(is_ppc64<E>);
return ty == R_PPC64_REL24 || ty == R_PPC64_REL24_NOTOC;
static_assert(is_hppa<E>);
return ty == R_PARISC_PCREL17F || ty == R_PARISC_PCREL32;
}
}
@@ -108,21 +114,14 @@ 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_PLT32 && is_thumb))
(rel.r_type == R_ARM_JUMP24 && 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())
// (TODO)
if constexpr (is_hppa<E>)
if (sym.has_opd(ctx))
return false;
if (rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().uses_toc())
return false;
}
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
@@ -202,31 +201,14 @@ 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 at D.
// Move D foward as far as we can jump from B to anywhere in a thunk after 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>()) {
@@ -246,53 +228,48 @@ 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 < 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++]);
while (a < osec.thunks.size() &&
osec.thunks[a]->offset + max_distance<E>() < c_offset)
reset_thunk(*osec.thunks[a++]);
// Create a thunk for input sections between B and C and place it at D.
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
RangeExtensionThunk<E> *thunk =
new RangeExtensionThunk<E>(osec, osec.thunks.size(), offset);
osec.thunks.emplace_back(thunk);
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;
// 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; i < thunk->symbols.size(); i++) {
Symbol<E> &sym = *thunk->symbols[i];
sym.extra.thunk_idx = thunk->thunk_idx;
sym.extra.thunk_sym_idx = i;
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
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;
});
@@ -300,18 +277,15 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
b = c;
}
while (thunk_idx < osec.thunks.size())
reset_thunk(*osec.thunks[thunk_idx++]);
while (a < osec.thunks.size())
reset_thunk(*osec.thunks[a++]);
osec.shdr.sh_size = offset;
}
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || MOLD_HPPA32
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
View File
@@ -1,212 +0,0 @@
// 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
+3 -5
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <filesystem>
#include <sys/stat.h>
@@ -7,10 +7,8 @@ namespace mold {
std::string get_realpath(std::string_view path) {
std::error_code ec;
std::filesystem::path link = std::filesystem::read_symlink(path, ec);
if (ec)
return std::string(path);
return (filepath(path) / ".." / link).lexically_normal().string();
std::string ret = std::filesystem::canonical(path, ec).string();
return ec ? std::string(path) : ret;
}
// Removes redundant '/..' or '/.' from a given path.
+22 -24
View File
@@ -1,7 +1,7 @@
#pragma once
#include "common.h"
#include "../elf/elf.h"
#include "mold.h"
#include "elf/elf.h"
namespace mold {
@@ -23,15 +23,15 @@ enum class FileType {
LLVM_BITCODE,
};
template <typename MappedFile>
bool is_text_file(MappedFile *mf) {
template <typename C>
bool is_text_file(MappedFile<C> *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 Context, typename MappedFile>
inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
template <typename E, typename C>
inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
using namespace mold::elf;
const char *data = mf->get_contents().data();
@@ -45,23 +45,21 @@ inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
? sh_begin->sh_link : ehdr.e_shstrndx;
for (ElfShdr<E> &sec : shdrs) {
// 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."))
// 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."))
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>)};
@@ -85,8 +83,8 @@ inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
return false;
}
template <typename Context, typename MappedFile>
FileType get_file_type(Context &ctx, MappedFile *mf) {
template <typename C>
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
using namespace elf;
std::string_view data = mf->get_contents();
@@ -102,10 +100,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<I386>(ctx, mf))
if (is_gcc_lto_obj<I386>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<X86_64>(ctx, mf))
if (is_gcc_lto_obj<X86_64>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
@@ -118,10 +116,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<M68K>(ctx, mf))
if (is_gcc_lto_obj<M68K>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<SPARC64>(ctx, mf))
if (is_gcc_lto_obj<SPARC64>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <cstring>
+2 -2
View File
@@ -4,7 +4,7 @@
// For more info, read
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
#include "common.h"
#include "mold.h"
#include <cmath>
@@ -13,7 +13,7 @@ namespace mold {
i64 HyperLogLog::get_cardinality() const {
double z = 0;
for (i64 val : buckets)
z += std::ldexp(1.0, -val);
z += pow(2, -val);
return ALPHA * NBUCKETS * NBUCKETS / z;
}
+2 -7
View File
@@ -1,6 +1,6 @@
#!/bin/sh
#!/bin/bash
set -e
. /etc/os-release
source /etc/os-release
set -x
@@ -40,11 +40,6 @@ 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
View File
File renamed without changes.
+7 -20
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include "config.h"
#include <cstring>
@@ -110,26 +110,14 @@ static void sighandler(int signo, siginfo_t *info, void *ucontext) {
static std::mutex mu;
std::scoped_lock lock{mu};
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";
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";
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
break;
}
}
cleanup();
_exit(1);
}
@@ -139,7 +127,6 @@ 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);
@@ -161,7 +148,7 @@ int main(int argc, char **argv) {
#if MOLD_IS_SOLD
std::string cmd = mold::filepath(argv[0]).filename().string();
if (cmd == "ld64" || cmd.starts_with("ld64."))
if (cmd == "ld64" || cmd == "ld64.mold")
return mold::macho::main(argc, argv);
#endif
+70 -138
View File
@@ -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,23 +40,6 @@
# 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());
}
@@ -77,7 +60,7 @@ namespace mold {
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
template <typename Context> class OutputFile;
template <typename C> class OutputFile;
inline char *output_tmpfile;
inline thread_local bool opt_demangle;
@@ -103,44 +86,41 @@ static u64 combine_hash(u64 a, u64 b) {
// Error output
//
template <typename Context>
template <typename C>
class SyncOut {
public:
SyncOut(Context &ctx, std::ostream *out = &std::cout) : out(out) {
SyncOut(C &ctx, std::ostream &out = std::cout) : out(out) {
opt_demangle = ctx.arg.demangle;
}
~SyncOut() {
if (out) {
std::scoped_lock lock(mu);
*out << ss.str() << "\n";
}
std::scoped_lock lock(mu);
out << ss.str() << "\n";
}
template <class T> SyncOut &operator<<(T &&val) {
if (out)
ss << std::forward<T>(val);
ss << std::forward<T>(val);
return *this;
}
static inline std::mutex mu;
private:
std::ostream *out;
std::ostream &out;
std::stringstream ss;
};
template <typename Context>
static std::string add_color(Context &ctx, std::string msg) {
template <typename C>
static std::string add_color(C &ctx, std::string msg) {
if (ctx.arg.color_diagnostics)
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
return "mold: " + msg + ": ";
}
template <typename Context>
template <typename C>
class Fatal {
public:
Fatal(Context &ctx) : out(ctx, &std::cerr) {
Fatal(C &ctx) : out(ctx, std::cerr) {
out << add_color(ctx, "fatal");
}
@@ -156,13 +136,13 @@ public:
}
private:
SyncOut<Context> out;
SyncOut<C> out;
};
template <typename Context>
template <typename C>
class Error {
public:
Error(Context &ctx) : out(ctx, &std::cerr) {
Error(C &ctx) : out(ctx, std::cerr) {
if (ctx.arg.noinhibit_exec) {
out << add_color(ctx, "warning");
} else {
@@ -177,14 +157,13 @@ public:
}
private:
SyncOut<Context> out;
SyncOut<C> out;
};
template <typename Context>
template <typename C>
class Warn {
public:
Warn(Context &ctx)
: out(ctx, ctx.arg.suppress_warnings ? nullptr : &std::cerr) {
Warn(C &ctx) : out(ctx, std::cerr) {
if (ctx.arg.fatal_warnings) {
out << add_color(ctx, "error");
ctx.has_error = true;
@@ -199,64 +178,7 @@ public:
}
private:
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;
SyncOut<C> out;
};
//
@@ -316,6 +238,17 @@ 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());
@@ -323,12 +256,7 @@ 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;
@@ -428,8 +356,8 @@ inline i64 uleb_size(u64 val) {
return 9;
}
template <typename Context>
std::string_view save_string(Context &ctx, const std::string &str) {
template <typename C>
std::string_view save_string(C &ctx, const std::string &str) {
u8 *buf = new u8[str.size() + 1];
memcpy(buf, str.data(), str.size());
buf[str.size()] = '\0';
@@ -477,9 +405,9 @@ public:
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
this->nbuckets = nbuckets;
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(char *));
key_sizes = (u32 *)malloc(nbuckets * sizeof(u32));
values = (T *)malloc(nbuckets * sizeof(T));
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]));
}
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
@@ -499,7 +427,7 @@ public:
if (ptr == nullptr) {
if (!keys[idx].compare_exchange_weak(ptr, marker,
std::memory_order_acquire))
std::memory_order_acq_rel))
continue;
new (values + idx) T(val);
key_sizes[idx] = key.size();
@@ -520,7 +448,7 @@ public:
return {nullptr, false};
}
const char *get_key(i64 idx) {
bool has_key(i64 idx) {
return keys[idx].load(std::memory_order_relaxed);
}
@@ -529,6 +457,7 @@ public:
static constexpr i64 MAX_RETRY = 128;
i64 nbuckets = 0;
std::atomic<const char *> *keys = nullptr;
u32 *key_sizes = nullptr;
T *values = nullptr;
@@ -541,8 +470,6 @@ private:
#endif
}
private:
std::atomic<const char *> *keys = nullptr;
static constexpr const char *marker = "marker";
};
@@ -550,13 +477,13 @@ private:
// output-file.h
//
template <typename Context>
template <typename C>
class OutputFile {
public:
static std::unique_ptr<OutputFile<Context>>
open(Context &ctx, std::string path, i64 filesize, i64 perm);
static std::unique_ptr<OutputFile<C>>
open(C &ctx, std::string path, i64 filesize, i64 perm);
virtual void close(Context &ctx) = 0;
virtual void close(C &ctx) = 0;
virtual ~OutputFile() = default;
u8 *buf = nullptr;
@@ -719,13 +646,13 @@ public:
}
Counter &operator++(int) {
if (enabled) [[unlikely]]
if (enabled)
values.local()++;
return *this;
}
Counter &operator+=(int delta) {
if (enabled) [[unlikely]]
if (enabled)
values.local() += delta;
return *this;
}
@@ -762,10 +689,10 @@ struct TimerRecord {
void
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
template <typename Context>
template <typename C>
class Timer {
public:
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
Timer(C &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));
}
@@ -816,16 +743,16 @@ private:
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
template <typename Context>
template <typename C>
class MappedFile {
public:
static MappedFile *open(Context &ctx, std::string path);
static MappedFile *must_open(Context &ctx, std::string path);
static MappedFile *open(C &ctx, std::string path);
static MappedFile *must_open(C &ctx, std::string path);
~MappedFile() { unmap(); }
void unmap();
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size);
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
std::string_view get_contents() {
return std::string_view((char *)data, size);
@@ -855,6 +782,7 @@ public:
std::string name;
u8 *data = nullptr;
i64 size = 0;
i64 mtime = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
@@ -864,8 +792,8 @@ public:
#endif
};
template <typename Context>
MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
template <typename C>
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
@@ -876,11 +804,8 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
fd = ::open(path.c_str(), O_RDONLY);
#endif
if (fd == -1) {
if (errno != ENOENT)
Fatal(ctx) << "opening " << path << " failed: " << errno_string();
if (fd == -1)
return nullptr;
}
struct stat st;
if (fstat(fd, &st) == -1)
@@ -892,6 +817,14 @@ MappedFile<Context> *MappedFile<Context>::open(Context &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),
@@ -915,18 +848,17 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
return mf;
}
template <typename Context>
MappedFile<Context> *
MappedFile<Context>::must_open(Context &ctx, std::string path) {
template <typename C>
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
if (MappedFile *mf = MappedFile::open(ctx, path))
return mf;
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
}
template <typename Context>
MappedFile<Context> *
MappedFile<Context>::slice(Context &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile;
template <typename C>
MappedFile<C> *
MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile<C>;
mf->name = name;
mf->data = data + start;
mf->size = size;
@@ -936,8 +868,8 @@ MappedFile<Context>::slice(Context &ctx, std::string name, u64 start, u64 size)
return mf;
}
template <typename Context>
void MappedFile<Context>::unmap() {
template <typename C>
void MappedFile<C>::unmap() {
if (size == 0 || parent || !data)
return;
+1 -1
View File
@@ -17,7 +17,7 @@
// slow, but complex patterns are rare in practice, so it should be
// OK.
#include "common.h"
#include "mold.h"
#include <queue>
#include <regex>
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <fcntl.h>
#include <filesystem>
@@ -14,9 +14,9 @@ inline u32 get_umask() {
return orig_umask;
}
template <typename Context>
template <typename C>
static std::pair<i64, char *>
open_or_create_file(Context &ctx, std::string path, i64 filesize, i64 perm) {
open_or_create_file(C &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(Context &ctx, std::string path, i64 filesize, i64 perm) {
return {fd, path2};
}
template <typename Context>
class MemoryMappedOutputFile : public OutputFile<Context> {
template <typename C>
class MemoryMappedOutputFile : public OutputFile<C> {
public:
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, true) {
MemoryMappedOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(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(Context &ctx) override {
void close(C &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
@@ -92,18 +92,18 @@ private:
int fd2 = -1;
};
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
template <typename C>
class MallocOutputFile : public OutputFile<C> {
public:
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), perm(perm) {
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, false), perm(perm) {
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << "mmap failed: " << errno_string();
}
void close(Context &ctx) override {
void close(C &ctx) override {
Timer t(ctx, "close_file");
if (this->path == "-") {
@@ -125,9 +125,9 @@ private:
i64 perm;
};
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
template <typename C>
std::unique_ptr<OutputFile<C>>
OutputFile<C>::open(C &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<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm
is_special = true;
}
OutputFile<Context> *file;
OutputFile<C> *file;
if (is_special)
file = new MallocOutputFile(ctx, path, filesize, perm);
file = new MallocOutputFile<C>(ctx, path, filesize, perm);
else
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
#ifdef MADV_HUGEPAGE
// Enable transparent huge page for an output memory-mapped file.
@@ -163,7 +163,7 @@ OutputFile<Context>::open(Context &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>(file);
return std::unique_ptr<OutputFile<C>>(file);
}
} // namespace mold
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <fcntl.h>
#include <filesystem>
@@ -6,17 +6,17 @@
namespace mold {
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
template <typename C>
class MallocOutputFile : public OutputFile<C> {
public:
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), perm(perm) {
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, false), perm(perm) {
this->buf = (u8 *)malloc(filesize);
if (!this->buf)
Fatal(ctx) << "malloc failed";
}
void close(Context &ctx) override {
void close(C &ctx) override {
Timer t(ctx, "close_file");
if (this->path == "-") {
@@ -39,19 +39,19 @@ private:
i64 perm;
};
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
template <typename C>
std::unique_ptr<OutputFile<C>>
OutputFile<C>::open(C &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<Context> *file = new MallocOutputFile(ctx, path, filesize, perm);
OutputFile<C> *file = new MallocOutputFile<C>(ctx, path, filesize, perm);
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile<Context>>(file);
return std::unique_ptr<OutputFile<C>>(file);
}
} // namespace mold
File renamed without changes.
+6 -6
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.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 * 1'000'000'000 + t.tv_nsec;
return (i64)t.tv_sec * 1000000000 + 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 * 1'000'000'000 + t.tv_usec * 1'000;
return (i64)t.tv_sec * 1000000000 + t.tv_usec * 1000;
};
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 / 1'000'000'000),
((double)rec.sys / 1'000'000'000),
(((double)rec.end - rec.start) / 1'000'000'000),
((double)rec.user / 1000000000),
((double)rec.sys / 1000000000),
(((double)rec.end - rec.start) / 1000000000),
std::string(indent * 2, ' ').c_str(),
rec.name.c_str());
View File
File renamed without changes.
+2 -5
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
namespace mold {
@@ -24,10 +24,7 @@ struct UstarHeader {
int sum = 0;
for (i64 i = 0; i < sizeof(*this); i++)
sum += ((u8 *)this)[i];
// We need to convince the compiler that sum isn't too big to silence
// -Werror=format-truncation.
ASSUME(sum < 01'000'000);
assert(sum < 01000000);
snprintf(checksum, sizeof(checksum), "%06o", sum);
}
+2 -11
View File
@@ -1,16 +1,10 @@
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 "amd64")
set(HOST x86_64)
elseif(${HOST} MATCHES "arm.*")
if(${HOST} MATCHES "arm.*")
set(HOST arm)
elseif(${HOST} STREQUAL "ppc64")
set(HOST powerpc64)
endif()
if(${TRIPLE} MATCHES "${HOST}-.*")
@@ -63,11 +57,8 @@ add_target(s390x-linux-gnu)
add_target(m68k-linux-gnu)
add_target(sh4-linux-gnu)
add_target(alpha-linux-gnu)
add_target(hppa-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()
-2
View File
@@ -6,8 +6,6 @@
[ $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
-24
View File
@@ -1,24 +0,0 @@
#!/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
+11 -12
View File
@@ -5,27 +5,26 @@ cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
__attribute__((weak)) int fn1();
int main() {
if (fn1)
fn1();
fn1();
}
EOF
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
cat <<EOF | $CC -o $t/b.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 -
cat <<EOF | $CC -o $t/c.so -shared -fPIC -Wl,-soname,libbar.so -xc -
int fn2() { return 42; }
EOF
$CC -o $t/exe1 $t/a.o -Wl,-no-as-needed -L$t -lbar -lfoo
$CC -B. -o $t/exe $t/a.o -Wl,-no-as-needed $t/b.so $t/c.so
readelf --dynamic $t/exe1 > $t/log1
grep -Fq 'Shared library: [libfoo.so]' $t/log1
grep -Fq 'Shared library: [libbar.so]' $t/log1
readelf --dynamic $t/exe > $t/readelf
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
grep -Fq 'Shared library: [libbar.so]' $t/readelf
$CC -o $t/exe2 $t/a.o -Wl,-as-needed -L$t -lbar -lfoo
$CC -B. -o $t/exe $t/a.o -Wl,-as-needed $t/b.so $t/c.so
readelf --dynamic $t/exe2 > $t/log2
! grep -Fq 'Shared library: [libfoo.so]' $t/log2 || false
! grep -Fq 'Shared library: [libbar.so]' $t/log2 || false
readelf --dynamic $t/exe > $t/readelf
! grep -Fq 'Shared library: [libfoo.so]' $t/readelf || false
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
+10 -9
View File
@@ -1,22 +1,22 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -shared -fPIC -o $t/libfoo.so -Wl,--soname,libfoo.so -xc -
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
int foo() { return 3; }
EOF
cat <<EOF | $CC -shared -fPIC -o $t/libbar.so -Wl,--soname,libbar.so -xc -
cat <<EOF | $CC -shared -fPIC -o $t/b.so -xc -
int bar() { return 3; }
EOF
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
int foo();
int baz() { return foo(); }
EOF
$CC -B. -shared -o $t/libbaz.so -Wl,--soname,libbaz.so -L$t $t/a.o -lfoo
$CC -shared -o $t/c.so $t/c.o $t/a.so
cat <<EOF | $CC -c -o $t/b.o -xc -
cat <<EOF | $CC -c -o $t/d.o -xc -
#include <stdio.h>
int baz();
int main() {
@@ -24,9 +24,10 @@ int main() {
}
EOF
$CC -B. -o $t/exe $t/b.o -L$t -Wl,--as-needed -lbaz -lbar -lfoo
$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$'
readelf --dynamic $t/exe > $t/log
grep -q libbaz $t/log || false
! grep -q libbar $t/log || false
grep -q libfoo $t/log || false
! grep -q /a.so $t/log || false
grep -q /c.so $t/log || false
! grep -q /b.so $t/log || false
+8 -1
View File
@@ -5,6 +5,13 @@
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
# This test does not pass even with GNU ld, which means function pointer
# equality is not guaranteed on HP/PA. I'm not sure if this is an bug in
# QEMU or this test really fails on a real HP/PA machine, but it's quite
# surprising. Many programs could silently break if function pointer
# equality is not guaranteed.
[ $MACHINE = hppa ] && skip
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
int foo = 4;
@@ -19,7 +26,7 @@ EOF
$CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic -Wl,-Bno-symbolic
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
cat <<EOF | $CC -B. -c -o $t/c.o -xc - -fno-PIE
#include <stdio.h>
extern int foo;
+3
View File
@@ -5,6 +5,9 @@
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
# Pointer equality does not seem to be guaranteed on HP/PA
[ $MACHINE = hppa ] && skip
cat <<EOF | $CC -o $t/a.so -fPIC -shared -xc -
void *foo() {
return foo;
+17 -27
View File
@@ -16,33 +16,14 @@ canonical_name() {
HOST=$(canonical_name $(uname -m))
# Set tool names
if [ "$TRIPLE" = "" ]; then
MACHINE=$HOST
TESTDIR=out/test/elf/$HOST
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP=objdump
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
if [ -n "$TRIPLE" ]; then
MACHINE=$(canonical_name $(echo $TRIPLE | sed 's/-.*//'))
TESTDIR=out/test/elf/$MACHINE
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}"
@@ -51,6 +32,15 @@ else
OBJCOPY="$TRIPLE-objcopy"
STRIP="$TRIPLE-strip"
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
else
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP=objdump
OBJCOPY=objcopy
STRIP=strip
QEMU=
fi
# Common functions
@@ -87,5 +77,5 @@ trap on_exit EXIT
# Print out the startup message
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=$TESTDIR/$testname
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
-4
View File
@@ -3,10 +3,6 @@
[ $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();
-26
View File
@@ -1,26 +0,0 @@
#!/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'
+22 -15
View File
@@ -3,11 +3,8 @@
[ $MACHINE = sh4 ] && skip
static=
test_cflags -static && static=-static
# I don't know why, but we need -pthread on m68k
static="$static -pthread"
static=1
test_cflags -static || static=0
cat <<EOF > $t/a.cc
int main() {
@@ -23,11 +20,15 @@ EOF
$CXX -c -o $t/b.o $t/a.cc -fPIC
$CXX -c -o $t/c.o $t/a.cc -fno-PIC
$CXX -B. -o $t/exe1 $t/b.o $static
$QEMU $t/exe1
if [ $static = 1 ]; then
$CXX -B. -o $t/exe1 $t/b.o -static
$QEMU $t/exe1
fi
$CXX -B. -o $t/exe2 $t/c.o $static
$QEMU $t/exe2
if [ $static = 1 ]; then
$CXX -B. -o $t/exe2 $t/c.o -static
$QEMU $t/exe2
fi
$CXX -B. -o $t/exe3 $t/b.o -pie
$QEMU $t/exe3
@@ -38,14 +39,18 @@ $QEMU $t/exe4
$CXX -B. -o $t/exe5 $t/b.o -pie -Wl,--gc-sections
$QEMU $t/exe5
$CXX -B. -o $t/exe6 $t/c.o $static -Wl,--gc-sections
$QEMU $t/exe6
if [ $static = 1 ]; then
$CXX -B. -o $t/exe6 $t/c.o -static -Wl,--gc-sections
$QEMU $t/exe6
fi
if [ $MACHINE = x86_64 ]; then
$CXX -c -o $t/d.o $t/a.cc -mcmodel=large -fPIC
$CXX -B. -o $t/exe7 $t/d.o $static
$QEMU $t/exe7
if [ $static = 1 ]; then
$CXX -B. -o $t/exe7 $t/d.o -static
$QEMU $t/exe7
fi
$CXX -B. -o $t/exe8 $t/d.o -pie
$QEMU $t/exe8
@@ -54,8 +59,10 @@ fi
if [ $MACHINE = x86_64 -o $MACHINE = aarch64 ]; then
$CXX -c -o $t/e.o $t/a.cc -mcmodel=large -fno-PIC
$CXX -B. -o $t/exe9 $t/e.o $static
$QEMU $t/exe9
if [ $static = 1 ]; then
$CXX -B. -o $t/exe9 $t/e.o -static
$QEMU $t/exe9
fi
$CXX -B. -o $t/exe10 $t/e.o -no-pie
$QEMU $t/exe10
-3
View File
@@ -4,9 +4,6 @@
# .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() {
-80
View File
@@ -1,80 +0,0 @@
#!/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'
-6
View File
@@ -9,9 +9,3 @@ 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
-42
View File
@@ -1,42 +0,0 @@
#!/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'
+2 -5
View File
@@ -11,7 +11,7 @@ int main() {
}
EOF
cat <<EOF | $CC -fPIC -o $t/b.o -c -xc -
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
#include <stdio.h>
__attribute__((ifunc("resolve_foobar")))
@@ -28,8 +28,5 @@ static Func *resolve_foobar(void) {
}
EOF
$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
$CC -B. -o $t/exe $t/a.o $t/b.so
$QEMU $t/exe | grep -q 'Hello world'
-37
View File
@@ -1,37 +0,0 @@
#!/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$'
+2 -2
View File
@@ -1,8 +1,8 @@
#!/bin/bash
. $(dirname $0)/common.inc
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' | \
$CC -c -xassembler -o $t/a.o -
seq 1 100000 | sed 's/.*/int x\0 __attribute__((section(".data.\0")));/g' | \
$CC -c -xc -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o -
#include <stdio.h>
+3 -3
View File
@@ -4,9 +4,9 @@
# OneTBB isn't tsan-clean
nm mold | grep -q '__tsan_init' && skip
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.globl x\0\nx\0: .word 0\n/g' | \
$CC -c -xassembler -o $t/a.o -
seq 1 100000 | sed 's/.*/int x\0 __attribute__((section(".data.\0")));/g' | \
$CC -c -xc -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 'GLOBAL DEFAULT 100000'
readelf -Ws $t/b.o | grep -Fq 'OBJECT GLOBAL DEFAULT 100000'
+3 -1
View File
@@ -10,5 +10,7 @@ void foo();
int main() { foo(); }
EOF
! $CC -B. -o $t/exe $t/a.o $t/b.o -Wl,--print-dependencies > $t/log 2> /dev/null
! $CC -B. -o $t/exe $t/a.o $t/b.o \
-Wl,--print-dependencies=full > $t/log 2> /dev/null
grep -q 'b\.o.*a\.o.*foo$' $t/log
+1
View File
@@ -3,6 +3,7 @@
# 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.
-17
View File
@@ -1,17 +0,0 @@
#!/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'
-20
View File
@@ -1,20 +0,0 @@
#!/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
-21
View File
@@ -1,21 +0,0 @@
#!/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
-22
View File
@@ -1,22 +0,0 @@
#!/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
-19
View File
@@ -1,19 +0,0 @@
#!/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$'
+1 -4
View File
@@ -10,8 +10,5 @@ int main() {
}
EOF
$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
$CC -B. -o $t/exe2 $t/a.o -static-pie
$QEMU $t/exe2 | grep -q 'Hello world'
-13
View File
@@ -1,13 +0,0 @@
#!/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
-14
View File
@@ -1,14 +0,0 @@
#!/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
-38
View File
@@ -1,38 +0,0 @@
#!/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'
-3
View File
@@ -45,7 +45,4 @@ $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
+17
View File
@@ -0,0 +1,17 @@
#!/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
View File
@@ -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/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,-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'
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-no-relax
$QEMU $t/exe | grep -q '3 5 3 5'
-10
View File
@@ -1,10 +0,0 @@
#!/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
Loaded 100 of 346 files, more files were not shown because too many files have changed in this diff. Show more