Compare commits

...
349 Commits
Author SHA1 Message Date
Rui Ueyama d78b748696 Bump mold version to 2.1.0 2023-08-13 13:35:43 +09:00
Rui Ueyama 77968a7fc1 Make --pack-dyn-relocs=relr as a synonym for -z pack-relative-relocs 2023-08-13 10:47:50 +09:00
Rui Ueyama 00a68c5fcf Format 2023-08-12 17:47:51 +09:00
Rui Ueyama 6f9752a1bf Remove a useless timer 2023-08-12 16:58:51 +09:00
Rui Ueyama 6a32828dd8 Improve error messages 2023-08-12 14:36:42 +09:00
Rui Ueyama b5e0d7a7c9 Refactor 2023-08-12 13:41:46 +09:00
Rui Ueyama 3b0b28ff97 Update comments 2023-08-12 13:19:11 +09:00
Rui Ueyama 6ce3730083 Use the same error message for s390x 2023-08-11 17:07:10 +09:00
Rui Ueyama d3e7d88753 Remove error checks
We do not have these error checks for other targets.
We want to be consistent across targets.
2023-08-11 16:55:44 +09:00
Rui Ueyama 9dee80eb4f Simplify 2023-08-11 16:55:16 +09:00
Rui Ueyama eb99c87f75 Use PCALAU12I instead of PCADDU12I in PLT
So that the PLT code is more in line with compiler-generated code.
2023-08-11 16:32:55 +09:00
Rui Ueyama e45f2a8efb Fix CMakeLists.txt 2023-08-11 15:07:09 +09:00
Rui Ueyama 86ab3890d6 Upgrade RV32 toolchain in CI 2023-08-11 15:05:51 +09:00
Rui Ueyama 3b5ccb9342 Implement range extension thunks for LoongArch 2023-08-11 10:54:19 +09:00
Rui Ueyama 9441170d39 Refactor 2023-08-11 01:30:43 +09:00
Rui Ueyama ffb83247f4 Fix CI 2023-08-10 17:59:40 +09:00
Rui Ueyama 13f42071b9 Update comments 2023-08-10 17:49:47 +09:00
Rui Ueyama 032931fa3b Enable LoongArch tests on CI 2023-08-10 16:41:08 +09:00
Rui Ueyama 472b325349 Improve Loongarch support 2023-08-10 15:42:54 +09:00
Rui Ueyama b852d925c2 Revert "Fix TLSLD"
This reverts commit 56cdba50cf.
2023-08-10 13:48:12 +09:00
Rui Ueyama 56cdba50cf Fix TLSLD 2023-08-10 13:25:36 +09:00
Rui Ueyama cfec68e054 Refactor 2023-08-10 11:27:11 +09:00
Rui Ueyama d9f850b032 Fix instruction bitmask 2023-08-10 10:36:06 +09:00
Rui Ueyama 1f012337c0 Refactor 2023-08-10 10:09:49 +09:00
Rui Ueyama 90cf642ca7 Fix high 32 bits computation
I believe the correct expression is this one.
2023-08-10 09:56:06 +09:00
Rui Ueyama d12c48ce63 Refactor 2023-08-10 09:56:06 +09:00
Rui Ueyama 67b9d6cb8d Remove unnecessary function 2023-08-10 08:45:24 +09:00
Rui Ueyama 2e12e635a0 Refactor 2023-08-10 08:30:07 +09:00
Rui Ueyama 1b5631990c Simplify 2023-08-10 08:26:24 +09:00
Rui Ueyama acf846acc0 Assume little endian on LooongArch 2023-08-09 21:30:55 +09:00
Rui Ueyama e1347d8f7d Refactor 2023-08-09 21:29:53 +09:00
Rui Ueyama 6d809b56e6 Refactor 2023-08-09 21:13:00 +09:00
Rui Ueyama 4528507155 Handle obsolete relocations as unknown ones
A "unknown relocation" message should be enough to show that the
relocation is not supported by mold.
2023-08-09 20:57:45 +09:00
Rui Ueyama da07871416 Move LoongArch tests behind MOLD_ENABLE_QEMU_TESTS_LOONGARCH 2023-08-09 20:54:29 +09:00
Rui Ueyama 354d0254ee Update comments 2023-08-09 20:53:39 +09:00
Rui Ueyama b711bf2009 Merge pull request #1081 from MQ-mengqing/main
Add loongarch basic support
2023-08-09 20:53:22 +09:00
Rui Ueyama 94e2a71866 Refactor 2023-08-09 17:16:11 +09:00
Rui Ueyama f467ad1add Create a symbol version dependency to GLIBC_ABI_DT_RELR
Fixes https://github.com/rui314/mold/issues/653
2023-08-09 12:02:06 +09:00
Rui Ueyama f631477a68 Set e_flags and .MIPS.abiflags 2023-08-08 19:20:59 +09:00
Rui Ueyama 56479e724f Improve an error message 2023-08-08 18:14:26 +09:00
Rui Ueyama d752bac24a Do not handle --strip-debug as a synonym for --strip-all
Fixes https://github.com/rui314/mold/issues/1080
2023-08-08 17:46:45 +09:00
Rui Ueyama a50258fdf8 Refactor 2023-08-08 11:37:36 +09:00
Rui Ueyama 710dfd01d6 Fix ODR violation
Fixes https://github.com/rui314/mold/issues/1082
2023-08-07 19:15:31 +09:00
Jinyang He 03b1a1cc68 Add loongarch basic support
link: https://reviews.llvm.org/D138135
link: https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
2023-08-07 10:17:29 +08:00
Rui Ueyama 1c73fd57fc Fix CI 2023-08-06 17:32:36 +09:00
Rui Ueyama 24078a4395 Merge .mips_got sections 2023-08-06 15:09:25 +09:00
Rui Ueyama 270cbf42df Attempt to fix CI 2023-08-06 15:01:42 +09:00
Rui Ueyama ea75dae8ec Refactor 2023-08-06 14:01:11 +09:00
Rui Ueyama 6d290babe2 Refactor 2023-08-06 13:48:29 +09:00
Rui Ueyama 428faa75b2 Create multiple GOT sections for MIPS 2023-08-06 13:36:14 +09:00
Rui Ueyama 829c9a980e Refactor 2023-08-06 13:29:34 +09:00
Rui Ueyama 76af9907fd Fix CI 2023-08-05 18:24:49 +09:00
Rui Ueyama 9893bb331f Update comments 2023-08-05 18:08:34 +09:00
Rui Ueyama 6108e60936 Attempt to fix CI 2023-08-05 17:27:06 +09:00
Rui Ueyama 9e2e376f5a Add MIPS arch string to the -V message 2023-08-05 15:31:04 +09:00
Rui Ueyama 2db8b3fa48 Enable MIPS tests on CI 2023-08-05 15:28:03 +09:00
Rui Ueyama e2c6f0c171 Refactor 2023-08-05 14:46:21 +09:00
Rui Ueyama eba7cdade2 Improve MIPS support
All tests now pass for MIPS64LE and MIPS64BE.
2023-08-05 13:07:37 +09:00
Rui Ueyama ca88e45bf3 Disable tests for MIPS 2023-08-05 09:00:57 +09:00
Rui Ueyama 19776686ec Enable MIPS tests if -DMOLD_ENABLE_QEMU_TESTS_MIPS64=ON
m68k tests are disabled by default because they are broken with
recent updates to Ubuntu 22.04.
2023-08-05 08:42:40 +09:00
Rui Ueyama e0a150a22c Refactor 2023-08-04 19:36:18 +09:00
Rui Ueyama 0df6063d8a Improve MIPS support 2023-08-04 18:48:52 +09:00
Rui Ueyama 5199414933 Refactor 2023-08-03 17:03:52 +09:00
Rui Ueyama 85f1dec81d Improve MIPS support 2023-08-03 16:30:48 +09:00
Rui Ueyama 97c937a56d Refactor 2023-08-02 20:21:11 +09:00
Rui Ueyama f1113f7c76 Refactor 2023-08-02 10:24:26 +09:00
Rui Ueyama 0ced83597f Refactor 2023-08-01 16:27:44 +09:00
Rui Ueyama 1f5fbb808b Do not create dynamic relocations for R_AARCH64_MOVW_UABS_G3 2023-08-01 16:09:40 +09:00
Rui Ueyama 5fb6091fb7 Handle R_RISCV_PCREL_HI20 as a PLT-generating reloc 2023-08-01 15:44:27 +09:00
Rui Ueyama e764557007 Handle R_AARCH64_MOVW_UABS_G3 as a PLT-generating reloc
ARM64's crt1.o may refer to `main` with the following four instructions
instead of with the usual `R_AARCH64_ABS64` relocation:

  18:   d2e00000        movz    x0, #0x0, lsl #48
                        18: R_AARCH64_MOVW_UABS_G3      main
  1c:   f2c00000        movk    x0, #0x0, lsl #32
                        1c: R_AARCH64_MOVW_UABS_G2_NC   main
  20:   f2a00000        movk    x0, #0x0, lsl #16
                        20: R_AARCH64_MOVW_UABS_G1_NC   main
  24:   f2800000        movk    x0, #0x0
                        24: R_AARCH64_MOVW_UABS_G0_NC   main
  28:   d2e00003        movz    x3, #0x0, lsl #48

So we need to create a PLT if a symbol is referred to by this way.

Fixes https://github.com/rui314/mold/issues/1074
2023-08-01 15:02:30 +09:00
Rui Ueyama 961cc10e0d Refactor 2023-08-01 11:04:03 +09:00
Rui Ueyama 49fc7da32e Refactor 2023-08-01 00:39:25 +09:00
Rui Ueyama dd56734fda Fix symbol table for very large section indices 2023-07-31 19:38:04 +09:00
Rui Ueyama 2015347b0c Report an error for incompatible relocation types 2023-07-31 15:44:29 +09:00
Rui Ueyama 4daa9aad36 Do not write special symbol values to .symtab_shndx 2023-07-31 15:10:30 +09:00
Rui Ueyama 084ca55a36 Support -z nosectionheader 2023-07-31 13:50:50 +09:00
Rui Ueyama e06d886252 Report an error if a non-PIC object file is given for an PIC output 2023-07-31 12:44:27 +09:00
Rui Ueyama 9fe3d750cc Fix test
Fixes https://github.com/rui314/mold/issues/1073
2023-07-31 09:26:00 +09:00
Rui Ueyama 13bb00bfa2 Refactor 2023-07-31 09:05:19 +09:00
Rui Ueyama 79a72011b7 Refactor 2023-07-30 23:47:42 +09:00
Rui Ueyama f2b278ce10 Attempt to fix a test for Alt Linux
Fixes https://github.com/rui314/mold/issues/1073
2023-07-30 22:01:31 +09:00
Rui Ueyama ec10452b67 Run tests on ppc64le machines 2023-07-30 13:12:44 +09:00
Rui Ueyama ba3fd65bcc Refactor
Thanks to small string optimization, storing a short string to std::string
is usually more efficient than referring it from std::string_view.
2023-07-30 12:03:58 +09:00
Rui Ueyama f816c45b88 Extend comments 2023-07-30 11:55:06 +09:00
Rui Ueyama ca14cdf9b6 Do not create a PT_RISCV_ATTRIBUTES segment if .riscv.attributes is missing 2023-07-30 11:36:35 +09:00
Rui Ueyama 6d667fad45 Fix CI 2023-07-30 11:25:18 +09:00
Rui Ueyama aa644914fe Merge .riscv.attributes input sections 2023-07-30 00:56:47 +09:00
Rui Ueyama 1cbe4d44ae Fix style 2023-07-29 21:13:50 +09:00
Rui Ueyama 91805d1e26 Add Emerge Tools as our sponsor 2023-07-29 21:07:44 +09:00
Rui Ueyama 9212f2ebd7 Refactor 2023-07-29 12:08:24 +09:00
Rui Ueyama 784fd27e9e Refactor 2023-07-28 17:33:46 +09:00
Rui Ueyama b78f237a68 Fix -Wunused-variable
Fixes https://github.com/rui314/mold/issues/1063
2023-07-28 16:55:18 +09:00
Rui Ueyama 1582b720d5 Weak undefs should not keep DSOs alive
Fixes https://github.com/rui314/mold/issues/1067
2023-07-28 15:12:52 +09:00
Rui Ueyama 710127977e Make --repro output readable by busybox's tar command 2023-07-28 15:12:45 +09:00
Rui Ueyama 8e07496e08 Refactor 2023-07-28 12:55:31 +09:00
Rui Ueyama 163f5db0d1 Simplify 2023-07-28 10:58:28 +09:00
Rui Ueyama be2e1b17cd Skip test if grep is not a "real" grep 2023-07-27 21:48:53 +09:00
Rui Ueyama 5f5e73360b Add build dependencies for Alpine Linux 2023-07-27 21:48:53 +09:00
Rui Ueyama b0b015bc2e Merge pull request #1066 from aloisklink/install-LICENSE.third-party 2023-07-27 14:49:00 +09:00
Rui Ueyama 7f66689e21 Merge pull request #1068 from pcc/adrp 2023-07-27 14:42:12 +09:00
Peter Collingbourne 17a5c3e0ab Support R_AARCH64_ADR_PREL_PG_HI21_NC 2023-07-26 21:33:26 -07:00
Alois Klink 3ed5b8ddf7 Install LICENSE.third-party when using CMake 2023-07-27 01:10:22 +01:00
Rui Ueyama de991b519a Bump mold version to 2.0 2023-07-26 19:40:11 +09:00
Rui Ueyama a066fe25c6 Change the license to the MIT license 2023-07-26 14:49:42 +09:00
Rui Ueyama 6e8a9dfec1 Merge pull request #1058 from andreas-schwab/main 2023-07-24 19:09:17 +09:00
Rui Ueyama 31eeb44d4f Format 2023-07-23 13:23:36 +09:00
Rui Ueyama 44b15cbb35 Update the bundled xxhash to v0.8.2 2023-07-21 19:28:46 +09:00
Rui Ueyama b5b3ffa6fe Merge pull request #1062 from Expander/replace-pow 2023-07-20 19:54:41 +09:00
Rui Ueyama 83a924ddac Fix CI 2023-07-20 17:41:08 +09:00
Rui Ueyama 2e8bd0b7a6 Handle an output relocatable object file with a large number of sections
Fixes https://github.com/rui314/mold/issues/1061
2023-07-20 16:38:54 +09:00
Alexander Voigt b685f3cab7 replace pow(2,x) by ldexp(1.0,x) for performance 2023-07-20 09:28:35 +02:00
Rui Ueyama 2d25a93126 Handle -undefined as a synonym for --undefined instead of -u ndefined
Fixes https://github.com/rui314/mold/discussions/1059
2023-07-18 16:59:44 +09:00
Rui Ueyama a8eb52f61a Format 2023-07-17 16:00:39 +09:00
Rui Ueyama 761c341c19 Improve MIPS64 support 2023-07-17 11:45:55 +09:00
Rui Ueyama 000e395c07 Improve MIPS64 support 2023-07-16 08:01:42 +09:00
Rui Ueyama 4b06f1e9f6 Refactor 2023-07-15 15:01:03 +09:00
Rui Ueyama 29dc0cf53f Initial MIPS64 support
Although incomplete, this implementation is capable of passing the
following tests:

  TRIPLE=mips64el-linux-gnuabi64 bash -x ../test/elf/hello-dynamic.sh
  TRIPLE=mips64-linux-gnuabi64 bash -x ../test/elf/hello-dynamic.sh
2023-07-14 15:11:53 +09:00
Rui Ueyama f8b55680f8 Revert "Merge .alpha_got with .got"
This reverts commit 72671df483.
2023-07-14 14:12:45 +09:00
Rui Ueyama cb08466693 Attempt to fix CI 2023-07-14 10:41:36 +09:00
Rui Ueyama 72671df483 Merge .alpha_got with .got
It's not only Alpha but MIPS relocations also need dynamic symbols
with addends, so we want to use this code not only for Alpha.
2023-07-14 10:06:18 +09:00
Rui Ueyama 83524e3690 Update comments 2023-07-09 11:25:02 +09:00
Rui Ueyama 0125d9c254 Refactor 2023-07-06 17:33:41 +09:00
Andreas Schwab 1ac5fe7c1e Fix support for R_RISCV_SET_ULEB128 and R_RISCV_SUB_ULEB128
Handle allocated sections and write the full value into the location.

Signed-off-by: Andreas Schwab <[email protected]>
2023-07-06 10:23:38 +02:00
Rui Ueyama 4b42f38257 Handle embedded default version strings in LTO symbols
Fixes https://github.com/rui314/mold/issues/1056
2023-07-05 19:07:52 +09:00
Rui Ueyama d2b153c522 Refactor 2023-07-05 18:43:07 +09:00
Rui Ueyama ab126ad1d9 Make sure to import versioned symbols
Undefined versioned symbols are handled specially; they are resolved
at the very end of name resolution stage. When they are resolved, we
need to make sure that they are marked as imported symbols.

Fixes https://github.com/rui314/mold/issues/1057
2023-07-05 11:34:33 +09:00
Rui Ueyama b04aba89d3 Add -z stack-size
https://github.com/rui314/mold/issues/917
2023-06-29 15:58:00 +09:00
Rui Ueyama 2cb41d8a2b Upgrade bundled mimalloc to v2.1.2 2023-06-29 12:23:39 +09:00
Rui Ueyama 1b1a142a89 Upgrade bundled OneTBB to version 2021.9.0 2023-06-29 12:21:58 +09:00
Rui Ueyama dcfe76bc80 Format 2023-06-29 12:17:21 +09:00
Rui Ueyama d4d93d7fb7 Format 2023-06-19 12:35:20 +09:00
Rui Ueyama ebd780e8ba Support R_PPC64_REL32
Fixes https://github.com/rui314/mold/issues/1049
2023-06-17 19:00:55 +09:00
Rui Ueyama 65bd36ba40 Generate a PT_OPENBSD_RANDOMIZE segment 2023-06-14 14:08:40 +09:00
Rui Ueyama bc86ab514f Add -nopie as an alias for -no-pie
OpenBSD seems to be using an alternative spelling for this option.
2023-06-14 09:55:55 +08:00
Rui Ueyama 8ee7773260 Do not use mimalloc on OpenBSD
It seems like mimalloc doesn't work on OpenBSD at all.
2023-06-13 11:10:04 +08:00
Rui Ueyama e011889436 Fix build on systems without ucontext.h in libc
Upstream patch:
https://github.com/oneapi-src/oneTBB/commit/ea4e6156a2cde839fc01b21b76cd9b5743ec0cef
2023-06-05 11:52:56 +09:00
Rui Ueyama 16e22ec067 Handle "amd64" as a synonym for "x86_64"
OpenBSD's `uname` returns `amd64` instead of `x86_64`.
2023-06-04 15:28:44 +09:00
Rui Ueyama d2ee47a4ac Print out a hint about older versions of litbb
https://github.com/rui314/mold/issues/1031
2023-06-04 15:28:44 +09:00
Rui Ueyama dce8ebe526 Merge pull request #1033 from ishitatsuyuki/wrap-mold
mold-wrapper: Wrap ld.mold as well.
2023-04-19 20:10:36 +09:00
Tatsuyuki Ishi 23de4be59f mold-wrapper: Wrap ld.mold as well.
Convenient if you have configured to use mold for a particular project but want
to test a new local build.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-04-19 19:35:29 +09:00
Rui Ueyama 76e3f87775 Merge pull request #1034 from ishitatsuyuki/errno 2023-04-19 19:26:54 +09:00
Tatsuyuki Ishi fca6fad720 MappedFile: Fail on open() errors other than ENOENT.
open() can fail with transient reasons, such as running out of file descriptor
limit. Previously, this was treated the same as file not being found, which
caused confusing error messages [1].

With this change anything but ENOENT will be treated as a fatal error. This will
also include e.g. permission errors, but if there's something within the search
path that is not accessible, it's likely better to tell the user instead of
silently skipping it.

[1]: https://github.com/rui314/mold/issues/1026

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-04-19 17:29:37 +09:00
Rui Ueyama ceffcfcadd Fix test
Fixes https://github.com/rui314/mold/issues/1030
2023-04-18 16:47:16 +09:00
Rui Ueyama 1ad19a4ce1 Simplify 2023-04-16 14:57:47 +09:00
Rui Ueyama 67a58c01d0 Update comments 2023-04-16 14:41:10 +09:00
Rui Ueyama 186ac191c4 Update comments 2023-04-15 15:15:18 +09:00
Rui Ueyama 34bf04bc41 Improve tests 2023-04-15 12:53:43 +09:00
Rui Ueyama e6db53fadb Do not set STT_GNU_IFUNC to non-exported symbols
STT_GNU_IFUNC is significant only for exported symbols. Setting
non-exported symbol type to STT_GNU_IFUNC actually causes an issue
if the symbol is used as a canonical PLT because the runtime would
try to run the canonical PLT as an IFUNC.

Fixes https://github.com/rui314/mold/issues/1028
2023-04-15 12:32:21 +09:00
Rui Ueyama 461001328b Simplify 2023-04-02 09:09:04 +08:00
Rui Ueyama 4bffe26bc0 Support R_RISCV_SET_ULEB128 and R_RISCV_SUB_ULEB128
https://github.com/riscv-non-isa/riscv-elf-psabi-doc/pull/361
2023-03-31 10:26:50 +08:00
Rui Ueyama 290b4eaa46 Refactor 2023-03-28 17:30:44 +08:00
Rui Ueyama 0f1b828e6a Refactor 2023-03-28 16:57:57 +08:00
Rui Ueyama 46288758a0 Improve README with ChatGPT 2023-03-22 16:48:14 +08:00
Rui Ueyama e8789c71a4 Format 2023-03-22 16:35:22 +08:00
Rui Ueyama f54203d76c Update the auto-generated manpage 2023-03-22 12:55:52 +08:00
Rui Ueyama a5634794b3 Improve the man page
I made this change by asking GPT-4-based ChatGPT to improve this
document with an additional note that this document was written by a
non-native English speaker and may have wrong or unnatural phrases.
2023-03-22 12:40:35 +08:00
Rui Ueyama b403b811cd Report a possible ODR violation
https://github.com/rui314/mold/issues/1019
2023-03-18 16:32:04 +08:00
Rui Ueyama 36fc065548 Add a feature to not run mold simultaneously
https://github.com/rui314/mold/issues/117
2023-03-17 14:58:27 +08:00
Rui Ueyama 39b4548a2a Simplify 2023-03-17 13:58:11 +08:00
Rui Ueyama 52f0c4a06f Fix typos 2023-03-17 13:49:59 +08:00
Rui Ueyama c74fe12dd5 Fix test 2023-03-16 18:24:25 +08:00
Rui Ueyama fe11565110 Improve comments 2023-03-16 18:08:44 +08:00
Rui Ueyama cca255e6be Bump mold version to 1.11.0 2023-03-16 14:01:09 +08:00
Rui Ueyama 325d12d169 Upload test artifacts only on test failure 2023-03-15 08:32:51 +08:00
Rui Ueyama 2a26a0db64 Fix CI 2023-03-15 08:32:51 +08:00
Rui Ueyama 72c9425ae3 Merge pull request #1016 from marxin/fix-Wunused-but-set-variable
Fix warning: variable ‘check’ set but not used [-Wunused-but-set-variable]
2023-03-14 21:44:48 +09:00
Rui Ueyama ff46c33d3f Merge pull request #1015 from marxin/fix-Wint-in-bool-context
fix thunks.cc:81:32: warning: enum constant in boolean context [-Wint…
2023-03-14 21:44:33 +09:00
Martin Liska c59b3f5fa5 Fix warning: variable ‘check’ set but not used [-Wunused-but-set-variable]
Signed-off-by: Martin Liska <[email protected]>
2023-03-14 13:28:45 +01:00
Martin Liska 84a5509da2 fix thunks.cc:81:32: warning: enum constant in boolean context [-Wint-in-bool-context]
Fixes the following error:
/elf/thunks.cc:81:32: warning: enum constant in boolean context [-Wint-in-bool-context]
  return ty == R_PPC64_REL24 || R_PPC64_REL24_NOTOC;

Signed-off-by: Martin Liska <[email protected]>
2023-03-14 13:25:05 +01:00
Rui Ueyama 305153b9c7 Archive test results 2023-03-14 19:22:56 +08:00
Rui Ueyama 1adde7aa51 Make undefined symbol in a DSO to keep other DSOs alive
Fixes https://github.com/rui314/mold/issues/1014
2023-03-14 18:52:03 +08:00
Rui Ueyama 4fa2bc17b8 Allow empty mergeable sections
Fixes https://github.com/rui314/mold/issues/1012
2023-03-14 07:14:18 +08:00
Rui Ueyama 294a467116 Rename files 2023-03-13 16:23:36 +08:00
Rui Ueyama 67eb3069b3 Fix build failure
Fixes https://github.com/rui314/mold/issues/1008
2023-03-07 18:34:59 +08:00
Rui Ueyama ad2694445c Import changes from sold 2023-03-07 15:57:28 +08:00
Rui Ueyama 6a1713e440 Merge pull request #1004 from ishitatsuyuki/lto-eh-frame 2023-03-07 12:01:54 +09:00
Tatsuyuki Ishi 62d65371d7 [ELF] Kill .eh_frame sections after symbol resolution.
Handle .eh_frame in a similar way as mergeable sections.

This seems to be required for GCC CRT compiled with LTO.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-03-06 13:22:06 +08:00
Rui Ueyama c4722fe5ae Build Linux binaries on GitHub Actions 2023-03-05 14:46:32 +08:00
Tatsuyuki Ishi 13472be059 [ELF] Resolve .eh_frame symbols renamed by GCC LTO.
When LTO is enabled for CRT it seems symbols like __EH_FRAME_BEGIN__.lto_priv.0
can be created. Treat it as the same as the non-LTO variant.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-03-05 13:59:40 +08:00
Rui Ueyama 6d2dbd42a6 Preparation to support Apple ld64's -w option 2023-03-05 08:48:09 +08:00
Rui Ueyama d2970e096d Support R_X86_64_GOTOFF64 in non-memory-allocated sections
Fixes https://github.com/rui314/mold/issues/1003
2023-03-05 08:07:24 +08:00
Rui Ueyama 77c4795139 Workaround for an old GCC issue
Fixes https://github.com/rui314/mold/issues/1003
2023-03-04 17:10:49 +08:00
Rui Ueyama 5434babdf3 Fix typo 2023-03-04 16:53:02 +08:00
Rui Ueyama 530b3ebd88 Enable tests for Power10 if MOLD_ENABLE_QEMU_TESTS_POWER10 is true 2023-03-04 16:13:57 +08:00
Rui Ueyama 33172a7b61 Update comments 2023-03-04 11:01:25 +08:00
Rui Ueyama 5065547c4e Support the Power10 ABI 2023-03-04 09:43:42 +08:00
Rui Ueyama 0870078ed2 Merge pull request #997 from ksco/fix_rv64_test 2023-02-22 18:27:57 +09:00
Yang Liu 992ad0fc08 [RISCV] Fix riscv64_obj-compatible 2023-02-22 16:40:42 +08:00
Rui Ueyama 5ae8ec9669 Merge pull request #993 from ksco/riscv_compatibility 2023-02-21 14:03:08 +09:00
Rui Ueyama 0bd5adde7e Refactor 2023-02-20 18:06:33 +08:00
Yang Liu 7e8a9f230c [RISCV] Add an obj ABI incompatibility tests
Signed-off-by: Yang Liu <[email protected]>
2023-02-19 19:59:29 +08:00
Yang Liu f7bc901c2d [RISCV] Check object files' compatibility
Signed-off-by: Yang Liu <[email protected]>
2023-02-19 19:58:13 +08:00
Rui Ueyama e1a7590db3 Fix LTO symbol resolution instability
Previously, we clear liveness bits for all input files after compiling
IR object files to real object files so that we can do name resolution
completely from scratch. However, that can result in inconsistent name
resolution results before and after LTO.

Consider a weak defined symbol X in an object file. A weak undef doesn't
pull files out of an archive file. However, if symbol X is transformed
to an undefined symbol as a result of LTO, it could pull out a file from
an archive. That can have cascading effects and result in a link failure.

With this fix, all object files that are alive at the time when LTO kicks
in are guaranteed to be kept after LTO.

Fixes https://github.com/rui314/mold/issues/991
2023-02-19 16:26:46 +08:00
Rui Ueyama f592b6e167 Refactor 2023-02-16 15:18:25 +08:00
Rui Ueyama 8c0f455b97 Refactor 2023-02-16 08:58:47 +08:00
Rui Ueyama 0d08c52258 Refactor 2023-02-15 20:46:50 +08:00
Rui Ueyama a42626b975 Skip if ppc64
Fixes https://github.com/rui314/mold/issues/989
2023-02-15 15:51:35 +08:00
Rui Ueyama ec728d0cef Simplify 2023-02-13 10:17:37 +08:00
Rui Ueyama 20bddc75cf Avoid inline assembly 2023-02-12 11:58:47 +08:00
Rui Ueyama 45bece4bd9 Handle ppc64 as a synonym for powerpc64
Fixes https://github.com/rui314/mold/issues/986
2023-02-12 09:06:10 +08:00
Rui Ueyama 445a23fbc0 Merge pull request #985 from ksco/fix_ci_warnings
[CI] Use clang++-12 to suppress warnings
2023-02-12 08:11:18 +09:00
Rui Ueyama 6952a2efa5 Merge pull request #984 from ksco/weak_undef4
Add an another weak undef test
2023-02-12 08:09:44 +09:00
Yang Liu 8c2ccd2f55 [CI] Use clang++-12 to suppress warnings 2023-02-11 15:00:08 +08:00
Yang Liu 9ce963b480 Add an another weak undef test
This test ensures that mold does not extract archive members to resolve
undefined weak symbols.

Signed-off-by: Yang Liu <[email protected]>
2023-02-11 14:51:30 +08:00
Rui Ueyama 35794cfc66 Merge pull request #983 from ksco/weak_undef3 2023-02-11 13:24:06 +09:00
Yang Liu e300c6d9b2 [RISCV] Add a weak undef symbol test case
Signed-off-by: Yang Liu <[email protected]>
2023-02-11 11:43:02 +08:00
Rui Ueyama 8660db16a8 Update man page 2023-02-11 08:51:20 +08:00
Rui Ueyama 25b4f0b054 Merge pull request #980 from ksco/fix_undef_weak
[RISCV] Fix R_RISCV_PCREL_HI20
2023-02-10 10:28:53 +09:00
Yang Liu fc809c6c73 [RISCV] Fix R_RISCV_PCREL_HI20 2023-02-09 14:15:56 +08:00
Rui Ueyama 51845ac5fe Support R_RISCV_PLT32
https://github.com/riscv-non-isa/riscv-elf-psabi-doc/pull/363
2023-02-06 12:48:56 +08:00
Rui Ueyama 8e50fad6b9 Update man page 2023-02-06 12:48:56 +08:00
Rui Ueyama 3e818cd350 Simplify 2023-02-06 08:24:33 +08:00
Rui Ueyama 4327d4547e Fix typo 2023-02-06 08:10:45 +08:00
Rui Ueyama abba8b5864 Update man page 2023-02-05 21:10:39 +08:00
Rui Ueyama 8ede25fa42 Write man page in Markdown instead of mandoc
mandoc is hard to read and hard to write. With `ronn` command, we can
write a man page in Markdown and convert it to roff.
2023-02-05 16:46:46 +08:00
Rui Ueyama 50d4bc04ab Update README 2023-02-05 12:10:09 +08:00
Rui Ueyama 128f8b3b8d Remove mentions to mold/macOS
Fixes https://github.com/rui314/mold/issues/978
2023-02-05 12:04:07 +08:00
Rui Ueyama 5140010a6e Attempt to fix R_ARM_PLT32
https://github.com/rui314/mold/issues/974
2023-02-05 08:06:13 +08:00
Rui Ueyama 24c21d7ed2 Simplify
is_imported and is_exported are computed and overwritten by
compute_import_export(), so these early assignments are redundant.
2023-02-05 07:52:37 +08:00
Rui Ueyama e50590043c Support R_AARCH64_PLT32 2023-02-01 11:36:06 +08:00
Rui Ueyama fdc592b169 Warn on shared object files in archives
https://github.com/rui314/mold/issues/976
2023-01-30 09:38:44 +08:00
Rui Ueyama 82731d62f9 Add a new sponsor name 2023-01-28 09:34:28 +08:00
Rui Ueyama 912a863b13 Refactor 2023-01-28 09:03:24 +08:00
Rui Ueyama 627bf7c8ef Do not relax GOTPCRELX reloc if it's unreachable
Fixes https://github.com/rui314/mold/issues/975
2023-01-27 20:12:40 +08:00
Rui Ueyama beddabe64d Import changes for the sold linker 2023-01-26 08:34:04 +08:00
Rui Ueyama 339b1b4704 Merge pull request #973 from ganta/patch-1
Adjust the image ratio and fix the alt attribute.
2023-01-24 12:47:55 +09:00
Hideki Igarashi 8dff18db0e Adjust the image ratio and fix the alt attribute.
Signed-off-by: Hideki Igarashi <[email protected]>
2023-01-24 12:40:26 +09:00
Rui Ueyama ebb9b6aff8 Add commas 2023-01-23 21:45:23 +08:00
Rui Ueyama 22af2ff328 Add Cybozu as a corporate-tier sponsor 2023-01-23 17:02:23 +08:00
Rui Ueyama d755793345 Remove unused struct member 2023-01-23 09:35:38 +08:00
Rui Ueyama ec756333cb Support --hash-style=none to cancel --hash-style=[sysv|gnu|both]
https://github.com/rui314/mold/issues/971
2023-01-22 20:29:15 +08:00
Rui Ueyama ce3aa9a752 Update an include file path
Pointed out by https://github.com/rui314/mold/pull/972/files
2023-01-22 19:35:45 +08:00
Rui Ueyama e48b90f941 Make Atomic copyable and copy-constructible 2023-01-22 16:25:25 +08:00
Rui Ueyama 7f407fb1ae Refactor 2023-01-22 12:44:59 +08:00
Rui Ueyama ac212c3d00 Improve range extension insertion algorithm
Now the algorithm matches with the code comment.
2023-01-22 12:29:33 +08:00
Rui Ueyama 040180f933 Bump mold version to 1.10.1 2023-01-22 09:57:00 +08:00
Rui Ueyama 7e6554677f Fix out-of-bound memory access
Fixes https://github.com/rui314/mold/issues/969
2023-01-21 12:06:10 +08:00
Rui Ueyama dbeb10c0d2 Recognize ld64.sold as a command name 2023-01-20 16:21:18 +08:00
Rui Ueyama c935d6387a Bump mold version to 1.10.0 2023-01-20 15:03:58 +08:00
Rui Ueyama acdd92e741 Refactor 2023-01-20 15:02:32 +08:00
Rui Ueyama 62660bc02a Refactor 2023-01-20 12:07:03 +08:00
Rui Ueyama 90d9fd06ae Refactor 2023-01-19 20:34:00 +08:00
Rui Ueyama 82d412cbff Report an error for a TLS access model mismatch 2023-01-19 16:59:06 +08:00
Rui Ueyama 85efa3796a Set DF_STATIC_TLS to DSOs that cannot be loaded with dlopen() 2023-01-19 16:37:03 +08:00
Rui Ueyama 3f672919c2 Refactor 2023-01-19 15:01:22 +08:00
Rui Ueyama a2e8f13ddd Refactor 2023-01-19 14:54:48 +08:00
Rui Ueyama 821fb07083 Also fix R_390_{PC,PLT}12DBL 2023-01-19 14:16:22 +08:00
Rui Ueyama b9bd71ecfc Fix R_390_{12,GOT12,GOTPLT12}
When writing a 12 bit value, we need to preserve the most significant 4 bits.
2023-01-19 14:01:05 +08:00
Rui Ueyama 9df4d6e479 Fix test/elf/compressed-debug-info.sh for SH-4 2023-01-19 12:51:51 +08:00
Rui Ueyama 29eb97fda5 Rename a file 2023-01-19 11:43:29 +08:00
Rui Ueyama 67c6255c9a Refactor 2023-01-19 10:31:21 +08:00
Rui Ueyama 2d68cbbfd2 Save 8 byte of memory for each global symbol 2023-01-19 08:54:16 +08:00
Rui Ueyama f98e0ef92a Refactor 2023-01-18 20:57:24 +08:00
Rui Ueyama 83303995c7 Refactor 2023-01-18 20:40:26 +08:00
Rui Ueyama 7d369dd9e8 Refactor 2023-01-18 17:29:17 +08:00
Rui Ueyama 9f66ea7026 Build the native binary by default 2023-01-18 16:29:25 +08:00
Rui Ueyama 67a96ae8e1 Refactor 2023-01-18 16:20:20 +08:00
Rui Ueyama 8936194ba7 Report all undefined symbols correctly
Previously, we failed to report an undefined error if we have two
undefined symbols, a strong and a weak one, for the same symbol.

It is an undefined symbol error if an undefined symbol is resolved
to a weak undefined symbol.

Fixes https://github.com/rui314/mold/issues/967
2023-01-18 16:01:36 +08:00
Rui Ueyama 00af084416 Remove an unnecessary dependency 2023-01-18 12:45:08 +08:00
Rui Ueyama 3829372a42 Refactor 2023-01-18 11:24:15 +08:00
Rui Ueyama 54ab1ec4b4 Silence -Werror=format-truncation
Ideally we wanted to use __builtin_assume() or [[assume]], but the former
is available only in clang and the latter is in C++23.

Fixes https://github.com/rui314/mold/issues/966
2023-01-18 11:00:59 +08:00
Rui Ueyama f4b0596b3a Update the man page 2023-01-18 09:02:55 +08:00
Rui Ueyama 71f6b0bc32 Refactor 2023-01-17 21:05:26 +08:00
Rui Ueyama fb8e63e498 Make some tests faster 2023-01-17 09:19:09 +08:00
Rui Ueyama 1a133a4df5 Upgrade the bundled mimalloc from 2.0.7 to 2.0.9 2023-01-17 08:53:56 +08:00
Rui Ueyama 60fc036bd6 Enabling logging is [[unlikely]] 2023-01-17 07:25:18 +08:00
Rui Ueyama 3d96596485 Add a newline after a comment for --print-dependencies 2023-01-16 18:08:52 +08:00
Rui Ueyama 0a8e90bd84 Rename a file 2023-01-16 16:22:30 +08:00
Rui Ueyama 1e961000e6 Move more files under common/ 2023-01-16 15:35:24 +08:00
Rui Ueyama 3539487471 Remove an unnecessary file 2023-01-16 15:31:33 +08:00
Rui Ueyama 6caf0867b0 Reorganize files 2023-01-16 15:27:10 +08:00
Rui Ueyama 28a6c0e4f0 Update references 2023-01-16 12:34:36 +08:00
Rui Ueyama a4349df0a5 [PPC64v1] Support a PLT section containing more than 32767 entries. 2023-01-16 11:06:34 +08:00
Rui Ueyama d2335707de [x86] Always relax TLSGD/TLSLD if -static 2023-01-14 15:27:28 +08:00
Rui Ueyama 1d2b08ade7 [x86] Always relax GOTX relocations 2023-01-14 15:19:27 +08:00
Rui Ueyama dd93988dc6 Simplify 2023-01-14 13:14:07 +08:00
Rui Ueyama 06ec355e88 Improve instal-build-deps script
voidlinux/voidlinux Docker image doesn't contain bash, so we should
eliminate the dependency to bash from this script.
2023-01-14 12:00:49 +08:00
Rui Ueyama c10f36c71c Merge pull request #963 from mystuffs/main
Add: build deps when compiling mold on Void Linux
2023-01-14 12:37:56 +09:00
Rui Ueyama f32ce333dd [s390x] Implement TLSGD to TLSIE relaxation 2023-01-14 11:33:58 +08:00
rilysh d48c52cc2f Merge branch 'rui314:main' into main 2023-01-14 08:49:54 +05:30
Rui Ueyama 25d02bb261 [x86-64] Implement TLSGD to TLSIE relaxation
If we know that the .so file we are creating will not be dlopen'ed,
we can relax __tls_get_addr function calls to GOT loads.
2023-01-14 10:22:06 +08:00
rilysh fda7657eab add: build deps for Void Linux
Install build dependencies when compiling mold on Void Linux

Signed-off-by: rilysh <[email protected]>
2023-01-14 01:06:06 +00:00
Rui Ueyama 983fe0f10f Refactor 2023-01-13 21:07:30 +08:00
Rui Ueyama 8437097305 Refactor 2023-01-13 20:46:19 +08:00
Rui Ueyama 5a9341659c Remove an unnecessary log message 2023-01-13 17:43:08 +08:00
Rui Ueyama 125af4c855 Refactor 2023-01-13 16:34:50 +08:00
Rui Ueyama fa1db07ec8 Optimize section fragment liveness management 2023-01-13 15:23:09 +08:00
Rui Ueyama f09bf0615b Simplify 2023-01-13 11:36:02 +08:00
Rui Ueyama 534cd392db Do not run if -static is not supported
Fixes https://github.com/rui314/mold/issues/961
2023-01-13 08:48:09 +08:00
Rui Ueyama 5dee5ea49d Refactor 2023-01-12 17:11:18 +08:00
Rui Ueyama 957eaf0f18 Optimize DynamicSection::finalize
Parallelizing this loop makes sense if we have hundreds of thousands
of dynamic symbols.
2023-01-12 13:44:02 +08:00
Rui Ueyama 12fad60eb4 Reduce the number of dynamic symbols for TLS symbols 2023-01-12 11:52:10 +08:00
Rui Ueyama 404077e330 Refactor 2023-01-12 10:49:20 +08:00
Rui Ueyama 73947c56e1 Create section symbols for non-ALLOC sections
Previously, we created section symbols only for memory-allocated secitons.
That resulted in symbols with invalid section indices for the --relocatable
output.

https://github.com/rui314/mold/issues/957
2023-01-12 09:57:24 +08:00
Rui Ueyama 63fae64e77 Refactor 2023-01-12 09:55:53 +08:00
Rui Ueyama 7f27b180ac Optimize 2023-01-11 17:29:39 +08:00
Rui Ueyama 6f4ba44946 Avoid sequentially consistent memory access 2023-01-11 14:16:41 +08:00
Rui Ueyama dcc58e4f7a Refactor 2023-01-11 10:45:45 +08:00
Rui Ueyama a68dfdb2d3 Reduce the sizes of Symbol and Relocation for needs_thunk targets
Since the maximum size of RangeExtensionThunk is 100 KiB, it can't contain
too many entries, so the contents is addressable with a 16-bit value.

We don't create more than 32768 range extension thunks (unless the output
is hundreds gibibytes in size), so we can also use i16 to specify the
index of RangeExtensionThunk.
2023-01-10 17:39:17 +08:00
Rui Ueyama 277dd7710a Refactor 2023-01-10 17:27:35 +08:00
Rui Ueyama d03c9a1654 Fix data race
The iterator obtained by `find()` may be invalidated by other writes
happening concurrently, so we shouldn't release the mutex while we
are using it.
2023-01-10 15:22:07 +08:00
Rui Ueyama 19bb456721 Merge pull request #954 from MehdiChinoune/fix-mingw-w64 2023-01-10 15:34:05 +09:00
مهدي شينون (Mehdi Chinoune) 4e26d7b237 Fix building on MinGW-w64
Signed-off-by: Mehdi Chinoune <[email protected]>
2023-01-10 06:41:46 +01:00
Rui Ueyama 6a797a4fbc Fix CI 2023-01-10 13:41:31 +08:00
Rui Ueyama 14784bbc4e Fix CI 2023-01-10 13:04:12 +08:00
Rui Ueyama 6fd47dbab4 Document the --print-dependencies option
We used to have `--print-dependencies` and `--print-dependencies=full`
options. I think this distinction is necesssary. Now, `=full` is renamed
`--print-dependencies`.
2023-01-10 12:54:00 +08:00
Rui Ueyama e31801f22f Refactor 2023-01-10 12:37:25 +08:00
Rui Ueyama 58e81bb0a0 Cache more results 2023-01-09 20:32:51 +08:00
Rui Ueyama d2cdce4410 Implement another algorithm to optimize create_output_sections
https://github.com/rui314/mold/issues/937
2023-01-09 19:31:22 +08:00
Rui Ueyama 7132822cc7 Mitigate lock contention
This change speeds up the `create_output_sections` pass.

https://github.com/rui314/mold/issues/937
2023-01-09 18:05:18 +08:00
Rui Ueyama ca98fe843d Fix -Werror=implicit-fallthrough
These aren't bug, but I decided to repeat the same code instead of
adding [[fallthrough]] because the code duplication is very small.

Fixes https://github.com/rui314/mold/issues/952
2023-01-09 17:04:51 +08:00
Rui Ueyama bbfed9af3f Do not access uninitialized memory 2023-01-09 16:38:10 +08:00
Rui Ueyama 707ddaa630 Avoid unnecessary memory initializations 2023-01-09 14:44:02 +08:00
Rui Ueyama d127583c86 Refactor 2023-01-09 13:59:35 +08:00
Rui Ueyama 7128ee371d Skip if ARM64 too
https://github.com/rui314/mold/issues/951
2023-01-09 13:21:35 +08:00
Rui Ueyama 321158b99f Skip test if musl libc
Fixes https://github.com/rui314/mold/issues/951
2023-01-09 13:03:49 +08:00
Rui Ueyama d71a672089 Fix an option name
LLVM lld does not accept `-gdb-index` as an option
2023-01-09 12:46:15 +08:00
Rui Ueyama e17d7daee8 Fix a crash bug in --emit-relocs
Fixes https://github.com/rui314/mold/issues/950
2023-01-09 09:31:27 +08:00
Rui Ueyama d4e40a31c1 Do not R_AARCH64_ADR_GOT_PAGE if it refers an IFUNC 2023-01-09 08:56:48 +08:00
Rui Ueyama 915cd23b2f Skip if GNU IFUNC is not supported
Even on x86-64, non-GNU libc may not support the IFUNC.

Fixes https://github.com/rui314/mold/issues/949
2023-01-09 08:26:36 +08:00
Rui Ueyama 994f800f8f Add a missing relocation overflow check 2023-01-09 08:15:18 +08:00
Rui Ueyama 2b3a684869 Refactor 2023-01-09 08:15:03 +08:00
Rui Ueyama 68b2b35148 Refactor 2023-01-08 21:15:53 +08:00
Rui Ueyama da6dd7c0ac [ARM64] Relax R_AARCH64_ADR_PREL_PG_HI21 2023-01-08 20:47:49 +08:00
Rui Ueyama f2311b1efa [ARM64] Relax GOT-loading ADRP+LDR to ADRP+ADD 2023-01-08 16:43:23 +08:00
Rui Ueyama b8bb78fde7 Update comments 2023-01-08 13:58:41 +08:00
Rui Ueyama e4a1a57318 Update comments 2023-01-08 13:21:36 +08:00
Rui Ueyama 1ebaa27fc4 Refactor 2023-01-08 12:46:51 +08:00
Rui Ueyama d6db850121 Refactor 2023-01-08 12:02:40 +08:00
Rui Ueyama ebf732fca9 Update comments 2023-01-08 11:26:21 +08:00
Rui Ueyama eba86d5312 Document TLS 2023-01-08 09:38:55 +08:00
Rui Ueyama 648fcc60ca Refactor 2023-01-08 07:35:14 +08:00
Rui Ueyama 9f5b7e278f Enable -gdb-index when building mold 2023-01-07 17:26:08 +08:00
Rui Ueyama 14fe1ba070 Fix typo 2023-01-07 17:21:14 +08:00
Rui Ueyama 9d3cfd5c7d Refactor 2023-01-07 16:39:13 +08:00
Rui Ueyama c2d5f602e7 Refactor 2023-01-07 15:00:03 +08:00
Rui Ueyama ed42ce0d36 Do not install LICENSE file if it does not exist 2023-01-07 14:28:08 +08:00
Rui Ueyama 8201d0ed2a Add const 2023-01-07 11:59:18 +08:00
Rui Ueyama 779a404950 [RISC-V] Fix wrong R_RISCV_LO12_{I,S} relaxation 2023-01-07 11:27:13 +08:00
Rui Ueyama 52a0638dd2 Simplify
We used to try to avoid using identifiers such as S, A or P more than
once because they were defined as macros expaneded to function calls.
Now they are just variables, so we can use them as many times as we
want without any performance penalty.
2023-01-07 11:09:47 +08:00
Rui Ueyama 73034f94fe Do not trigger undefined behavior for unknown relocations
Unknown relocations will be reported by scan_relocations(). We don't
want to execute unreachable() statement in get_addend() because it
triggers undefined behavior.

https://github.com/rui314/mold/issues/947
2023-01-07 10:23:07 +08:00
Rui Ueyama ad0b6d0ac6 Bump mold version to 1.9.0 2023-01-06 16:13:10 +08:00
Rui Ueyama 6e918eda31 Fix CI 2023-01-06 12:04:47 +08:00
Rui Ueyama 3c968289f8 Fix crash bug for when --gdb-index and -gsplit-dwarf are used together
Fixes https://github.com/rui314/mold/issues/940
2023-01-06 11:09:12 +08:00
Rui Ueyama cb204decf4 Fix R_RISCV_HI20 relaxation
If we compile a program for high address, this code would accidentally
clear high 20 bits.
2023-01-06 10:22:20 +08:00
Rui Ueyama f9fe3d621f Refactor 2023-01-05 21:48:03 +08:00
Rui Ueyama b51d5baae6 Handle R_ARM_PLT32 as an alias for R_ARM_JUMP24 instead of R_ARM_CALL
It looks like R_ARM_PLT32 can be applied to non-BL/non-BLX instructions,
so we can't handle it as an alias for R_ARM_CALL.

https://github.com/rui314/mold/issues/934
2023-01-05 18:07:52 +08:00
Rui Ueyama 747ba1d41e Add a missing relocation type
https://github.com/rui314/mold/issues/934
2023-01-05 17:20:56 +08:00
Rui Ueyama 5c4ed9259e Enable more tests for arm32 2023-01-05 15:20:52 +08:00
365 changed files with 68214 additions and 11364 deletions

No files matched your search

+42 -14
View File
@@ -26,11 +26,20 @@ jobs:
- name: build and test
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
sudo apt-get install -y clang++-12
mkdir build
cd build
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ matrix.target }} ..
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++-12 ${{ matrix.target }} ..
cmake --build . -j$(nproc)
ctest . -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-results-clang
path: |
build/out
build/Testing
build-gcc:
runs-on: ubuntu-20.04
@@ -42,26 +51,45 @@ jobs:
# Install cross toolchains
dpkg --add-architecture i386
./install-build-deps.sh update
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
apt-get install -y sudo qemu-user gdb zstd dwarfdump xz-utils {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha,mips,mipsel}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf {gcc,g++}-10-{mips64,mips64el}-linux-gnuabi64
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
ln -sf /usr/bin/$i-linux-gnu-g++-10 /usr/bin/$i-linux-gnu-g++
for i in {i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha,mips,mipsel}-linux-gnu arm-linux-gnueabihf {mips64,mips64el}-linux-gnuabi64; do
ln -sf /usr/bin/$i-gcc-10 /usr/bin/$i-gcc
ln -sf /usr/bin/$i-g++-10 /usr/bin/$i-g++
done
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
# Install a RV32 toolchain from third party since it's not available
# as an Ubuntu package.
mkdir /usr/local/rv32
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.08.26/riscv32-glibc-ubuntu-20.04-nightly-2022.08.26-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/riscv32-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-objdump /usr/bin/riscv32-linux-gnu-objdump
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
chmod 755 /usr/bin/riscv32-linux-gnu-gcc /usr/bin/riscv32-linux-gnu-g++
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
done
# Install a LoongArch toolchain
mkdir /usr/local/larch
wget -O- -q https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz | tar -C /usr/local/larch --strip-components=1 --xz -xf -
ln -sf /usr/local/larch/target /usr/loongarch64-linux-gnu
cp -r /usr/local/larch/loongarch64-unknown-linux-gnu/lib/* /usr/loongarch64-linux-gnu/lib64/
for i in objdump objcopy strip; do
ln -sf /usr/local/larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-gcc -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-gcc
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-g++ -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-g++
chmod 755 /usr/bin/loongarch64-linux-gnu-{gcc,g++}
wget -O/usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2023.08.08/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
shell: bash
- name: ccache
uses: hendrikmuhs/ccache-action@v1
@@ -70,14 +98,14 @@ jobs:
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
mkdir build
cd build
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On ..
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On -DMOLD_ENABLE_QEMU_TESTS_LOONGARCH=On ..
cmake --build . -j$(nproc)
ctest . -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v3
if: always()
if: failure()
with:
name: test-results
name: test-results-gcc
path: |
build/out
build/Testing
+15
View File
@@ -0,0 +1,15 @@
name: linux-packages
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: build
run: ./dist.sh x86_64
- name: upload
uses: actions/upload-artifact@v3
with:
path: mold-*-linux.tar.gz
+50 -27
View File
@@ -44,7 +44,7 @@
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.13)
project(mold VERSION 1.8.0)
project(mold VERSION 2.1.0)
include(CMakeDependentOption)
include(GNUInstallDirs)
@@ -52,7 +52,7 @@ include(GNUInstallDirs)
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
if(MOLD_USE_MOLD)
add_link_options(-fuse-ld=mold)
add_link_options(-fuse-ld=mold -Wl,--gdb-index)
endif()
add_executable(mold)
@@ -65,7 +65,16 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-Wno-sign-compare
-Wno-unused-function)
-Wno-unused-function
-ggnu-pubnames)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(OPENBSD ON)
endif()
# Build mold with -flto if -DMOLD_LTO=ON
@@ -142,7 +151,7 @@ check_cxx_source_compiles(
"static_assert(sizeof(void *) == 4); int main() {}" P32)
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
"NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT P32" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
MOLD_USE_MIMALLOC OFF)
@@ -198,12 +207,16 @@ endif()
# We always use Clang to build mold on Windows. MSVC can't compile mold.
if(WIN32)
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
endif()
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
if(MINGW)
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
target_link_libraries(mold PRIVATE bcrypt)
endif()
else()
include(CheckLibraryExists)
check_library_exists(m pow "" LIBM_FOUND)
@@ -245,7 +258,7 @@ if(NOT HAVE_FULL_ATOMIC_SUPPORT)
endif()
# Add -pthread
if(NOT APPLE AND NOT WIN32)
if(NOT APPLE AND NOT MSVC)
target_compile_options(mold PRIVATE -pthread)
target_link_options(mold PRIVATE -pthread)
endif()
@@ -255,15 +268,15 @@ add_custom_target(git_hash
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
DEPENDS update-git-hash.cmake
-P ${CMAKE_SOURCE_DIR}/common/update-git-hash.cmake
DEPENDS common/update-git-hash.cmake
BYPRODUCTS git-hash.cc
VERBATIM)
add_dependencies(mold git_hash)
# Create config.h file
configure_file(config.h.in config.h)
configure_file(common/config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
@@ -275,16 +288,19 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# compiler instances. This is hacky but greatly reduces compile time
# on a multicore machine.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE
PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4 ALPHA)
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
S390X SPARC64 M68K SH4 ALPHA MIPS64LE MIPS64BE LOONGARCH32 LOONGARCH64)
list(APPEND MOLD_ELF_TEMPLATE_FILES
elf/arch-loongarch.cc
elf/arch-riscv.cc
elf/cmdline.cc
elf/dwarf.cc
elf/gc-sections.cc
elf/icf.cc
elf/input-files.cc
elf/input-sections.cc
elf/jobs.cc
elf/linker-script.cc
elf/lto.cc
elf/main.cc
@@ -294,6 +310,7 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
elf/relocatable.cc
elf/subprocess.cc
elf/thunks.cc
elf/tls.cc
)
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
@@ -335,32 +352,32 @@ endif()
# Add other non-template source files.
target_sources(mold PRIVATE
compress.cc
demangle.cc
common/compress.cc
common/demangle.cc
common/filepath.cc
common/glob.cc
common/hyperloglog.cc
common/main.cc
common/multi-glob.cc
common/perf.cc
common/tar.cc
common/uuid.cc
elf/arch-alpha.cc
elf/arch-arm32.cc
elf/arch-arm64.cc
elf/arch-i386.cc
elf/arch-m68k.cc
elf/arch-mips64.cc
elf/arch-ppc32.cc
elf/arch-ppc64v1.cc
elf/arch-ppc64v2.cc
elf/arch-riscv.cc
elf/arch-s390x.cc
elf/arch-sh4.cc
elf/arch-sparc64.cc
elf/arch-x86-64.cc
elf/elf.cc
filepath.cc
git-hash.cc
glob.cc
hyperloglog.cc
main.cc
multi-glob.cc
perf.cc
tar.cc
third-party/rust-demangle/rust-demangle.c
uuid.cc
)
if(MOLD_IS_SOLD)
@@ -421,9 +438,15 @@ 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()
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.third-party")
install(FILES "LICENSE.third-party" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
endif()
function(mold_install_relative_symlink OLD NEW)
install(CODE "
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
@@ -436,10 +459,10 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
endfunction()
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld.mold)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
-15
View File
@@ -1,15 +0,0 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "clang_cl_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
}
]
}
-44
View File
@@ -1,44 +0,0 @@
# Contributing to mold
We request all patches to mold to be released under the dual license
of the GNU AGPLv3 and the MIT license. Please read the following and
sign-off your patch as an indication of agreement.
> ## Developer's Certificate of Origin
>
> By making a contribution to this project, I certify that:
>
> 1. The contribution was created in whole or in part by me and I
> have the right to submit it under the Dual License of the GNU
> AGPLv3 and the MIT license; or
>
> 2. The contribution is based upon previous work that, to the best
> of my knowledge, is covered under the the Dual License and I
> have the right under the Dual License to submit that work with
> modifications, whether created in whole or in part by me, under
> the Dual License (unless I am permitted to submit under
> different licenses), as indicated in the file; or
>
> 3. The contribution was provided directly to me by some other
> person who certified (1), (2) or (3) and I have not modified it.
>
> 4. I understand and agree that this project and the contribution
> are public and that a record of the contribution (including all
> personal information I submit with it, including my sign-off) is
> maintained indefinitely and may be redistributed consistent with
> this project or the open source license(s) involved.
If you agree, you just add a line saying:
```
Signed-off-by: Random J Developer <[email protected]>
```
using your real name (sorry, no pseudonyms or anonymous contributions.)
This will be done for you automatically if you use ``git commit -s``.
Reverts should also include "Signed-off-by". ``git revert -s`` does that
for you.
We assume that any contribution that's 10 lines or less does not meet
the threshold of originality and therefore copyright does not apply.
You don't need to add a "Signed-off-by" line to such small patches.
+1 -1005
View File
File diff suppressed because it is too large. Load diff
+348
View File
@@ -0,0 +1,348 @@
Mold includes a number of subcomponents with separate copyright notices
and license terms. Your use of the source code for the these subcomponents
is subject to the terms and conditions of the following licenses.
=== For the third-party/mimalloc component:
MIT License
Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=== For the third-party/rust-demangle component:
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
=== For the third-party/tbb component:
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=== For the third-party/xxhash component:
xxHash Library
Copyright (c) 2012-2020 Yann Collet
All rights reserved.
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=== For the third-party/zlib component:
(C) 1995-2022 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
[email protected] [email protected]
=== For the third-party/zstd component:
BSD License
For Zstandard software
Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+114 -167
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 is a repo of a free, AGPL-licensed version of the linker.
If you are looking for a commercial, non-AGPL version of the same linker,
<i>This repository contains a free version of the mold linker.
If you are looking for a commercial version that supports macOS
please visit the
[repo of the sold linker](https://github.com/bluewhalesystems/sold).</i>
[repository of the sold linker](https://github.com/bluewhalesystems/sold).</i>
mold is a faster drop-in replacement for existing Unix linkers.
It is several times faster than the LLVM lld linker, the second-fastest
open-source linker which I originally created a few years ago.
mold is designed to increase developer productivity by reducing
build time, especially in rapid debug-edit-rebuild cycles.
mold is a faster drop-in replacement for existing Unix linkers. It is several
times quicker than the LLVM lld linker, the second-fastest open-source linker,
which I initially developed a few years ago. mold aims to enhance developer
productivity by minimizing build time, particularly in rapid
debug-edit-rebuild cycles.
Here is a performance comparison of GNU gold, LLVM lld, and mold for
linking final debuginfo-enabled executables of major large programs
on a simulated 8-core 16-threads machine.
Here is a performance comparison of GNU gold, LLVM lld, and mold when linking
final debuginfo-enabled executables for major large programs on a simulated
8-core, 16-thread machine.
![Link speed comparison](docs/comparison.png)
@@ -26,48 +26,47 @@ on a simulated 8-core 16-threads machine.
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
mold is so fast that it is only 2x _slower_ than `cp` on the same
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
if you find mold is not faster than other linkers.
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
machine. If you find that mold is not faster than other linkers, please feel
free to [file a bug report](https://github.com/rui314/mold/issues).
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian PowerPC ELFv2,
s390x, SPARC64 and m68k.
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, SH-4, and DEC
Alpha.
mold/macOS is commercial software. For mold/macOS, please visit
https://github.com/bluewhalesystems/sold.
## Why does the speed of linking matter?
## Why does linking speed matter?
If you are using a compiled language such as C, C++ or Rust, a build
consists of two phases. In the first phase, a compiler compiles
source files into object files (`.o` files). In the second phase,
a linker takes all object files to combine them into a single executable
or a shared library file.
If you are using a compiled language such as C, C++, or Rust, a build consists
of two phases. In the first phase, a compiler compiles source files into
object files (`.o` files). In the second phase, a linker takes all object
files and combines them into a single executable or shared library file.
The second phase takes a long time if your build output is large.
mold can make it faster, saving your time and keeping you from being
distracted while waiting for a long build to finish. The difference is
most noticeable when you are in rapid debug-edit-rebuild cycles.
The second phase can be time-consuming if your build output is large. mold can
speed up this process, saving you time and preventing distractions while
waiting for a lengthy build to finish. The difference is most noticeable
during rapid debug-edit-rebuild cycles.
## Install
## Installation
Binary packages for the following systems are currently available.
Binary packages for the following systems are currently available:
[![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 need a
recent version of a C++ compiler and a C++ standard library. GCC 10.2
or Clang 12.0.0 (or later) as well as libstdc++ 10 or libc++ 7 (or
later) are recommended.
mold is written in C++20, so if you build mold yourself, you will need a
recent version of a C++ compiler and a C++ standard library. We recommend GCC
10.2 or Clang 12.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
### Install dependencies
### Install Dependencies
To install build dependencies, run `./install-build-deps.sh` in this
directory. It recognizes your Linux distribution and tries to install
necessary packages. You may want to run it as root.
directory. It will detect your Linux distribution and attempt to install the
necessary packages. You may need to run it as root.
### Compile mold
@@ -75,59 +74,58 @@ necessary packages. You may want to run it as root.
git clone https://github.com/rui314/mold.git
mkdir mold/build
cd mold/build
git checkout v1.8.0
git checkout v2.1.0
../install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
cmake --build . -j $(nproc)
sudo cmake --install .
```
You may need to pass a C++20 compiler command name to `cmake`.
In the above case, `c++` is passed. If it doesn't work for you,
try a specific version of a compiler such as `g++-10` or `clang++-12`.
You might need to pass a C++20 compiler command name to `cmake`. In the
example above, `c++` is passed. If that doesn't work for you, try a specific
version of a compiler, such as `g++-10` or `clang++-12`.
By default, `mold` is installed to `/usr/local/bin`. You can change
that by passing `-DCMAKE_INSTALL_PREFIX=<directory>`. For other cmake
options, see the comments in `CMakeLists.txt`.
By default, `mold` is installed to `/usr/local/bin`. You can change the
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
For other cmake options, see the comments in `CMakeLists.txt`.
If you don't use a recent enough Linux distribution, or if for any reason
`cmake` in the above commands doesn't work for you, you can use Docker to
build it in a Docker environment. To do so, just run `./dist.sh` in this
directory instead of `cmake`. The shell script pulls a Docker image,
builds mold and auxiliary files inside it, and packs them into a
single tar file `mold-$version-$arch-linux.tar.gz`. You can extract
the tar file anywhere and use `mold` executable in it.
If you are not using a recent enough Linux distribution, or if `cmake` does
not work for you for any reason, you can use Docker to build mold in a Docker
environment. To do so, run `./dist.sh` in this directory instead of using
`cmake`. The shell script will pull a Docker image, build mold and auxiliary
files inside it, and package them into a single tar file named
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere and
use the mold executable within it.
## How to use
<details><summary>A classic way to use mold</summary>
On Unix, the linker command (which is usually `/usr/bin/ld`) is
invoked indirectly by the compiler driver (which is usually `cc`,
`gcc` or `clang`), which is typically in turn indirectly invoked by
`make` or some other build system command.
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
indirectly invoked by `make` or another build system command.
If you can specify an additional command line option to your compiler
driver by modifying build system's config files, add one of the
following flags to use `mold` instead of `/usr/bin/ld`:
If you can specify an additional command line option for your compiler driver
by modifying the build system's config files, add one of the following flags
to use mold instead of `/usr/bin/ld`:
- Clang: pass `-fuse-ld=mold`
- For Clang: pass `-fuse-ld=mold`
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
argument, so you need to use `-B` option instead. `-B` is an option
to tell GCC where to look for external commands such as `ld`.
- For GCC before 12.1.0: the `-fuse-ld` option does not accept `mold` as a
valid argument, so you need to use the `-B` option instead. The `-B` option
tells GCC where to look for external commands like `ld`.
If you have installed mold with `make install`, there should be a
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
depending on your `$PREFIX`), and `ld` command should be there. The
`ld` is actually a symlink to `mold`. So, all you need is to pass
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
If you have installed mold with `make install`, there should be a directory
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
`-B/usr/local/libexec/mold`) to GCC.
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
take an absolute path as an argument for `-fuse-ld` though.
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
accept an absolute path as an argument for `-fuse-ld`.
</details>
@@ -141,32 +139,19 @@ linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
where `/path/to/mold` is an absolute path to `mold` exectuable. In the
above example, we use `clang` as a linker driver as it can always take
the `-fuse-ld` option. If your GCC is recent enough to recognize the
option, you may be able to remove the `linker = "clang"` line.
where `/path/to/mold` is an absolute path to the mold executable. In the
example above, we use `clang` as a linker driver since it always accepts the
`-fuse-ld` option. If your GCC is recent enough to recognize the option, you
may be able to remove the `linker = "clang"` line.
```toml
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
If you want to use mold for all projects, put the above snippet to
If you want to use mold for all projects, add the above snippet to
`~/.cargo/config.toml`.
If you are using macOS, you can modify `config.toml` in a similar manner.
Here is an example with `mold` installed via [Homebrew](https://brew.sh).
```toml
[target.x86_64-apple-darwin]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.aarch64-apple-darwin]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
</details>
<details><summary>If you are using Nim</summary>
@@ -178,67 +163,46 @@ when findExe("mold").len > 0 and defined(linux):
switch("passL", "-fuse-ld=mold")
```
where `mold` must be included in the PATH environment variable. In this example
The above example uses `gcc` as the linker driver.
Use the `fuse-ld` option. If your GCC is recent enough to recognize this option.
where `mold` must be included in the `PATH` environment variable. In this
example, `gcc` is used as the linker driver. Use the `-fuse-ld` option if your
GCC is recent enough to recognize this option.
If you want to use mold for all projects, put the above snippet to `~/.config/config.nims`.
If you are using macOS, you can modify config.nims in a similar manner. Here is an example with mold installed via Homebrew.
```nim
when findExe("ld64.mold").len > 0 and defined(macosx):
switch("passL", "-fuse-ld=ld64.mold")
```
If you want to use mold for all projects, add the above snippet to
`~/.config/config.nims`.
</details>
<details><summary>mold -run</summary>
It is sometimes very hard to pass an appropriate command line option
to `cc` to specify an alternative linker. To deal with the situation,
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
`ld.gold` and redirect it to itself. To use the feature, run `make`
(or another build command) as a subcommand of mold as follows:
It is sometimes very hard to pass an appropriate command line option to `cc`
to specify an alternative linker. To address this situation, mold has a
feature to intercept all invocations of `ld`, `ld.lld`, or `ld.gold` and
redirect them to itself. To use this feature, run `make` (or another build
command) as a subcommand of mold as follows:
```shell
mold -run make <make-options-if-any>
```
Internally, mold invokes a given command with `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object
file intercepts all function calls to `exec(3)`-family functions to
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
</details>
<details><summary>On macOS</summary>
mold/macOS is available as an alpha version. It can be used to build not
only macOS apps but also iOS apps because their binary formats are the same.
The command name of mold/macOS is `ld64.mold`. If you build mold on macOS,
it still produces `mold` and `ld.mold`, but these executables are useful
only for cross compilation (i.e. building Linux apps on macOS.)
If you find any issue with mold/macOS, please file it to
<a href=https://github.com/rui314/mold/issues>our GitHub Issues</a>.
Internally, mold invokes the given command with the `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object file
intercepts all function calls to `exec(3)`-family functions to replace
`argv[0]` with `mold` if it is `ld`, `ld.gold`, or `ld.lld`.
</details>
<details><summary>GitHub Actions</summary>
You can use our <a href=https://github.com/rui314/setup-mold>setup-mold</a>
GitHub Action to speed up GitHub-hosted continuous build. GitHub Actions
runs on a two-core machine, but mold is still significantly faster than
the default GNU linker there especially when a program being linked is
large.
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
run on a two-core machine, mold is still significantly faster than the default
GNU linker, especially when linking large programs.
</details>
<details><summary>Verify that you are using mold</summary>
mold leaves its identification string in `.comment` section in an output
mold leaves its identification string in the `.comment` section of an output
file. You can print it out to verify that you are actually using mold.
```shell
@@ -249,70 +213,53 @@ String dump of section '.comment':
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
```
If `mold` is in `.comment`, the file is created by mold.
If `mold` is present in the `.comment` section, the file was created by mold.
</details>
<details><summary>Online manual</summary>
Since mold is a drop-in replacement, you should be able to use it
without reading its manual. But just in case you need it, it's available
online at <a href=https://rui314.github.io/mold.html>here</a>.
You can also read the same manual by `man mold`.
Since mold is a drop-in replacement, you should be able to use it without
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
is available. You can read the same manual by running `man mold`.
</details>
## Why is mold so fast?
One reason is because it simply uses faster algorithms and efficient
data structures than other linkers do. The other reason is that the
new linker is highly parallelized.
One reason is that it utilizes faster algorithms and more efficient data
structures compared to other linkers. Another reason is that mold is highly
parallelized.
Here is a side-by-side comparison of per-core CPU usage of lld (left)
and mold (right). They are linking the same program, Chromium
executable.
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
mold (right), linking the same program, a Chromium executable.
![CPU usage comparison in htop animation](docs/htop.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.
As you can see, mold uses all available cores throughout its execution and
finishes quickly. In contrast, lld fails to utilize available cores most of
the time. In this demo, the maximum parallelism is artificially capped at 16,
so that the bars fit in the GIF.
For details, please read [design notes](docs/design.md).
## License
mold is available under AGPL. Note that that does not mean that you
have to license your program under AGPL if you use mold to link your
program. An output of the mold linker is a derived work of the object
files and libraries you pass to the linker but not a derived work of
the mold linker itself.
Besides that, you can also buy a commercial, non-AGPL license with
technical support from our company, Blue Whale Systems PTE LTD. If you
are a big company, please consider obtaining it before making hundreds
or thousands of developers of your company to depend on mold. mold is
mostly a single-person open-source project, and just like other
open-source projects, we are not legally obligated to keep maintaining
it. A legally-binding commercial license contract addresses the
concern. By purchasing a license, you are guaranteed that mold will be
maintained for you. Please [contact us](mailto:[email protected])
for a commercial license inquiry.
For details, please see the [design notes](docs/design.md).
## Sponsors
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
and [OpenCollective](https://opencollective.com/mold-linker).
We thank you to everybody who sponsors our project. In particular,
we'd like to acknowledge the following people and organizations who
have sponsored $128/month or more:
and [OpenCollective](https://opencollective.com/mold-linker). We thank
everyone who sponsors our project. In particular, we'd like to acknowledge the
following people and organizations who have sponsored $128/month or more:
### Corporate sponsors
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
<a href="https://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>
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
- [Uber](https://uber.com)
- [G-Research](https://www.gresearch.co.uk)
- [Signal Slot Inc.](https://github.com/signal-slot)
### Individual sponsors
View File
File renamed without changes.
+12 -14
View File
@@ -25,7 +25,7 @@
#pragma once
#include "mold.h"
#include "common.h"
#include "filetype.h"
namespace mold {
@@ -76,12 +76,12 @@ struct ArHdr {
}
};
template <typename C>
std::vector<MappedFile<C> *>
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
template <typename Context, typename MappedFile>
std::vector<MappedFile *>
read_thin_archive_members(Context &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile<C> *> vec;
std::vector<MappedFile *> vec;
std::string_view strtab;
while (data < begin + mf->size) {
@@ -117,19 +117,18 @@ read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
std::string path = name.starts_with('/') ?
name : (filepath(mf->name).parent_path() / name).string();
vec.push_back(MappedFile<C>::must_open(ctx, path));
vec.push_back(MappedFile::must_open(ctx, path));
vec.back()->thin_parent = mf;
data = body;
}
return vec;
}
template <typename C>
std::vector<MappedFile<C> *>
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
template <typename Context, typename MappedFile>
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile<C> *> vec;
std::vector<MappedFile *> vec;
std::string_view strtab;
while (begin + mf->size - data >= 2) {
@@ -163,10 +162,9 @@ read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
return vec;
}
template <typename C>
std::vector<MappedFile<C> *>
read_archive_members(C &ctx, MappedFile<C> *mf) {
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
template <typename Context, typename MappedFile>
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
switch (get_file_type(ctx, mf)) {
case FileType::AR:
return read_fat_archive_members(ctx, mf);
case FileType::THIN_AR:
+6 -6
View File
@@ -1,14 +1,14 @@
#pragma once
#include "mold.h"
#include "common.h"
namespace mold {
template <typename C>
template <typename Context>
std::vector<std::string_view>
read_response_file(C &ctx, std::string_view path) {
read_response_file(Context &ctx, std::string_view path) {
std::vector<std::string_view> vec;
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
u8 *data = mf->data;
auto read_quoted = [&](i64 i, char quote) {
@@ -63,8 +63,8 @@ read_response_file(C &ctx, std::string_view path) {
}
// Replace "@path/to/some/text/file" with its file contents.
template <typename C>
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
template <typename Context>
std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
std::vector<std::string_view> vec;
for (i64 i = 0; argv[i]; i++) {
if (argv[i][0] == '@')
+160 -78
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,6 +40,23 @@
# define unreachable() assert(0 && "unreachable")
#endif
// __builtin_assume() is supported only by clang, and [[assume]] is
// available only in C++23, so we use this macro when giving a hint to
// the compiler's optimizer what's true.
#define ASSUME(x) do { if (!(x)) __builtin_unreachable(); } while (0)
// This is an assert() that is enabled even in the release build.
#define ASSERT(x) \
do { \
if (!(x)) { \
std::cerr << "Assertion failed: (" << #x \
<< "), function " << __FUNCTION__ \
<< ", file " << __FILE__ \
<< ", line " << __LINE__ << ".\n"; \
std::abort(); \
} \
} while (0)
inline uint64_t hash_string(std::string_view str) {
return XXH3_64bits(str.data(), str.size());
}
@@ -60,7 +77,7 @@ namespace mold {
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
template <typename C> class OutputFile;
template <typename Context> class OutputFile;
inline char *output_tmpfile;
inline thread_local bool opt_demangle;
@@ -86,41 +103,44 @@ static u64 combine_hash(u64 a, u64 b) {
// Error output
//
template <typename C>
template <typename Context>
class SyncOut {
public:
SyncOut(C &ctx, std::ostream &out = std::cout) : out(out) {
SyncOut(Context &ctx, std::ostream *out = &std::cout) : out(out) {
opt_demangle = ctx.arg.demangle;
}
~SyncOut() {
std::scoped_lock lock(mu);
out << ss.str() << "\n";
if (out) {
std::scoped_lock lock(mu);
*out << ss.str() << "\n";
}
}
template <class T> SyncOut &operator<<(T &&val) {
ss << std::forward<T>(val);
if (out)
ss << std::forward<T>(val);
return *this;
}
static inline std::mutex mu;
private:
std::ostream &out;
std::ostream *out;
std::stringstream ss;
};
template <typename C>
static std::string add_color(C &ctx, std::string msg) {
template <typename Context>
static std::string add_color(Context &ctx, std::string msg) {
if (ctx.arg.color_diagnostics)
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
return "mold: " + msg + ": ";
}
template <typename C>
template <typename Context>
class Fatal {
public:
Fatal(C &ctx) : out(ctx, std::cerr) {
Fatal(Context &ctx) : out(ctx, &std::cerr) {
out << add_color(ctx, "fatal");
}
@@ -136,13 +156,13 @@ public:
}
private:
SyncOut<C> out;
SyncOut<Context> out;
};
template <typename C>
template <typename Context>
class Error {
public:
Error(C &ctx) : out(ctx, std::cerr) {
Error(Context &ctx) : out(ctx, &std::cerr) {
if (ctx.arg.noinhibit_exec) {
out << add_color(ctx, "warning");
} else {
@@ -157,13 +177,14 @@ public:
}
private:
SyncOut<C> out;
SyncOut<Context> out;
};
template <typename C>
template <typename Context>
class Warn {
public:
Warn(C &ctx) : out(ctx, std::cerr) {
Warn(Context &ctx)
: out(ctx, ctx.arg.suppress_warnings ? nullptr : &std::cerr) {
if (ctx.arg.fatal_warnings) {
out << add_color(ctx, "error");
ctx.has_error = true;
@@ -178,7 +199,64 @@ public:
}
private:
SyncOut<C> out;
SyncOut<Context> out;
};
//
// Atomics
//
// This is the same as std::atomic except that the default memory
// order is relaxed instead of sequential consistency.
template <typename T>
struct Atomic : std::atomic<T> {
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
using std::atomic<T>::atomic;
Atomic(const Atomic<T> &other) { store(other.load()); }
Atomic<T> &operator=(const Atomic<T> &other) {
store(other.load());
return *this;
}
void operator=(T val) { store(val); }
operator T() const { return load(); }
void store(T val) { std::atomic<T>::store(val, relaxed); }
T load() const { return std::atomic<T>::load(relaxed); }
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
bool test_and_set() {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return load() || exchange(true);
}
};
//
// Bit vector
//
class BitVector {
public:
BitVector() = default;
BitVector(u32 size) : vec((size + 7) / 8) {}
void resize(u32 size) { vec.resize((size + 7) / 8); }
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
private:
std::vector<u8> vec;
};
//
@@ -238,17 +316,6 @@ void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
std::memory_order_relaxed));
}
// An optimized "mark" operation for parallel mark-and-sweep algorithms.
// Returns true if `visited` was false and updated to true.
inline bool fast_mark(std::atomic<bool> &visited) {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return !visited.load(std::memory_order_relaxed) &&
!visited.exchange(true, std::memory_order_relaxed);
}
template <typename T, typename U>
inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
@@ -256,7 +323,12 @@ inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
template <typename T>
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
i64 size = 0;
for (std::vector<T> &v : vec)
size += v.size();
std::vector<T> ret;
ret.reserve(size);
for (std::vector<T> &v : vec)
append(ret, v);
return ret;
@@ -322,30 +394,36 @@ inline i64 write_uleb(u8 *buf, u64 val) {
return i;
}
inline u64 read_uleb(u8 *&buf) {
inline u64 read_uleb(u8 **buf) {
u64 val = 0;
u8 shift = 0;
u8 byte;
do {
byte = *buf++;
byte = *(*buf)++;
val |= (byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
return val;
}
inline u64 read_uleb(u8 const*&buf) {
return read_uleb(const_cast<u8 *&>(buf));
inline u64 read_uleb(u8 *buf) {
u8 *tmp = buf;
return read_uleb(&tmp);
}
inline u64 read_uleb(std::string_view &str) {
u8 *start = (u8 *)&str[0];
inline u64 read_uleb(std::string_view *str) {
u8 *start = (u8 *)str->data();
u8 *ptr = start;
u64 val = read_uleb(ptr);
str = str.substr(ptr - start);
u64 val = read_uleb(&ptr);
*str = str->substr(ptr - start);
return val;
}
inline u64 read_uleb(std::string_view str) {
std::string_view tmp = str;
return read_uleb(&tmp);
}
inline i64 uleb_size(u64 val) {
#if __GNUC__
#pragma GCC unroll 8
@@ -356,8 +434,16 @@ inline i64 uleb_size(u64 val) {
return 9;
}
template <typename C>
std::string_view save_string(C &ctx, const std::string &str) {
inline void overwrite_uleb(u8 *loc, u64 val) {
while (*loc & 0b1000'0000) {
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
val >>= 7;
}
*loc = val & 0b0111'1111;
}
template <typename Context>
std::string_view save_string(Context &ctx, const std::string &str) {
u8 *buf = new u8[str.size() + 1];
memcpy(buf, str.data(), str.size());
buf[str.size()] = '\0';
@@ -405,9 +491,9 @@ public:
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
this->nbuckets = nbuckets;
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
key_sizes = (u32 *)calloc(nbuckets, sizeof(key_sizes[0]));
values = (T *)calloc(nbuckets, sizeof(values[0]));
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(char *));
key_sizes = (u32 *)malloc(nbuckets * sizeof(u32));
values = (T *)malloc(nbuckets * sizeof(T));
}
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
@@ -427,7 +513,7 @@ public:
if (ptr == nullptr) {
if (!keys[idx].compare_exchange_weak(ptr, marker,
std::memory_order_acq_rel))
std::memory_order_acquire))
continue;
new (values + idx) T(val);
key_sizes[idx] = key.size();
@@ -448,7 +534,7 @@ public:
return {nullptr, false};
}
bool has_key(i64 idx) {
const char *get_key(i64 idx) {
return keys[idx].load(std::memory_order_relaxed);
}
@@ -457,7 +543,6 @@ public:
static constexpr i64 MAX_RETRY = 128;
i64 nbuckets = 0;
std::atomic<const char *> *keys = nullptr;
u32 *key_sizes = nullptr;
T *values = nullptr;
@@ -470,6 +555,8 @@ private:
#endif
}
private:
std::atomic<const char *> *keys = nullptr;
static constexpr const char *marker = "marker";
};
@@ -477,13 +564,13 @@ private:
// output-file.h
//
template <typename C>
template <typename Context>
class OutputFile {
public:
static std::unique_ptr<OutputFile<C>>
open(C &ctx, std::string path, i64 filesize, i64 perm);
static std::unique_ptr<OutputFile<Context>>
open(Context &ctx, std::string path, i64 filesize, i64 perm);
virtual void close(C &ctx) = 0;
virtual void close(Context &ctx) = 0;
virtual ~OutputFile() = default;
u8 *buf = nullptr;
@@ -646,13 +733,13 @@ public:
}
Counter &operator++(int) {
if (enabled)
if (enabled) [[unlikely]]
values.local()++;
return *this;
}
Counter &operator+=(int delta) {
if (enabled)
if (enabled) [[unlikely]]
values.local() += delta;
return *this;
}
@@ -689,10 +776,10 @@ struct TimerRecord {
void
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
template <typename C>
template <typename Context>
class Timer {
public:
Timer(C &ctx, std::string name, Timer *parent = nullptr) {
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
record = new TimerRecord(name, parent ? parent->record : nullptr);
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
}
@@ -743,16 +830,16 @@ private:
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
template <typename C>
template <typename Context>
class MappedFile {
public:
static MappedFile *open(C &ctx, std::string path);
static MappedFile *must_open(C &ctx, std::string path);
static MappedFile *open(Context &ctx, std::string path);
static MappedFile *must_open(Context &ctx, std::string path);
~MappedFile() { unmap(); }
void unmap();
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size);
std::string_view get_contents() {
return std::string_view((char *)data, size);
@@ -782,7 +869,6 @@ public:
std::string name;
u8 *data = nullptr;
i64 size = 0;
i64 mtime = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
@@ -792,8 +878,8 @@ public:
#endif
};
template <typename C>
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
template <typename Context>
MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
@@ -804,8 +890,11 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
fd = ::open(path.c_str(), O_RDONLY);
#endif
if (fd == -1)
if (fd == -1) {
if (errno != ENOENT)
Fatal(ctx) << "opening " << path << " failed: " << errno_string();
return nullptr;
}
struct stat st;
if (fstat(fd, &st) == -1)
@@ -817,14 +906,6 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
mf->name = path;
mf->size = st.st_size;
#ifdef _WIN32
mf->mtime = st.st_mtime;
#elif defined(__APPLE__)
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
#else
mf->mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
#endif
if (st.st_size > 0) {
#ifdef _WIN32
HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
@@ -848,17 +929,18 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
return mf;
}
template <typename C>
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
template <typename Context>
MappedFile<Context> *
MappedFile<Context>::must_open(Context &ctx, std::string path) {
if (MappedFile *mf = MappedFile::open(ctx, path))
return mf;
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
}
template <typename C>
MappedFile<C> *
MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile<C>;
template <typename Context>
MappedFile<Context> *
MappedFile<Context>::slice(Context &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile;
mf->name = name;
mf->data = data + start;
mf->size = size;
@@ -868,8 +950,8 @@ MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
return mf;
}
template <typename C>
void MappedFile<C>::unmap() {
template <typename Context>
void MappedFile<Context>::unmap() {
if (size == 0 || parent || !data)
return;
+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 "mold.h"
#include "common.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 "mold.h"
#include "common.h"
#include <cstdlib>
@@ -6,7 +6,7 @@
#include <cxxabi.h>
#endif
#include "third-party/rust-demangle/rust-demangle.h"
#include "../third-party/rust-demangle/rust-demangle.h"
namespace mold {
+5 -3
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include <filesystem>
#include <sys/stat.h>
@@ -7,8 +7,10 @@ namespace mold {
std::string get_realpath(std::string_view path) {
std::error_code ec;
std::string ret = std::filesystem::canonical(path, ec).string();
return ec ? std::string(path) : ret;
std::filesystem::path link = std::filesystem::read_symlink(path, ec);
if (ec)
return std::string(path);
return (filepath(path) / ".." / link).lexically_normal().string();
}
// Removes redundant '/..' or '/.' from a given path.
+24 -22
View File
@@ -1,7 +1,7 @@
#pragma once
#include "mold.h"
#include "elf/elf.h"
#include "common.h"
#include "../elf/elf.h"
namespace mold {
@@ -23,15 +23,15 @@ enum class FileType {
LLVM_BITCODE,
};
template <typename C>
bool is_text_file(MappedFile<C> *mf) {
template <typename MappedFile>
bool is_text_file(MappedFile *mf) {
u8 *data = mf->data;
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
isprint(data[2]) && isprint(data[3]);
}
template <typename E, typename C>
inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
template <typename E, typename Context, typename MappedFile>
inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
using namespace mold::elf;
const char *data = mf->get_contents().data();
@@ -45,21 +45,23 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
? sh_begin->sh_link : ehdr.e_shstrndx;
for (ElfShdr<E> &sec : shdrs) {
// GCC LTO object contains only sections symbols followed by a common
// symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1` for older
// GCC releases).
//
// However, FAT LTO objects don't have any of the above mentioned symbols
// and can identify LTO by `.gnu.lto_.symtab.` section, similarly
// to what lto-plugin does. However, if LTO linker plug-in is not available,
// use the emitted assembly instead.
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
if (opt_plugin && name.starts_with (".gnu.lto_.symtab."))
// GCC FAT LTO objects contain both regular ELF sections and GCC-
// specific LTO sections, so that they can be linked as LTO objects if
// the LTO linker plugin is available and falls back as regular
// objects otherwise. GCC FAT LTO object can be identified by the
// presence of `.gcc.lto_.symtab` section.
if (!ctx.arg.plugin.empty()) {
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
if (name.starts_with(".gnu.lto_.symtab."))
return true;
}
if (sec.sh_type != SHT_SYMTAB)
continue;
// GCC non-FAT LTO object contains only sections symbols followed by
// a common symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1`
// for older GCC releases).
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
@@ -83,8 +85,8 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
return false;
}
template <typename C>
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
template <typename Context, typename MappedFile>
FileType get_file_type(Context &ctx, MappedFile *mf) {
using namespace elf;
std::string_view data = mf->get_contents();
@@ -100,10 +102,10 @@ FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<I386>(mf, opt_plugin))
if (is_gcc_lto_obj<I386>(ctx, mf))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<X86_64>(mf, opt_plugin))
if (is_gcc_lto_obj<X86_64>(ctx, mf))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
@@ -116,10 +118,10 @@ FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<M68K>(mf, opt_plugin))
if (is_gcc_lto_obj<M68K>(ctx, mf))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<SPARC64>(mf, opt_plugin))
if (is_gcc_lto_obj<SPARC64>(ctx, mf))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
+1 -1
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.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 "mold.h"
#include "common.h"
#include <cmath>
@@ -13,7 +13,7 @@ namespace mold {
i64 HyperLogLog::get_cardinality() const {
double z = 0;
for (i64 val : buckets)
z += pow(2, -val);
z += std::ldexp(1.0, -val);
return ALPHA * NBUCKETS * NBUCKETS / z;
}
View File
File renamed without changes.
+20 -7
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include "config.h"
#include <cstring>
@@ -110,14 +110,26 @@ static void sighandler(int signo, siginfo_t *info, void *ucontext) {
static std::mutex mu;
std::scoped_lock lock{mu};
if ((signo == SIGSEGV || signo == SIGBUS) &&
output_buffer_start <= info->si_addr &&
info->si_addr < output_buffer_end) {
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
switch (signo) {
case SIGSEGV:
case SIGBUS:
if (output_buffer_start <= info->si_addr &&
info->si_addr < output_buffer_end) {
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
}
break;
case SIGABRT: {
const char msg[] =
"mold: aborted\n"
"mold: If mold failed due to a spurious failure of pthread_create, "
"it's likely because of https://github.com/oneapi-src/oneTBB/pull/824. "
"You should ensure that you are using 2021.9.0 or newer version of libtbb.\n";
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
break;
}
}
cleanup();
_exit(1);
}
@@ -127,6 +139,7 @@ void install_signal_handler() {
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO;
sigaction(SIGABRT, &action, NULL);
sigaction(SIGINT, &action, NULL);
sigaction(SIGTERM, &action, NULL);
sigaction(SIGBUS, &action, NULL);
@@ -148,7 +161,7 @@ int main(int argc, char **argv) {
#if MOLD_IS_SOLD
std::string cmd = mold::filepath(argv[0]).filename().string();
if (cmd == "ld64" || cmd == "ld64.mold")
if (cmd == "ld64" || cmd.starts_with("ld64."))
return mold::macho::main(argc, argv);
#endif
+1 -1
View File
@@ -17,7 +17,7 @@
// slow, but complex patterns are rare in practice, so it should be
// OK.
#include "mold.h"
#include "common.h"
#include <queue>
#include <regex>
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include <fcntl.h>
#include <filesystem>
@@ -14,9 +14,9 @@ inline u32 get_umask() {
return orig_umask;
}
template <typename C>
template <typename Context>
static std::pair<i64, char *>
open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
open_or_create_file(Context &ctx, std::string path, i64 filesize, i64 perm) {
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
char *path2 = (char *)save_string(ctx, tmpl).data();
@@ -47,11 +47,11 @@ open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
return {fd, path2};
}
template <typename C>
class MemoryMappedOutputFile : public OutputFile<C> {
template <typename Context>
class MemoryMappedOutputFile : public OutputFile<Context> {
public:
MemoryMappedOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, true) {
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, true) {
i64 fd;
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
@@ -70,7 +70,7 @@ public:
::close(fd2);
}
void close(C &ctx) override {
void close(Context &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
@@ -92,18 +92,18 @@ private:
int fd2 = -1;
};
template <typename C>
class MallocOutputFile : public OutputFile<C> {
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
public:
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, false), perm(perm) {
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), perm(perm) {
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << "mmap failed: " << errno_string();
}
void close(C &ctx) override {
void close(Context &ctx) override {
Timer t(ctx, "close_file");
if (this->path == "-") {
@@ -125,9 +125,9 @@ private:
i64 perm;
};
template <typename C>
std::unique_ptr<OutputFile<C>>
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
@@ -142,11 +142,11 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
is_special = true;
}
OutputFile<C> *file;
OutputFile<Context> *file;
if (is_special)
file = new MallocOutputFile<C>(ctx, path, filesize, perm);
file = new MallocOutputFile(ctx, path, filesize, perm);
else
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
#ifdef MADV_HUGEPAGE
// Enable transparent huge page for an output memory-mapped file.
@@ -163,7 +163,7 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile<C>>(file);
return std::unique_ptr<OutputFile>(file);
}
} // namespace mold
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include <fcntl.h>
#include <filesystem>
@@ -6,17 +6,17 @@
namespace mold {
template <typename C>
class MallocOutputFile : public OutputFile<C> {
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
public:
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, false), perm(perm) {
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), perm(perm) {
this->buf = (u8 *)malloc(filesize);
if (!this->buf)
Fatal(ctx) << "malloc failed";
}
void close(C &ctx) override {
void close(Context &ctx) override {
Timer t(ctx, "close_file");
if (this->path == "-") {
@@ -39,19 +39,19 @@ private:
i64 perm;
};
template <typename C>
std::unique_ptr<OutputFile<C>>
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
OutputFile<C> *file = new MallocOutputFile<C>(ctx, path, filesize, perm);
OutputFile<Context> *file = new MallocOutputFile(ctx, path, filesize, perm);
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile<C>>(file);
return std::unique_ptr<OutputFile<Context>>(file);
}
} // namespace mold
File renamed without changes.
+6 -6
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include <functional>
#include <iomanip>
@@ -31,7 +31,7 @@ static i64 now_nsec() {
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (i64)t.tv_sec * 1000000000 + t.tv_nsec;
return (i64)t.tv_sec * 1'000'000'000 + t.tv_nsec;
#endif
}
@@ -46,7 +46,7 @@ static std::pair<i64, i64> get_usage() {
return {to_nsec(user), to_nsec(kernel)};
#else
auto to_nsec = [](struct timeval t) -> i64 {
return (i64)t.tv_sec * 1000000000 + t.tv_usec * 1000;
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
};
struct rusage ru;
@@ -79,9 +79,9 @@ void TimerRecord::stop() {
static void print_rec(TimerRecord &rec, i64 indent) {
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
((double)rec.user / 1000000000),
((double)rec.sys / 1000000000),
(((double)rec.end - rec.start) / 1000000000),
((double)rec.user / 1'000'000'000),
((double)rec.sys / 1'000'000'000),
(((double)rec.end - rec.start) / 1'000'000'000),
std::string(indent * 2, ' ').c_str(),
rec.name.c_str());
View File
File renamed without changes.
+8 -2
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
namespace mold {
@@ -24,7 +24,10 @@ struct UstarHeader {
int sum = 0;
for (i64 i = 0; i < sizeof(*this); i++)
sum += ((u8 *)this)[i];
assert(sum < 01000000);
// We need to convince the compiler that sum isn't too big to silence
// -Werror=format-truncation.
ASSUME(sum < 01'000'000);
snprintf(checksum, sizeof(checksum), "%06o", sum);
}
@@ -47,6 +50,8 @@ struct UstarHeader {
char pad[12];
};
static_assert(sizeof(UstarHeader) == 512);
static std::string encode_path(std::string basedir, std::string path) {
path = path_clean(basedir + "/" + path);
@@ -78,6 +83,7 @@ void TarWriter::append(std::string path, std::string_view data) {
std::string attr = encode_path(basedir, path);
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
pax.name[0] = '/';
pax.typeflag[0] = 'x';
pax.finalize();
fwrite(&pax, sizeof(pax), 1, out);
File renamed without changes.
+1 -1
View File
@@ -1,4 +1,4 @@
#include "mold.h"
#include "common.h"
#include <random>
+12 -5
View File
@@ -7,20 +7,27 @@
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
# and librt, as they almost always exist on any Linux systems.
if [ $# -ne 1 ]; then
case $# in
0)
arch=$(uname -m)
[[ $arch = arm* ]] && arch=arm
;;
1)
arch="$1"
;;
*)
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
exit 1
fi
esac
arch=$1
echo $arch | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
{ echo "Error: no docker image for $arch"; exit 1; }
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
dest=mold-$version-$arch-linux
set -e -x
docker run --platform linux/$arch -it --rm -v "$(pwd):/mold" \
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" \
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
bash -c "mkdir /tmp/build &&
cd /tmp/build &&
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+495 -1239
View File
File diff suppressed because it is too large. Load diff
+802
View File
@@ -0,0 +1,802 @@
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 sectionheader`, `-z nosectionheader`:
`-z nosectionheader` tell the linker to omit the section header.
By default, the linker does not omit the section header.
* `-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>.
+25 -37
View File
@@ -62,8 +62,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -103,7 +103,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_ALPHA_REFQUAD:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_ALPHA_GPREL32:
*(ul32 *)loc = S + A - GP;
@@ -166,17 +166,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -213,16 +208,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on Alpha";
@@ -231,29 +221,30 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_dyn_absrel(ctx, sym, rel);
break;
case R_ALPHA_LITERAL:
if (rel.r_addend) {
sym.flags.fetch_or(NEEDS_ALPHA_GOT, std::memory_order_relaxed);
if (rel.r_addend)
ctx.extra.got->add_symbol(sym, rel.r_addend);
} else {
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
}
else
sym.flags |= NEEDS_GOT;
break;
case R_ALPHA_SREL32:
scan_pcrel(ctx, sym, rel);
break;
case R_ALPHA_BRSGP:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_ALPHA_TLSGD:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_ALPHA_TLSLDM:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_ALPHA_GOTTPREL:
sym.flags |= NEEDS_GOTTP;
break;
case R_ALPHA_TPRELHI:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
case R_ALPHA_TPRELLO:
check_tlsle(ctx, sym, rel);
break;
case R_ALPHA_GPREL32:
case R_ALPHA_LITUSE:
@@ -263,7 +254,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_ALPHA_GPRELLOW:
case R_ALPHA_DTPRELHI:
case R_ALPHA_DTPRELLO:
case R_ALPHA_TPRELLO:
break;
default:
Fatal(ctx) << *this << ": unknown relocation: " << rel;
@@ -273,16 +263,15 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// An R_ALPHA_LITERAL relocation may request the linker to create a GOT
// entry for an external symbol with a non-zero addend. This is an unusual
// request which we cannot find in any other targets.
// request which is not found in any other targets.
//
// Referring an external symbol with a non-zero addend is a bad practice.
// To see why, assume that we have two R_ALPHA_LITERAL relocs to symbol X
// with addend 8 and 16. To satisfy that request, we need to create not
// only one but two GOT entries for symbol X with different addends.
// Referring an external symbol with a non-zero addend is a bad practice
// because we need to create as many dynamic relocations as the number of
// distinctive addends for the same symbol.
//
// We don't want to mess up the implementation of the common GOT section
// for Alpha. So we create another GOT-like section, .alpha_got. Any GOT
// entry for an R_ALPHA_LITERAL reloc with a non-zero addends is created
// entry for an R_ALPHA_LITERAL reloc with a non-zero addend is created
// not in .got but in .alpha_got.
//
// Since .alpha_got entries are accessed relative to GP, .alpha_got
@@ -304,9 +293,9 @@ u64 AlphaGotSection::get_addr(Symbol<E> &sym, i64 addend) {
return this->shdr.sh_addr + (it - entries.begin()) * sizeof(Word<E>);
}
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) {
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) const {
i64 n = 0;
for (Entry &e : entries)
for (const Entry &e : entries)
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
n++;
return n;
@@ -319,9 +308,8 @@ void AlphaGotSection::finalize() {
}
void AlphaGotSection::copy_buf(Context<E> &ctx) {
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
reldyn_offset);
for (i64 i = 0; i < entries.size(); i++) {
Entry &e = entries[i];
@@ -330,7 +318,7 @@ void AlphaGotSection::copy_buf(Context<E> &ctx) {
if (e.sym->is_imported) {
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, e.sym->get_dynsym_idx(ctx), e.addend);
} else {
*buf = e.sym->get_addr(ctx) + e.addend;
if (ctx.arg.pic && !e.sym->is_absolute())
+107 -124
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,14 +12,16 @@
// be switched using BX (branch and mode exchange)-family instructions.
// We need to use that instructions to, for example, call a function
// encoded in Thumb from a function encoded in ARM. Sometimes, the
// linker even has to emit an interworking thunk code to switch mode.
// linker even has to emit interworking thunk code to switch mode.
//
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
// byte boundaries.
// byte boundaries. So the least significant bit of a function address
// is always 0.
//
// You can distinguish Thumb functions from ARM functions by looking
// at the least significant bit (LSB) of its "address". If LSB is 0,
// it's ARM; otherwise, Thumb.
// To distinguish Thumb functions from ARM fucntions, the LSB of a
// function address is repurposed as a boolean flag. If the LSB is 0,
// the function referred to by the address is encoded in ARM;
// otherwise, Thumb.
//
// For example, if a symbol `foo` is of type STT_FUNC and has value
// 0x2001, `foo` is a function using Thumb instructions whose address
@@ -42,8 +44,6 @@ using E = ARM32;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_ARM_NONE:
return 0;
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_TARGET1:
@@ -76,8 +76,9 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
}
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_TLS_CALL:
return sign_extend(*(ul32 *)loc & 0x00ff'ffff, 23) << 2;
return sign_extend(*(ul32 *)loc, 23) << 2;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
@@ -100,7 +101,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
return sign_extend(val, 15);
}
default:
unreachable();
return 0;
}
}
@@ -167,8 +168,8 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
write_thm_b_imm(loc, val);
break;
case R_ARM_CALL:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_PLT32:
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
@@ -230,8 +231,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
// ARM does not use .eh_frame for exception handling. Instead, it uses
// .ARM.exidx and .ARM.extab. So this function is empty.
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {}
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {}
// ARM and Thumb branch instructions can jump within ±16 MiB.
static bool is_jump_reachable(i64 val) {
@@ -247,17 +248,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks =
output_section->thunks;
auto get_trampoline_addr = [&](u64 addr) {
for (;;) {
assert(!thunks.empty());
i64 disp = output_section->shdr.sh_addr + thunks[0]->offset - addr;
auto get_tls_trampoline_addr = [&, i = 0](u64 addr) mutable {
for (; i < output_section->thunks.size(); i++) {
i64 disp = output_section->shdr.sh_addr + output_section->thunks[i]->offset -
addr;
if (is_jump_reachable(disp))
return disp;
thunks = thunks.subspan(1);
}
unreachable();
};
for (i64 i = 0; i < rels.size(); i++) {
@@ -288,15 +286,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_TARGET1:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_ARM_REL32:
*(ul32 *)loc = S + A - P;
break;
case R_ARM_THM_CALL: {
// THM_CALL relocation refers either BL or BLX instruction.
// They are different in only one bit. We need to use BL if
// the jump target is Thumb. Otherwise, use BLX.
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
@@ -304,6 +299,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
// THM_CALL relocation refers either BL or BLX instruction.
// They are different in only one bit. We need to use BL if
// the jump target is Thumb. Otherwise, use BLX.
i64 val = S + A - P;
if (is_jump_reachable(val)) {
if (T) {
@@ -332,8 +330,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_ARM_GOT_BREL:
*(ul32 *)loc = G + A;
break;
case R_ARM_CALL:
case R_ARM_PLT32: {
case R_ARM_CALL: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
// Just like THM_CALL, ARM_CALL relocation refers either BL or
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
@@ -341,45 +343,53 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
u64 val = S + A - P;
if (is_jump_reachable(val)) {
if (T) {
*(ul32 *)loc = 0xfa00'0000; // BLX
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
} else {
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(val, 25, 2);
}
} else {
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
}
break;
}
case R_ARM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
// These relocs refers a B (unconditional branch) instruction.
// Unlike BL or BLX, we can't rewrite B to BX in place when the
// processor mode switch is required because BX doesn't takes an
// immediate; it takes only a register. So if mode switch is
// required, we jump to a linker-synthesized thunk which does the
// job with a longer code sequence.
u64 val = S + A - P;
if (is_jump_reachable(val)) {
if (T)
*(ul32 *)loc = 0xfa00'0000 | (bit(val, 1) << 24) | bits(val, 25, 2);
else
*(ul32 *)loc = 0xeb00'0000 | bits(val, 25, 2);
} else {
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
}
if (!is_jump_reachable(val) || T)
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
}
case R_ARM_JUMP24:
case R_ARM_PLT32:
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
} else {
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
// takes an immediate; it takes only a register. So if mode switch
// is required, we jump to a linker-synthesized thunk which constructs
// a branch destination in a register and branches to that address.
u64 val = S + A - P;
if (!is_jump_reachable(val) || T)
val = get_arm_thunk_addr() + A - P;
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP11: {
case R_ARM_THM_JUMP11:
assert(T);
i64 val = S + A - P;
check(val, -(1 << 11), 1 << 11);
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
check(S + A - P, -(1 << 11), 1 << 11);
*(ul16 *)loc &= 0xf800;
*(ul16 *)loc |= bits(S + A - P, 11, 1);
break;
}
case R_ARM_THM_JUMP19: {
i64 val = S + A - P;
check(val, -(1 << 19), 1 << 19);
@@ -398,18 +408,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
break;
}
case R_ARM_THM_JUMP24:
case R_ARM_THM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0x8000'f3af; // NOP.W
} else {
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
// switch processor mode.
u64 val = S + A - P;
if (!is_jump_reachable(val) || !T)
val = get_thumb_thunk_addr() + A - P;
write_thm_b_imm(loc, val);
*(ul32 *)loc = 0x8000'f3af; // NOP
break;
}
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
// switch processor mode.
u64 val = S + A - P;
if (!is_jump_reachable(val) || !T)
val = get_thumb_thunk_addr() + A - P;
write_thm_b_imm(loc, val);
break;
}
case R_ARM_MOVW_PREL_NC:
write_mov_imm(loc, ((S + A) | T) - P);
break;
@@ -419,12 +431,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_ARM_THM_MOVW_PREL_NC:
write_thm_mov_imm(loc, ((S + A) | T) - P);
break;
case R_ARM_PREL31: {
i64 val = S + A - P;
check(val, -(1LL << 30), 1LL << 30);
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
case R_ARM_PREL31:
check(S + A - P, -(1LL << 30), 1LL << 30);
*(ul32 *)loc &= 0x8000'0000;
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
break;
}
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov_imm(loc, (S + A) | T);
break;
@@ -469,7 +480,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
// BL <tls_trampoline>
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
*(ul32 *)loc = 0xeb00'0000 | bits(get_tls_trampoline_addr(P + 8), 25, 2);
} else {
// BL -> NOP
*(ul32 *)loc = 0xe320'f000;
@@ -477,7 +488,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_THM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
u64 val = align_to(get_trampoline_addr(P + 4), 4);
u64 val = align_to(get_tls_trampoline_addr(P + 4), 4);
write_thm_b_imm(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else {
@@ -497,17 +508,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -546,18 +552,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_ARM_ABS32:
@@ -568,16 +569,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_ARM_THM_CALL:
case R_ARM_CALL:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_THM_JUMP24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TARGET2:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_ARM_MOVT_PREL:
case R_ARM_THM_MOVT_PREL:
@@ -585,17 +586,20 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_ARM_TLS_GD32:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_ARM_TLS_LDM32:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_ARM_TLS_IE32:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_ARM_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSDESC;
break;
case R_ARM_TLS_LE32:
check_tlsle(ctx, sym, rel);
break;
case R_ARM_REL32:
case R_ARM_BASE_PREL:
@@ -607,7 +611,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_CALL:
case R_ARM_THM_TLS_CALL:
case R_ARM_V4BX:
@@ -635,13 +638,13 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
// It has two entry points: +0 for Thumb and +4 for ARM.
const u8 entry[] = {
// .thumb
0xfc, 0x46, // mov ip, pc
0x60, 0x47, // bx ip # jumps to the following `ldr` insn
0x78, 0x47, // bx pc # jumps to 1f
0xc0, 0x46, // nop
// .arm
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, 2f
0x0f, 0xc0, 0x8c, 0xe0, // 1: add ip, ip, pc
0x04, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
0x0f, 0xc0, 0x8c, 0xe0, // 2: add ip, ip, pc
0x1c, 0xff, 0x2f, 0xe1, // bx ip
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
};
static_assert(E::thunk_hdr_size == sizeof(hdr));
@@ -668,7 +671,13 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
// likely that it's due to some historical reason.
//
// This function sorts .ARM.exidx records.
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
void fixup_arm_exidx_section(Context<E> &ctx) {
Timer t(ctx, "fixup_arm_exidx_section");
OutputSection<E> *osec = find_section(ctx, SHT_ARM_EXIDX);
if (!osec)
return;
// .ARM.exidx records consists of a signed 31-bit relative address
// and a 32-bit value. The relative address indicates the start
// address of a function that the record covers. The value is one of
@@ -688,11 +697,11 @@ static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
ul32 val;
};
if (osec.shdr.sh_size % sizeof(Entry))
if (osec->shdr.sh_size % sizeof(Entry))
Fatal(ctx) << "invalid .ARM.exidx section size";
Entry *ent = (Entry *)(ctx.buf + osec.shdr.sh_offset);
i64 num_entries = osec.shdr.sh_size / sizeof(Entry);
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
// Entry's addresses are relative to themselves. In order to sort
// records by addresses, we first translate them so that the addresses
@@ -719,38 +728,12 @@ static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
});
}
void fixup_arm_exidx_section(Context<E> &ctx) {
Timer t(ctx, "fixup_arm_exidx_section");
auto find_exidx = [&]() -> OutputSection<E> * {
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
return osec;
return nullptr;
};
OutputSection<E> *exidx = find_exidx();
if (!exidx)
return;
// Sort .ARM.exidx contents
sort_exidx(ctx, *exidx);
// .ARM.exidx's sh_link should be set to the .text section index.
// Runtime doesn't care about it, but the binutils's strip command does.
if (ctx.shdr) {
auto find_text = [&]() -> Chunk<E> * {
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->name == ".text")
return chunk;
return nullptr;
};
if (Chunk<E> *text = find_text()) {
exidx->shdr.sh_link = text->shndx;
if (Chunk<E> *text = find_section(ctx, ".text")) {
osec->shdr.sh_link = text->shndx;
ctx.shdr->copy_buf(ctx);
}
}
+146 -76
View File
@@ -102,8 +102,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -123,6 +123,24 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
}
}
static bool is_adrp(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
}
static bool is_ldr(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
}
static bool is_add(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
@@ -155,9 +173,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_AARCH64_ABS64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_ADD_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
break;
case R_AARCH64_LDST16_ABS_LO12_NC:
@@ -172,57 +191,84 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_AARCH64_LDST128_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
break;
case R_AARCH64_ADD_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
case R_AARCH64_MOVW_UABS_G0:
check(S + A, 0, 1 << 16);
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
break;
case R_AARCH64_MOVW_UABS_G0: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul32 *)loc |= bits(val, 15, 0) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G0_NC:
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
break;
case R_AARCH64_MOVW_UABS_G1: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc |= bits(val, 31, 16) << 5;
case R_AARCH64_MOVW_UABS_G1:
check(S + A, 0, 1LL << 32);
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G1_NC:
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
break;
case R_AARCH64_MOVW_UABS_G2: {
i64 val = S + A;
check(val, 0, 1LL << 48);
*(ul32 *)loc |= bits(val, 47, 32) << 5;
case R_AARCH64_MOVW_UABS_G2:
check(S + A, 0, 1LL << 48);
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G2_NC:
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
break;
case R_AARCH64_MOVW_UABS_G3:
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
break;
case R_AARCH64_ADR_GOT_PAGE: {
i64 val = page(G + GOT + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
case R_AARCH64_ADR_GOT_PAGE:
if (sym.has_got(ctx)) {
i64 val = page(G + GOT + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
i64 val = page(S + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
u32 reg = bits(*(ul32 *)loc, 4, 0);
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
i++;
}
break;
}
case R_AARCH64_ADR_PREL_PG_HI21: {
case R_AARCH64_ADR_PREL_PG_HI21:
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
// 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);
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
break;
}
case R_AARCH64_ADR_PREL_LO21: {
i64 val = S + A - P;
check(val, -(1LL << 20), 1LL << 20);
write_adr(loc, val);
case R_AARCH64_ADR_PREL_LO21:
check(S + A - P, -(1LL << 20), 1LL << 20);
write_adr(loc, S + A - P);
break;
}
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26: {
if (sym.is_remaining_undef_weak()) {
@@ -238,25 +284,23 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc |= bits(val, 27, 2);
break;
}
case R_AARCH64_PLT32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = S + A - P;
break;
case R_AARCH64_CONDBR19:
case R_AARCH64_LD_PREL_LO19: {
i64 val = S + A - P;
check(val, -(1LL << 20), 1LL << 20);
*(ul32 *)loc |= bits(val, 20, 2) << 5;
case R_AARCH64_LD_PREL_LO19:
check(S + A - P, -(1LL << 20), 1LL << 20);
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
break;
}
case R_AARCH64_PREL16: {
i64 val = S + A - P;
check(val, -(1LL << 15), 1LL << 15);
*(ul16 *)loc = val;
case R_AARCH64_PREL16:
check(S + A - P, -(1LL << 15), 1LL << 15);
*(ul16 *)loc = S + A - P;
break;
}
case R_AARCH64_PREL32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 32);
*(ul32 *)loc = val;
case R_AARCH64_PREL32:
check(S + A - P, -(1LL << 31), 1LL << 32);
*(ul32 *)loc = S + A - P;
break;
}
case R_AARCH64_PREL64:
*(ul64 *)loc = S + A - P;
break;
@@ -309,6 +353,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
check(S + A - ctx.tp_addr, 0, 1 << 12);
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
break;
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
break;
@@ -368,17 +415,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -424,48 +466,78 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_AARCH64_ABS64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_AARCH64_MOVW_UABS_G3:
scan_absrel(ctx, sym, rel);
break;
case R_AARCH64_ADR_GOT_PAGE:
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
// symbol's address from GOT. If the GOT value is a link-time
// constant, we may be able to rewrite the ADRP+LDR instruction pair
// with an ADRP+ADD, eliminating a GOT memory load.
if (ctx.arg.relax && sym.is_relative() && !sym.is_imported &&
!sym.is_ifunc() && i + 1 < rels.size()) {
// ADRP+LDR must be consecutive and use the same register to relax.
const ElfRel<E> &rel2 = rels[i + 1];
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
rel2.r_offset == rel.r_offset + 4 &&
rel2.r_sym == rel.r_sym &&
rel.r_addend == 0 &&
rel2.r_addend == 0 &&
is_adrp(loc) &&
is_ldr(loc + 4)) {
u32 rd = bits(*(ul32 *)loc, 4, 0);
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
if (rd == rn && rn == rt) {
i++;
break;
}
}
}
sym.flags |= NEEDS_GOT;
break;
case R_AARCH64_LD64_GOT_LO12_NC:
case R_AARCH64_LD64_GOTPAGE_LO15:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
case R_AARCH64_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_AARCH64_ADR_PREL_PG_HI21:
case R_AARCH64_ADR_PREL_PG_HI21_NC:
scan_pcrel(ctx, sym, rel);
break;
case R_AARCH64_TLSGD_ADR_PAGE21:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
case R_AARCH64_TLSDESC_CALL:
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSDESC;
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
check_tlsle(ctx, sym, rel);
break;
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_ADR_PREL_LO21:
@@ -482,20 +554,18 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_AARCH64_MOVW_UABS_G1_NC:
case R_AARCH64_MOVW_UABS_G2:
case R_AARCH64_MOVW_UABS_G2_NC:
case R_AARCH64_MOVW_UABS_G3:
case R_AARCH64_PREL16:
case R_AARCH64_PREL32:
case R_AARCH64_PREL64:
case R_AARCH64_TLSGD_ADD_LO12_NC:
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:
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -520,7 +590,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
u8 *loc = buf + i * E::thunk_size;
memcpy(loc , data, sizeof(data));
memcpy(loc, data, sizeof(data));
write_adrp(loc, page(S) - page(P));
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
}
+110 -128
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/mold/wiki/i386-psabi.pdf
// https://github.com/rui314/psabi/blob/main/i386.pdf
#include "mold.h"
@@ -42,8 +42,6 @@ using E = I386;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_386_NONE:
return 0;
case R_386_8:
case R_386_PC8:
return *loc;
@@ -66,8 +64,9 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_386_SIZE32:
case R_386_TLS_GOTDESC:
return *(ul32 *)loc;
default:
return 0;
}
unreachable();
}
template <>
@@ -141,6 +140,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
@@ -150,10 +150,9 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
}
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
template <>
@@ -178,8 +177,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -203,6 +202,58 @@ static u32 relax_got32x(u8 *loc) {
return 0;
}
// Relax GD to LE
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
};
switch (rel.r_type) {
case R_386_PLT32:
case R_386_PC32:
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
case R_386_GOT32:
case R_386_GOT32X:
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = val;
break;
default:
unreachable();
}
}
// Relax LD to LE
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
switch (rel.r_type) {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
0x90, // nop
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
}
default:
unreachable();
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
@@ -234,33 +285,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_386_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_386_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_386_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
}
case R_386_32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_386_PC8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*loc = val;
case R_386_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
break;
}
case R_386_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
case R_386_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A - P;
break;
}
case R_386_PC32:
case R_386_PLT32:
*(ul32 *)loc = S + A - P;
@@ -298,32 +341,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (sym.has_tlsgd(ctx)) {
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
} else {
// Relax GD to LE
switch (rels[i + 1].r_type) {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = ctx.tp_addr - S - A;
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
break;
}
default:
unreachable();
}
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
i++;
}
break;
@@ -331,34 +349,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (ctx.got->has_tlsld(ctx)) {
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
} else {
// Relax LD to LE
switch (rels[i + 1].r_type) {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
0x90, // nop
};
memcpy(loc - 2, insn, sizeof(insn));
break;
}
default:
unreachable();
}
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
i++;
}
break;
@@ -398,17 +389,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -425,36 +411,28 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_386_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_386_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_386_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
}
case R_386_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
case R_386_PC8: {
i64 val = S + A;
check(val, -(1 << 7), 1 << 7);
*loc = val;
case R_386_PC8:
check(S + A, -(1 << 7), 1 << 7);
*loc = S + A;
break;
}
case R_386_PC16: {
i64 val = S + A;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
case R_386_PC16:
check(S + A, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A;
break;
}
case R_386_PC32:
*(ul32 *)loc = S + A;
break;
@@ -489,19 +467,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_386_8:
@@ -518,23 +491,24 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_386_GOT32:
case R_386_GOTPC:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_386_GOT32X: {
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_got32x(loc - 2);
// We always want to relax GOT32X because static PIE doesn't
// work without it.
bool do_relax = !sym.is_imported && sym.is_relative() &&
relax_got32x(loc - 2);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
}
case R_386_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_386_TLS_GD:
if (i + 1 == rels.size())
@@ -545,10 +519,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
if (relax_tlsgd(ctx, sym))
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static ||
(ctx.arg.relax && !ctx.arg.shared && !sym.is_imported))
i++;
else
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_386_TLS_LDM:
if (i + 1 == rels.size())
@@ -559,14 +536,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
if (relax_tlsld(ctx))
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
i++;
else
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_386_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSDESC;
break;
case R_386_TLS_LE:
check_tlsle(ctx, sym, rel);
break;
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
+682
View File
@@ -0,0 +1,682 @@
// LoongArch is a new RISC ISA announced in 2021 by Loongson. The ISA
// feels like a modernized MIPS with a hint of RISC-V flavor, although
// it's not compatible with either one.
//
// While LoongArch is a fresh and clean ISA, its technological advantage
// over other modern RISC ISAs such as RISC-V doesn't seem to be very
// significant. It appears that the real selling point of LoongArch is
// that the ISA is developed and controlled by a Chinese company,
// reflecting a desire for domestic CPUs. Loongson is actively working on
// bootstrapping the entire ecosystem for LoongArch, sending patches to
// Linux, GCC, LLVM, etc.
//
// All instructions are 4 bytes long in LoongArch and aligned to 4-byte
// boundaries. It has 32 general-purpose registers. Among these, $t0 - $t8
// (aliases for $r12 - $r20) are temporary registers that we can use in
// our PLT and range extension thunks.
//
// The psABI defines a few linker relaxations. We haven't supported them
// yet.
//
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
#if MOLD_LOONGARCH64 || MOLD_LOONGARCH32
#include "mold.h"
namespace mold::elf {
using E = MOLD_TARGET;
static u64 page(u64 val) {
return val & 0xffff'ffff'ffff'f000;
}
static u64 hi20(u64 val, u64 pc) {
// A PC-relative address with a 32 bit offset is materialized in a
// register with the following instructions:
//
// pcalau12i $rN, %pc_hi20(sym)
// addi.d $rN, $rN, %lo12(sym)
//
// PCALAU12I materializes bits [63:12] by computing (pc + imm << 12)
// and zero-clear [11:0]. ADDI.D sign-extends its 12 bit immediate and
// add it to the register. To compensate the sign-extension, PCALAU12I
// needs to materialize a 0x1000 larger value than the desired [63:12]
// if [11:0] is sign-extended.
//
// This is similar but different from RISC-V because RISC-V's AUIPC
// doesn't zero-clear [11:0].
return page(val + 0x800) - page(pc);
}
static u64 hi64(u64 val, u64 pc) {
// A PC-relative 64-bit address is materialized with the following
// instructions for the large code model:
//
// pcalau12i $rX, %pc_hi20(sym)
// addi.d $rY, $zero, %lo12(sym)
// lu32i.d $rY, %pc64_lo20(sym)
// lu52i.d $rY, $r12, %pc64_hi12(sym)
// add.d $rX, $rX, $rY
//
// PCALAU12I computes (pc + imm << 12) to materialize a 64-bit value.
// ADDI.D adds a sign-extended 12 bit value to a register. LU32I.D and
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
//
// Compensating all the sign-extensions is a bit complicated.
u64 x = hi20(val, pc);
if ((val & 0x800) && !(x & 0x8000'0000))
return x - 0x1'0000'0000;
if (!(val & 0x800) && (x & 0x8000'0000))
return x + 0x1'0000'0000;
return x;
}
static void write_j20(u8 *loc, u32 val) {
// opcode, [19:0], rd
*(ul32 *)loc &= 0b1111111'00000000000000000000'11111;
*(ul32 *)loc |= bits(val, 19, 0) << 5;
}
static void write_k12(u8 *loc, u32 val) {
// opcode, [11:0], rj, rd
*(ul32 *)loc &= 0b1111111111'000000000000'11111'11111;
*(ul32 *)loc |= bits(val, 11, 0) << 10;
}
static void write_d5k16(u8 *loc, u32 val) {
// opcode, [15:0], rj, [20:16]
*(ul32 *)loc &= 0b111111'0000000000000000'11111'00000;
*(ul32 *)loc |= bits(val, 15, 0) << 10;
*(ul32 *)loc |= bits(val, 20, 16);
}
static void write_d10k16(u8 *loc, u32 val) {
// opcode, [15:0], [25:16]
*(ul32 *)loc &= 0b111111'0000000000000000'0000000000;
*(ul32 *)loc |= bits(val, 15, 0) << 10;
*(ul32 *)loc |= bits(val, 25, 16);
}
static void write_k16(u8 *loc, u32 val) {
// opcode, [15:0], rj, rd
*(ul32 *)loc &= 0b111111'0000000000000000'11111'11111;
*(ul32 *)loc |= bits(val, 15, 0) << 10;
}
template <>
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
static const ul32 insn_64[] = {
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
0x0011'bdad, // sub.d $t1, $t1, $t3
0x28c0'01cf, // ld.d $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
0x02ff'51ad, // addi.d $t1, $t1, -44 # .plt entry
0x02c0'01cc, // addi.d $t0, $t2, %lo12(.got.plt) # &.got.plt
0x0045'05ad, // srli.d $t1, $t1, 1 # .plt entry offset
0x28c0'218c, // ld.d $t0, $t0, 8 # link map
0x4c00'01e0, // jr $t3
};
static const ul32 insn_32[] = {
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
0x0011'3dad, // sub.w $t1, $t1, $t3
0x2880'01cf, // ld.w $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
0x02bf'51ad, // addi.w $t1, $t1, -44 # .plt entry
0x0280'01cc, // addi.w $t0, $t2, %lo12(.got.plt) # &.got.plt
0x0044'89ad, // srli.w $t1, $t1, 2 # .plt entry offset
0x2880'118c, // ld.w $t0, $t0, 4 # link map
0x4c00'01e0, // jr $t3
};
u64 gotplt = ctx.gotplt->shdr.sh_addr;
u64 plt = ctx.plt->shdr.sh_addr;
memcpy(buf, E::is_64 ? insn_64 : insn_32, E::plt_hdr_size);
write_j20(buf, hi20(gotplt, plt) >> 12);
write_k12(buf + 8, gotplt);
write_k12(buf + 16, gotplt);
}
static const ul32 plt_entry_64[] = {
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
0x28c0'01ef, // ld.d $t3, $t3, %lo12([email protected])
0x4c00'01ed, // jirl $t1, $t3, 0
0x0340'0000, // nop
};
static const ul32 plt_entry_32[] = {
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
0x2880'01ef, // ld.w $t3, $t3, %lo12([email protected])
0x4c00'01ed, // jirl $t1, $t3, 0
0x0340'0000, // nop
};
template <>
void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u64 gotplt = sym.get_gotplt_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
write_j20(buf, hi20(gotplt, plt) >> 12);
write_k12(buf + 4, gotplt);
}
template <>
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u64 got = sym.get_got_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
write_j20(buf, hi20(got, plt) >> 12);
write_k12(buf + 4, got);
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_LARCH_ADD6:
*loc = (*loc & 0b1100'0000) | ((*loc + val) & 0b0011'1111);
break;
case R_LARCH_ADD8:
*loc += val;
break;
case R_LARCH_ADD16:
*(ul16 *)loc += val;
break;
case R_LARCH_ADD32:
*(ul32 *)loc += val;
break;
case R_LARCH_ADD64:
*(ul64 *)loc += val;
break;
case R_LARCH_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
break;
case R_LARCH_SUB8:
*loc -= val;
break;
case R_LARCH_SUB16:
*(ul16 *)loc -= val;
break;
case R_LARCH_SUB32:
*(ul32 *)loc -= val;
break;
case R_LARCH_SUB64:
*(ul64 *)loc -= val;
break;
case R_LARCH_32_PCREL:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_LARCH_64_PCREL:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_LARCH_RELAX ||
rel.r_type == R_LARCH_MARK_LA || rel.r_type == R_LARCH_MARK_PCREL ||
rel.r_type == R_LARCH_ALIGN)
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 check_branch = [&](i64 val, i64 lo, i64 hi) {
if (val & 0b11)
Error(ctx) << *this << ": misaligned symbol " << sym
<< " for relocation " << rel;
check(val, lo, hi);
};
// Unlike other psABIs, the LoongArch ABI uses the same relocation
// types to refer to GOT entries for thread-local symbols and regular
// ones. Therefore, G may refer to a TLSGD or a regular GOT slot
// depending on the symbol type.
//
// Note that as of August 2023, both GCC and Clang treat TLSLD relocs
// as if they were TLSGD relocs for LoongArch, which is a clear bug.
// We need to handle TLSLD relocs as synonyms for TLSGD relocs for the
// sake of bug compatibility.
auto get_tls_idx = [&] {
if (sym.has_tlsgd(ctx))
return sym.get_tlsgd_idx(ctx);
return sym.get_got_idx(ctx);
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = get_tls_idx() * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_LARCH_32:
if constexpr (E::is_64)
*(ul32 *)loc = S + A;
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_LARCH_64:
assert(E::is_64);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_LARCH_B16:
check_branch(S + A - P, -(1 << 17), 1 << 17);
write_k16(loc, (S + A - P) >> 2);
break;
case R_LARCH_B21:
check_branch(S + A - P, -(1 << 22), 1 << 22);
write_d5k16(loc, (S + A - P) >> 2);
break;
case R_LARCH_B26: {
i64 val = S + A - P;
if (val < -(1 << 27) || (1 << 27) <= val)
val = get_thunk_addr(i) + A - P;
write_d10k16(loc, val >> 2);
break;
}
case R_LARCH_ABS_HI20:
write_j20(loc, (S + A) >> 12);
break;
case R_LARCH_ABS_LO12:
write_k12(loc, S + A);
break;
case R_LARCH_ABS64_LO20:
write_j20(loc, (S + A) >> 32);
break;
case R_LARCH_ABS64_HI12:
write_k12(loc, (S + A) >> 52);
break;
case R_LARCH_PCALA_HI20: {
i64 val = hi20(S + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
break;
}
case R_LARCH_PCALA_LO12:
write_k12(loc, S + A);
break;
case R_LARCH_PCALA64_LO20:
write_j20(loc, hi64(S + A, P) >> 32);
break;
case R_LARCH_PCALA64_HI12:
write_k12(loc, hi64(S + A, P) >> 52);
break;
case R_LARCH_GOT_PC_HI20: {
i64 val = hi20(GOT + G + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
break;
}
case R_LARCH_GOT_PC_LO12:
write_k12(loc, GOT + G + A);
break;
case R_LARCH_GOT64_PC_LO20:
write_j20(loc, hi64(GOT + G + A, P) >> 32);
break;
case R_LARCH_GOT64_PC_HI12:
write_k12(loc, hi64(GOT + G + A, P) >> 52);
break;
case R_LARCH_GOT_HI20:
write_j20(loc, (GOT + G + A) >> 12);
break;
case R_LARCH_GOT_LO12:
write_k12(loc, GOT + G + A);
break;
case R_LARCH_GOT64_LO20:
write_j20(loc, (GOT + G + A) >> 32);
break;
case R_LARCH_GOT64_HI12:
write_k12(loc, (GOT + G + A) >> 52);
break;
case R_LARCH_TLS_LE_HI20:
write_j20(loc, (S + A - ctx.tp_addr) >> 12);
break;
case R_LARCH_TLS_LE_LO12:
write_k12(loc, S + A - ctx.tp_addr);
break;
case R_LARCH_TLS_LE64_LO20:
write_j20(loc, (S + A - ctx.tp_addr) >> 32);
break;
case R_LARCH_TLS_LE64_HI12:
write_k12(loc, (S + A - ctx.tp_addr) >> 52);
break;
case R_LARCH_TLS_IE_PC_HI20: {
i64 val = hi20(sym.get_gottp_addr(ctx) + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
break;
}
case R_LARCH_TLS_IE_PC_LO12:
write_k12(loc, sym.get_gottp_addr(ctx) + A);
break;
case R_LARCH_TLS_IE64_PC_LO20:
write_j20(loc, hi64(sym.get_gottp_addr(ctx) + A, P) >> 32);
break;
case R_LARCH_TLS_IE64_PC_HI12:
write_k12(loc, hi64(sym.get_gottp_addr(ctx) + A, P) >> 52);
break;
case R_LARCH_TLS_IE_HI20:
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 12);
break;
case R_LARCH_TLS_IE_LO12:
write_k12(loc, sym.get_gottp_addr(ctx) + A);
break;
case R_LARCH_TLS_IE64_LO20:
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 32);
break;
case R_LARCH_TLS_IE64_HI12:
write_k12(loc, (sym.get_gottp_addr(ctx) + A) >> 52);
break;
case R_LARCH_TLS_LD_PC_HI20:
case R_LARCH_TLS_GD_PC_HI20: {
i64 val = hi20(sym.get_tlsgd_addr(ctx) + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
break;
}
case R_LARCH_TLS_LD_HI20:
case R_LARCH_TLS_GD_HI20:
write_j20(loc, (sym.get_tlsgd_addr(ctx) + A) >> 12);
break;
case R_LARCH_ADD6:
*loc = (*loc & 0b1100'0000) | ((*loc + S + A) & 0b0011'1111);
break;
case R_LARCH_ADD8:
*loc += S + A;
break;
case R_LARCH_ADD16:
*(ul16 *)loc += S + A;
break;
case R_LARCH_ADD32:
*(ul32 *)loc += S + A;
break;
case R_LARCH_ADD64:
*(ul64 *)loc += S + A;
break;
case R_LARCH_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
break;
case R_LARCH_SUB8:
*loc -= S + A;
break;
case R_LARCH_SUB16:
*(ul16 *)loc -= S + A;
break;
case R_LARCH_SUB32:
*(ul32 *)loc -= S + A;
break;
case R_LARCH_SUB64:
*(ul64 *)loc -= S + A;
break;
case R_LARCH_32_PCREL:
*(ul32 *)loc = S + A - P;
break;
case R_LARCH_64_PCREL:
*(ul64 *)loc = S + A - P;
break;
case R_LARCH_ADD_ULEB128:
overwrite_uleb(loc, read_uleb(loc) + S + A);
break;
case R_LARCH_SUB_ULEB128:
overwrite_uleb(loc, read_uleb(loc) - S - A);
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
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);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_LARCH_32:
*(ul32 *)loc = S + A;
break;
case R_LARCH_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
case R_LARCH_ADD6:
*loc = (*loc & 0b1100'0000) | ((*loc + S + A) & 0b0011'1111);
break;
case R_LARCH_ADD8:
*loc += S + A;
break;
case R_LARCH_ADD16:
*(ul16 *)loc += S + A;
break;
case R_LARCH_ADD32:
*(ul32 *)loc += S + A;
break;
case R_LARCH_ADD64:
*(ul64 *)loc += S + A;
break;
case R_LARCH_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
break;
case R_LARCH_SUB8:
*loc -= S + A;
break;
case R_LARCH_SUB16:
*(ul16 *)loc -= S + A;
break;
case R_LARCH_SUB32:
*(ul32 *)loc -= S + A;
break;
case R_LARCH_SUB64:
*(ul64 *)loc -= S + A;
break;
case R_LARCH_TLS_DTPREL32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_LARCH_TLS_DTPREL64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_LARCH_ADD_ULEB128:
overwrite_uleb(loc, read_uleb(loc) + S + A);
break;
case R_LARCH_SUB_ULEB128:
overwrite_uleb(loc, read_uleb(loc) - S - A);
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
}
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);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_LARCH_RELAX ||
rel.r_type == R_LARCH_MARK_LA || rel.r_type == R_LARCH_MARK_PCREL ||
rel.r_type == R_LARCH_ALIGN)
continue;
if (record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_LARCH_32:
if constexpr (E::is_64)
scan_absrel(ctx, sym, rel);
else
scan_dyn_absrel(ctx, sym, rel);
break;
case R_LARCH_64:
assert(E::is_64);
scan_dyn_absrel(ctx, sym, rel);
break;
case R_LARCH_B26:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_LARCH_GOT_HI20:
case R_LARCH_GOT_PC_HI20:
sym.flags |= NEEDS_GOT;
break;
case R_LARCH_TLS_IE_HI20:
case R_LARCH_TLS_IE_PC_HI20:
sym.flags |= NEEDS_GOTTP;
break;
case R_LARCH_TLS_LD_PC_HI20:
case R_LARCH_TLS_GD_PC_HI20:
case R_LARCH_TLS_LD_HI20:
case R_LARCH_TLS_GD_HI20:
sym.flags |= NEEDS_TLSGD;
break;
case R_LARCH_32_PCREL:
case R_LARCH_64_PCREL:
scan_pcrel(ctx, sym, rel);
break;
case R_LARCH_TLS_LE_HI20:
case R_LARCH_TLS_LE_LO12:
case R_LARCH_TLS_LE64_LO20:
case R_LARCH_TLS_LE64_HI12:
check_tlsle(ctx, sym, rel);
break;
case R_LARCH_B16:
case R_LARCH_B21:
case R_LARCH_ABS_HI20:
case R_LARCH_ABS_LO12:
case R_LARCH_ABS64_LO20:
case R_LARCH_ABS64_HI12:
case R_LARCH_PCALA_HI20:
case R_LARCH_PCALA_LO12:
case R_LARCH_PCALA64_LO20:
case R_LARCH_PCALA64_HI12:
case R_LARCH_GOT_PC_LO12:
case R_LARCH_GOT64_PC_LO20:
case R_LARCH_GOT64_PC_HI12:
case R_LARCH_GOT_LO12:
case R_LARCH_GOT64_LO20:
case R_LARCH_GOT64_HI12:
case R_LARCH_TLS_IE_PC_LO12:
case R_LARCH_TLS_IE64_PC_LO20:
case R_LARCH_TLS_IE64_PC_HI12:
case R_LARCH_TLS_IE_LO12:
case R_LARCH_TLS_IE64_LO20:
case R_LARCH_TLS_IE64_HI12:
case R_LARCH_ADD6:
case R_LARCH_SUB6:
case R_LARCH_ADD8:
case R_LARCH_SUB8:
case R_LARCH_ADD16:
case R_LARCH_SUB16:
case R_LARCH_ADD32:
case R_LARCH_SUB32:
case R_LARCH_ADD64:
case R_LARCH_SUB64:
case R_LARCH_ADD_ULEB128:
case R_LARCH_SUB_ULEB128:
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;
static const ul32 insn[] = {
0x1a00'000c, // pcalau12i $t0, 0
0x02c0'018c, // addi.d $t0, $t0, 0
0x4c00'0180, // jirl $zero, $t0, 0
0x0340'0000, // nop
};
static_assert(E::thunk_size == sizeof(insn));
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
u8 *loc = buf + i * E::thunk_size;
memcpy(loc, insn, sizeof(insn));
write_j20(loc, hi20(S, P) >> 12);
write_k12(loc + 4, S);
}
}
} // namespace mold::elf
#endif
+16 -24
View File
@@ -12,7 +12,7 @@
// access instructions and therefore can support position-independent
// code without too much hassle.
//
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
// https://github.com/rui314/psabi/blob/main/m68k.pdf
#include "mold.h"
@@ -56,8 +56,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -126,7 +126,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_68K_32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_68K_16:
write16(S + A);
@@ -221,17 +221,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -262,16 +257,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
@@ -294,35 +284,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_68K_GOTOFF32:
case R_68K_GOTOFF16:
case R_68K_GOTOFF8:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_68K_PLT32:
case R_68K_PLT16:
case R_68K_PLT8:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_68K_TLS_GD32:
case R_68K_TLS_GD16:
case R_68K_TLS_GD8:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_68K_TLS_LDM32:
case R_68K_TLS_LDM16:
case R_68K_TLS_LDM8:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_68K_TLS_IE32:
case R_68K_TLS_IE16:
case R_68K_TLS_IE8:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_68K_TLS_LE32:
case R_68K_TLS_LE16:
case R_68K_TLS_LE8:
check_tlsle(ctx, sym, rel);
break;
case R_68K_TLS_LDO32:
case R_68K_TLS_LDO16:
case R_68K_TLS_LDO8:
case R_68K_TLS_LE32:
case R_68K_TLS_LE16:
case R_68K_TLS_LE8:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
+673
View File
@@ -0,0 +1,673 @@
// MIPS is a RISC ISA developed in the '80s. The processor was once fairly
// popular; for examples, Silicon Graphics workstations and Nintendo 64
// game consoles are based on the processor. Even though it's no longer a
// popular choice when creating a new system, there are still many uses of
// the ISA especially in the network router segment.
//
// The MIPS psABIs are in a sad state due to the lack of ownership of the
// ABI. The last major Unix vendor in the MIPS market was Silicon
// Graphics, which effectively ceased its MIPS-based Unix workstation
// business in the '90s. Even at the time the MIPS ABIs looked peculiar.
// After that, various small vendors used MIPS to create appliances and
// notably routers, but no one tried to modernize or improve the ABIs. As
// a result, the MIPS ABIs left as probably the most diverged ABI compared
// to the other psABIs.
//
// Specifically, the MIPS ABIs has the following issues:
//
// 1. Since the ISA does not support PC-relative addressing, each function
// first materializes the address of GOT + 0x7ff0 in the GP register
// and access GOT entries relative to the GP's value. This GP-relative
// access is usually done with a single load instruction with a 16-bit
// offset. That means only GP ± 32 KiB is addressable. If GOT is larger
// than that, the linker is expected to create a GOT section for each
// input file and associate a different GP value for each GOT. This
// method is called "multi-GOT". Multi-GOT is not necessary for other
// ABIs because other processors either simply support PC-relative
// addressing or use two instructions to access GOT entries.
//
// 2. The MIPS ABIs require .dynsym entries to be sorted in a very
// specific manner to represent some dynamic relocations implicitly
// rather than explicitly in the .rela.dyn section. This feature is
// called "Quickstart" in the MIPS documentation.
//
// 3. Unlike other psABIs, a MIPS relocation record can have up to three
// types -- that is, each record has not only r_type but also r_type2
// and r_type3. A relocated value is computed as the combination of all
// the relocation types.
//
// In our MIPS support, we prioritize simplicity of implementation over
// marginal runtime efficiency. Specifically, we made the following
// decisions for simplification:
//
// 1. We do not sort .dynsym entries. Quickstart still kicks in at the
// load-time (there's no way to tell the loader to disable Quickstart),
// and the loader writes resolved addresses to our placeholder section
// `.mips_quickstart`. We just ignore these relocated values.
//
// 2. Instead of supporting arbitrary combinations of relocation types, we
// support only a limited set of them. This works because, in practice,
// the compiler emits only a limted set of relocation types.
#include "mold.h"
namespace mold::elf {
static constexpr i64 BIAS = 0x8000;
// We don't support lazy symbol resolution for MIPS. All dynamic symbols
// are resolved eagerly on process startup.
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) {}
template <typename E>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <typename E>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_MIPS_64:
// We relocate R_MIPS_64 in .eh_frame as a relative relocation.
// See the comment for mips_rewrite_cie() below.
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
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);
// 0x7ff0 is added to maximize the GP-relative addressable range
// for load/store instructions with a signed 16-bit displacement.
u64 GP = file.extra.got->shdr.sh_addr + 0x7ff0;
u64 GP0 = file.extra.gp0;
MipsGotSection<E> *got = file.extra.got;
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_hi16 = [&](u64 val) {
check(val, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc |= ((val + BIAS) >> 16) & 0xffff;
};
auto write_lo16 = [&](u64 val) {
check(val, -(1 << 15), 1 << 15);
*(U32<E> *)loc |= val & 0xffff;
};
auto write_lo16_nc = [&](u64 val) {
*(U32<E> *)loc |= val & 0xffff;
};
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:
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_HI16 << 16): {
u64 val = sym.is_local(ctx) ? (S + A + GP0 - GP) : (S + A - GP);
write_hi16(-val);
break;
}
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_LO16 << 16): {
u64 val = sym.is_local(ctx) ? (S + A + GP0 - GP) : (S + A - GP);
write_lo16_nc(-val);
break;
}
case R_MIPS_GPREL32 | (R_MIPS_64 << 8):
*(U64<E> *)loc = S + A + GP0 - GP;
break;
case R_MIPS_GOT_DISP:
case R_MIPS_CALL16:
case R_MIPS_CALL_LO16:
case R_MIPS_GOT_LO16:
write_lo16(got->get_got_addr(ctx, sym, A) - GP);
break;
case R_MIPS_CALL_HI16:
case R_MIPS_GOT_HI16:
write_hi16(got->get_got_addr(ctx, sym, A) - GP);
break;
case R_MIPS_GOT_PAGE:
write_lo16(got->get_gotpage_addr(ctx, sym, A) - GP);
break;
case R_MIPS_GOT_OFST:
write_lo16(0);
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_nc(S + A - ctx.tp_addr);
break;
case R_MIPS_TLS_GOTTPREL:
write_lo16(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_nc(S + A - ctx.dtp_addr);
break;
case R_MIPS_TLS_GD:
write_lo16(got->get_tlsgd_addr(ctx, sym) - GP);
break;
case R_MIPS_TLS_LDM:
write_lo16(got->get_tlsld_addr(ctx) - 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_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U64<E> *)loc = *val;
else
*(U64<E> *)loc = S + A;
break;
case R_MIPS_32:
*(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);
MipsGotSection<E> *got = file.extra.got;
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_64:
scan_toc_rel(ctx, sym, rel);
break;
case R_MIPS_GOT_DISP:
case R_MIPS_CALL16:
case R_MIPS_CALL_HI16:
case R_MIPS_CALL_LO16:
case R_MIPS_GOT_HI16:
case R_MIPS_GOT_LO16:
got->got_syms.push_back({&sym, rel.r_addend});
break;
case R_MIPS_GOT_PAGE:
case R_MIPS_GOT_OFST:
got->gotpage_syms.push_back({&sym, rel.r_addend});
break;
case R_MIPS_TLS_GOTTPREL:
assert(rel.r_addend == 0);
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:
assert(rel.r_addend == 0);
got->tlsgd_syms.push_back(&sym);
break;
case R_MIPS_TLS_LDM:
assert(rel.r_addend == 0);
got->has_tlsld = true;
break;
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_HI16 << 16):
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_LO16 << 16):
case R_MIPS_GPREL32 | (R_MIPS_64 << 8):
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>
static inline i64 get_gotpage_offset(const MipsGotSection<E> &got) {
return got.got_syms.size();
}
template <typename E>
static inline i64 get_tlsgd_offset(const MipsGotSection<E> &got) {
return get_gotpage_offset(got) + got.gotpage_syms.size();
}
template <typename E>
static inline i64 get_gottp_offset(const MipsGotSection<E> &got) {
return get_tlsgd_offset(got) + got.tlsgd_syms.size() * 2;
}
template <typename E>
static inline i64 get_tlsld_offset(const MipsGotSection<E> &got) {
return get_gottp_offset(got) + got.gottp_syms.size();
}
template <typename E>
static inline i64 get_num_got_entries(const MipsGotSection<E> &got) {
return get_tlsld_offset(got) + got.has_tlsld * 2;
}
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});
i64 idx = it - got_syms.begin();
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_gotpage_addr(Context<E> &ctx, Symbol<E> &sym,
i64 addend) const {
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
SymbolAddend{&sym, addend});
i64 idx = get_gotpage_offset(*this) + (it - gotpage_syms.begin());
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
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>);
i64 idx = get_tlsgd_offset(*this) + (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>);
i64 idx = get_gottp_offset(*this) + (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(has_tlsld);
return this->shdr.sh_addr + get_tlsld_offset(*this) * sizeof(Word<E>);
}
namespace {
template <typename E>
struct GotEntry {
u64 val = 0;
i64 r_type = R_NONE;
Symbol<E> *sym = nullptr;
};
}
template <typename E>
std::vector<GotEntry<E>>
get_got_entries(Context<E> &ctx, const MipsGotSection<E> &got) {
using SymbolAddend = typename MipsGotSection<E>::SymbolAddend;
std::vector<GotEntry<E>> entries;
auto add = [&](GotEntry<E> ent) { entries.push_back(ent); };
// Create GOT entries for ordinary symbols
for (const SymbolAddend &ent : got.got_syms) {
if (ent.sym->is_imported) {
// If a symbol is imported, let the dynamic linker to resolve it.
add({0, E::R_DYNAMIC, ent.sym});
} else if (ctx.arg.pic && ent.sym->is_relative()) {
// If we know an address at link-time, fill that GOT entry now.
// It may need a base relocation, though.
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 : got.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 : got.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 : got.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 (got.has_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);
// Finalize gotpage_syms
sort(gotpage_syms);
remove_duplicates(gotpage_syms);
// Finalize tlsgd_syms
sort(tlsgd_syms, compare<E>);
remove_duplicates(tlsgd_syms);
// Finalize gottp_syms
sort(gottp_syms, compare<E>);
remove_duplicates(gottp_syms);
this->shdr.sh_size = get_num_got_entries(*this) * sizeof(Word<E>);
}
template <typename E>
i64 MipsGotSection<E>::get_reldyn_size(Context<E> &ctx) const {
i64 n = 0;
for (GotEntry<E> &ent : get_got_entries(ctx, *this))
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<E> &ent : get_got_entries(ctx, *this)) {
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;
}
}
template <typename E>
void MipsQuickstartSection<E>::update_shdr(Context<E> &ctx) {
this->shdr.sh_size = (NUM_RESERVED + ctx.dynsym->symbols.size()) *
sizeof(Word<E>);
}
template <typename E>
void MipsQuickstartSection<E>::copy_buf(Context<E> &ctx) {
U64<E> *buf = (U64<E> *)(ctx.buf + this->shdr.sh_offset);
memset(buf, 0, this->shdr.sh_size);
// It is not clear how the runtime uses it, but all MIPS binaries
// have this value in GOT[1].
buf[1] = E::is_64 ? 0x8000'0000'0000'0000 : 0x8000'0000;
for (i64 i = 0; i < ctx.dynsym->symbols.size(); i++)
if (Symbol<E> *sym = ctx.dynsym->symbols[i])
if (!sym->file->is_dso && !sym->esym().is_undef())
buf[i + NUM_RESERVED] = sym->get_addr(ctx, NO_PLT);
}
template <typename E>
void MipsABIFlagsSection<E>::update_shdr(Context<E> &ctx) {
for (ObjectFile<E> *file : ctx.objs) {
if (file->extra.abi_flags) {
contents = file->extra.abi_flags->contents;
break;
}
}
this->shdr.sh_size = contents.size();
}
// .MIPS.abiflags section contains ABI info such as ISA level.
// We need to merge input .MIPS.abiflags sections into a single
// .MIPS.abiflags section. But for now, we just pick the first one.
template <typename E>
void MipsABIFlagsSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
memcpy(buf, contents.data(), contents.size());
}
// We merge consective .mips_got sections to reduce the total number of
// .mips_got entries. Note that each .mips_got should be equal or smaller
// than 64 KiB so that all of its entries are within its GP ± 32 KiB.
template <typename E>
void mips_merge_got_sections(Context<E> &ctx) {
for (ObjectFile<E> *file : ctx.objs)
file->extra.got->update_shdr(ctx);
for (i64 i = 0; i < ctx.objs.size(); i++) {
MipsGotSection<E> &got = *ctx.objs[i]->extra.got;
for (i++; i < ctx.objs.size(); i++) {
MipsGotSection<E> &got2 = *ctx.objs[i]->extra.got;
if (got.shdr.sh_size + got2.shdr.sh_size >= 65536)
break;
append(got.got_syms, got2.got_syms);
append(got.gotpage_syms, got2.gotpage_syms);
append(got.tlsgd_syms, got2.tlsgd_syms);
append(got.gottp_syms, got2.gottp_syms);
got.has_tlsld = got.has_tlsld || got2.has_tlsld;
got2.got_syms.clear();
got2.gotpage_syms.clear();
got2.tlsgd_syms.clear();
got2.gottp_syms.clear();
got2.has_tlsld = false;
got.update_shdr(ctx);
ctx.objs[i]->extra.got = &got;
}
}
}
// MIPS .eh_frame contains absolute addresses (i.e. R_MIPS_64 relocations)
// even if compiled with -fPIC. Instead of emitting base relocations, we
// rewrite CIEs so that we can write relative addresse instead of absolute
// ones to .eh_frame.
template <typename E>
void mips_rewrite_cie(Context<E> &ctx, u8 *buf, CieRecord<E> &cie) {
u8 *aug = buf + 9; // Skip Length, CIE ID and Version fields
if (*aug != 'z')
return;
aug++;
// Skip Augmentation String
u8 *p = aug + strlen((char *)aug) + 1;
read_uleb(&p); // Skip Code Alignment Factor
read_uleb(&p); // Skip Data Alignment Factor
p++; // Skip Return Address Register
read_uleb(&p); // Skip Augmentation Data Length
auto rewrite = [&](u8 *ptr) {
i64 sz;
switch (*ptr & 0xf) {
case DW_EH_PE_absptr:
sz = sizeof(Word<E>);
break;
case DW_EH_PE_udata4:
case DW_EH_PE_sdata4:
sz = 4;
break;
case DW_EH_PE_udata8:
case DW_EH_PE_sdata8:
sz = 8;
break;
default:
Fatal(ctx) << cie.input_section << ": unknown pointer size";
}
if ((*ptr & 0x70) == DW_EH_PE_absptr) {
if (sz == 4)
*ptr = (*ptr & 0x80) | DW_EH_PE_pcrel | DW_EH_PE_sdata4;
else
*ptr = (*ptr & 0x80) | DW_EH_PE_pcrel | DW_EH_PE_sdata8;
}
return sz;
};
// Now p points to the beginning of Augmentation Data
for (; *aug; aug++) {
switch (*aug) {
case 'L':
case 'R':
rewrite(p);
p++;
break;
case 'P':
p += rewrite(p) + 1;
break;
case 'S':
case 'B':
break;
default:
Error(ctx) << cie.input_section
<< ": unknown argumentation string in CIE: '"
<< (char)*aug << "'";
}
}
}
#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_eh_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>; \
template class MipsQuickstartSection<E>; \
template class MipsABIFlagsSection<E>; \
template void mips_merge_got_sections(Context<E> &); \
template void mips_rewrite_cie(Context<E> &, u8 *, CieRecord<E> &);
INSTANTIATE(MIPS64LE);
INSTANTIATE(MIPS64BE);
} // namespace mold::elf
+28 -42
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 are came from different
// places in a merged .got2 for two functions that came from different
// input files. Therefore, %r30 makes sense only within a single function.
//
// Technically, we can reuse a %r30 value in our PLT if we create a PLT
@@ -38,7 +38,7 @@
// doesn't seems to be worth its complexity. Our PLT simply doesn't rely
// on a %r30 value.
//
// https://github.com/rui314/mold/wiki/ppc32-psabi.pdf
// https://github.com/rui314/psabi/blob/main/ppc32.pdf
#include "mold.h"
@@ -46,15 +46,11 @@ namespace mold::elf {
using E = PPC32;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
@@ -130,8 +126,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -157,9 +153,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
u64 got2 = 0;
if (file.ppc32_got2)
got2 = file.ppc32_got2->output_section->shdr.sh_addr + file.ppc32_got2->offset;
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
@@ -178,7 +172,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_PPC_ADDR32:
case R_PPC_UADDR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_PPC_ADDR14:
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
@@ -201,16 +195,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc |= bits(S + A, 31, 2) << 2;
break;
case R_PPC_PLT16_LO:
*(ub16 *)loc = lo(G + GOT - got2 - A);
*(ub16 *)loc = lo(G + GOT - A - GOT2);
break;
case R_PPC_PLT16_HI:
*(ub16 *)loc = hi(G + GOT - got2 - A);
*(ub16 *)loc = hi(G + GOT - A - GOT2);
break;
case R_PPC_PLT16_HA:
*(ub16 *)loc = ha(G + GOT - got2 - A);
*(ub16 *)loc = ha(G + GOT - A - GOT2);
break;
case R_PPC_PLT32:
*(ub32 *)loc = G + GOT - got2 - A;
*(ub32 *)loc = G + GOT - A - GOT2;
break;
case R_PPC_REL14:
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
@@ -299,17 +293,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -341,18 +330,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_PPC_ADDR32:
@@ -385,30 +369,32 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC_PLT16_HI:
case R_PPC_PLT16_HA:
case R_PPC_PLT32:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_PPC_REL24:
case R_PPC_PLTREL24:
case R_PPC_PLTREL32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_PPC_GOT_TLSGD16:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_PPC_GOT_TLSLD16:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_PPC_GOT_TPREL16:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_PPC_TPREL16_LO:
case R_PPC_TPREL16_HI:
case R_PPC_TPREL16_HA:
check_tlsle(ctx, sym, rel);
break;
case R_PPC_LOCAL24PC:
case R_PPC_TLS:
case R_PPC_TLSGD:
case R_PPC_TLSLD:
case R_PPC_TPREL16_LO:
case R_PPC_TPREL16_HI:
case R_PPC_TPREL16_HA:
case R_PPC_DTPREL16_LO:
case R_PPC_DTPREL16_HI:
case R_PPC_DTPREL16_HA:
+102 -69
View File
@@ -5,14 +5,15 @@
//
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
// endianness. The most notable difference is, in ELFv1, a function
// pointer doesn't directly refer the entry point of a function but
// instead refers a data structure so-called "function descriptor".
// pointer doesn't directly refer to the entry point of a function but
// instead refers to a data structure so-called "function descriptor".
//
// The function descriptor is essentially a pair of a function entry point
// address and a value that should be set to %r2 before calling that
// function. There is actually a third member, but we don't need to know
// what that is. In total, the function descriptor is 24 bytes long. Here
// is why we need it.
// function. There is also a third member for "the environment pointer for
// languages such as Pascal and PL/1" according to the psABI, but it looks
// like no one acutally uses it. In total, the function descriptor is 24
// bytes long. Here is why we need it.
//
// PPC generally lacks PC-relative data access instructions. Position-
// independent code sets GOT + 0x8000 to %r2 and access global variables
@@ -25,8 +26,8 @@
//
// In this way, you can't call a function just by knowing the function's
// entry point address. You also need to know a proper %r2 value for the
// function. This is why a function pointer refers a tuple of an address
// and a %r2 value.
// function. This is why a function pointer refers to a tuple of an
// address and a %r2 value.
//
// If a function call is made through PLT, PLT takes care of restoring %r2.
// Therefore, the caller has to restore %r2 only for function calls
@@ -39,8 +40,10 @@
// few different addresses for different purposes. It may not only have an
// entry point address but may also have PLT and/or GOT addresses.
// In PPCV1, it may have an OPD address in addition to these. OPD address
// is used for relocations that refers the address of a function as a
// is used for relocations that refers to the address of a function as a
// function pointer.
//
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
#include "mold.h"
@@ -51,15 +54,11 @@ namespace mold::elf {
using E = PPC64V1;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
@@ -93,9 +92,35 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4;
*(ub32 *)(buf + 0) = 0x3800'0000 | sym.get_plt_idx(ctx); // li %r0, PLT_INDEX
*(ub32 *)(buf + 4) = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
ub32 *loc = (ub32 *)buf;
i64 idx = sym.get_plt_idx(ctx);
// The PPC64 ELFv1 ABI requires PLT entries to be vary in size depending
// on their indices. Unlike other targets, .got.plt is filled not by us
// but by the loader, so we don't have a control over where the initial
// call to the PLT entry jumps to. So we need to strictly follow the PLT
// section layout as the loader expect it to be.
if (idx < 0x8000) {
static const ub32 insn[] = {
0x3800'0000, // li r0, PLT_INDEX
0x4b00'0000, // b plt0
};
memcpy(loc, insn, sizeof(insn));
loc[0] |= idx;
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
} else {
static const ub32 insn[] = {
0x3c00'0000, // lis r0, PLT_INDEX@high
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
0x4b00'0000, // b plt0
};
memcpy(loc, insn, sizeof(insn));
loc[0] |= high(idx);
loc[1] |= lo(idx);
loc[2] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 8) & 0x00ff'ffff;
}
}
// .plt.got is not necessary on PPC64 because range extension thunks
@@ -104,8 +129,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -158,10 +183,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_PPC64_ADDR64:
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_PPC64_TOC:
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, dynrel);
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, &dynrel);
break;
case R_PPC64_TOC16_HA:
*(ub16 *)loc = ha(S + A - TOC);
@@ -169,12 +194,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_TOC16_LO:
*(ub16 *)loc = lo(S + A - TOC);
break;
case R_PPC64_TOC16_DS: {
i64 val = S + A - TOC;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc |= val & 0xfffc;
case R_PPC64_TOC16_DS:
check(S + A - TOC, -(1 << 15), 1 << 15);
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
break;
}
case R_PPC64_TOC16_LO_DS:
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
break;
@@ -194,6 +217,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
break;
}
case R_PPC64_REL32:
*(ub32 *)loc = S + A - P;
break;
case R_PPC64_REL64:
*(ub64 *)loc = S + A - P;
break;
@@ -233,12 +259,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_DTPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_DTPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
break;
@@ -263,17 +289,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -321,48 +342,45 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD,
std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
// Any relocation except R_PPC64_REL24 is considered as an
// address-taking relocation.
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
sym.flags.fetch_or(NEEDS_PPC_OPD, std::memory_order_relaxed);
sym.flags |= NEEDS_PPC_OPD;
switch (rel.r_type) {
case R_PPC64_ADDR64:
scan_toc_rel(ctx, sym, rel);
break;
case R_PPC64_TOC:
scan_toc_rel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_PPC64_PLT16_HA:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_PPC64_GOT_TLSGD16_HA:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_PPC64_GOT_TLSLD16_HA:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO:
@@ -375,8 +393,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_PLT16_LO_DS:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
@@ -521,10 +537,10 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
// However, in reality, .opd isn't a normal input section. It needs many
// special treatments as follows:
//
// 1. A function symbol refers not a .text but an .opd. Its address works
// fine for address-taking relocations such as R_PPC64_ADDR64. However,
// R_PPC64_REL24 (which is used for branch instruction) needs a
// function's real address instead of the function's .opd address.
// 1. A function symbol refers to not a .text but an .opd. Its address
// works fine for address-taking relocations such as R_PPC64_ADDR64.
// However, R_PPC64_REL24 (which is used for branch instruction) needs
// a function's real address instead of the function's .opd address.
// We need to read .opd contents to find out a function entry point
// address to apply R_PPC64_REL24.
//
@@ -532,9 +548,9 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
// are taken. Just copying input .opd sections to an output would
// produces lots of dead .opd entries.
//
// 3. In this design, all function symbols refer an .opd section, and that
// doesn't work well with graph traversal optimizations such as garbage
// collection or identical comdat folding. For example, garbage
// 3. In this design, all function symbols refer to an .opd section, and
// that doesn't work well with graph traversal optimizations such as
// garbage collection or identical comdat folding. For example, garbage
// collector would mark an .opd alive which in turn mark all functions
// thatare referenced by .opd as alive, effectively keeping all
// functions as alive.
@@ -547,9 +563,9 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
//
// So, in this function, we undo what the compiler did to .opd. We remove
// function symbols from .opd and reattach them to their function entry
// points. We also rewrite relocations that directly refer an input .opd
// section so that they refer function symbols instead. We then mark input
// .opd sections as dead.
// points. We also rewrite relocations that directly refer to an input
// .opd section so that they refer to function symbols instead. We then
// mark input .opd sections as dead.
//
// After this function, we mark symbols with the NEEDS_PPC_OPD flag if the
// symbol needs an .opd entry. We then create an output .opd just like we
@@ -612,8 +628,8 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
}
// When a function is exported, the dynamic symbol for the function should
// refers the function's .opd entry. This function marks such symbols with
// NEEDS_PPC_OPD.
// refers to the function's .opd entry. This function marks such symbols
// with NEEDS_PPC_OPD.
void ppc64v1_scan_symbols(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
@@ -640,13 +656,30 @@ void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
this->shdr.sh_size += ENTRY_SIZE;
}
i64 PPC64OpdSection::get_reldyn_size(Context<E> &ctx) const {
if (ctx.arg.pic)
return symbols.size() * 2;
return 0;
}
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
ElfRel<E> *rel = nullptr;
if (ctx.arg.pic)
rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
for (Symbol<E> *sym : symbols) {
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
*buf++ = addr;
*buf++ = ctx.extra.TOC->value;
*buf++ = 0;
if (ctx.arg.pic) {
u64 loc = sym->get_opd_addr(ctx);
*rel++ = ElfRel<E>(loc, E::R_RELATIVE, 0, addr);
*rel++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
}
}
}
+179 -86
View File
@@ -1,21 +1,29 @@
// This file implements the PowerPC ELFv2 ABI which was standardized in
// 2014. Modern little-endian PowerPC systems are based on this ABI.
// The ABI is often referred to as "ppc64le". This shouldn't be confused
// with "ppc64" which refers the original, big-endian PowerPC systems.
// with "ppc64" which refers to the original, big-endian PowerPC systems.
//
// PPC64 is a bit tricky to support because PC-relative load/store
// instructions are generally not available. Therefore, it's not easy
// for position-independent code to load a value from, for example,
// .got, as we can't do that with [PC + the offset to the .got entry].
// instructions hadn't been available until Power10 which debuted in 2021.
// Prior to Power10, it wasn't trivial for position-independent code (PIC)
// to load a value from, for example, .got, as we can't do that with [PC +
// the offset to the .got entry].
//
// We can get the program counter by the following four instructions
// In the following, I'll explain how PIC is supported on pre-Power10
// systems first and then explain what has changed with Power10.
//
//
// Position-independent code on Power9 or earlier:
//
// We can get the program counter on older PPC64 systems with the
// following four instructions
//
// mflr r1 // save the current link register to r1
// bl .+4 // branch to the next instruction as if it were a function
// mflr r0 // copy the return address to r0
// mtlr r1 // restore the original link register value
//
// , but that's too expensive to do if we do this for each load/store.
// , but it's too expensive to do if we do this for each load/store.
//
// As a workaround, most functions are compiled in such a way that r2 is
// assumed to always contain the address of .got + 0x8000. With this, we
@@ -27,9 +35,9 @@
// calls are usually within the same ELF module, so this mechanism is
// efficient.
//
// In PPC64, a function usually have two entry points, global and local.
// The global entry point is usually 8 bytes precedes the local entry
// point. In between is the following instructions:
// A function compiled for pre-Power10 usually has two entry points,
// global and local. The global entry point usually 8 bytes precedes
// the local entry point. In between is the following instructions:
//
// addis r2, r12, .TOC.@ha
// addi r2, r2, .TOC.@lo + 4;
@@ -46,12 +54,31 @@
// to that address. Then the callee computes its own TOC pointer using
// r12.
//
//
// Position-independent code on Power10:
//
// Power10 added 8-bytes-long instructions to the ISA. Some of them are
// PC-relative load/store instructions that take 34 bits offsets.
// Functions compiled with `-mcpu=power10` use these instructions for PIC.
// r2 does not have a special meaning in such fucntions.
//
// When a fucntion compiled for Power10 calls a function that uses the TOC
// pointer, we need to compute a correct value for TOC and set it to r2
// before transferring the control to the callee. Thunks are responsible
// for doing it.
//
// `_NOTOC` relocations such as `R_PPC64_REL24_NOTOC` indicate that the
// callee does not use TOC (i.e. compiled with `-mcpu=power10`). If a
// function using TOC is referenced via a `_NOTOC` relocation, that call
// is made through a range extension thunk.
//
//
// Note on section names: the PPC64 psABI uses a weird naming convention
// which calls .got.plt .plt. We ignored that part because it's just
// confusing. Since the runtime only cares about segments, we should be
// able to name sections whatever we want.
//
// https://openpowerfoundation.org/specifications/64bitelfabi/
// https://github.com/rui314/psabi/blob/main/ppc64v2.pdf
#include "mold.h"
@@ -59,15 +86,15 @@ namespace mold::elf {
using E = PPC64V2;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 prefix34(u64 x) {
return bits(x, 33, 16) | (bits(x, 15, 0) << 32);
}
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
@@ -120,8 +147,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -144,10 +171,11 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
i64 val = sym.esym().ppc_local_entry;
assert(val <= 7);
if (val == 0 || val == 1)
return 0;
if (val == 7)
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
if (val == 0 || val == 1)
return 0;
return 1 << val;
}
@@ -168,13 +196,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
@@ -182,12 +203,15 @@ 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")
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_PPC64_TOC16_HA:
*(ul16 *)loc = ha(S + A - TOC);
@@ -199,22 +223,37 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_TOC16_LO_DS:
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
break;
case R_PPC64_REL24: {
i64 val = S + A - P + get_local_entry_offset(ctx, sym);
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
val = get_thunk_addr(i) + A - P;
case R_PPC64_REL24:
if (sym.has_plt(ctx) || !sym.esym().preserves_r2()) {
i64 val = r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
check(val, -(1 << 25), 1 << 25);
*(ul32 *)loc |= bits(val, 25, 2) << 2;
// If a callee is an external function, PLT saves %r2 to the
// caller's r2 save slot. We need to restore it after function
// return. To do so, there's usually a NOP as a placeholder
// after a BL. 0x6000'0000 is a NOP.
if (sym.has_plt(ctx) && *(ul32 *)(loc + 4) == 0x6000'0000)
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
// The thunk saves %r2 to the caller's r2 save slot. We need to
// restore it after function return. To do so, there's usually a
// NOP as a placeholder after a BL. 0x6000'0000 is a NOP.
if (*(ul32 *)(loc + 4) == 0x6000'0000)
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
} else {
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
if (sign_extend(val, 25) != val)
val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
}
break;
case R_PPC64_REL24_NOTOC:
if (sym.has_plt(ctx) || sym.esym().uses_toc()) {
i64 val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
} else {
i64 val = S + A - P;
if (sign_extend(val, 25) != val)
val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
}
break;
case R_PPC64_REL32:
*(ul32 *)loc = S + A - P;
break;
}
case R_PPC64_REL64:
*(ul64 *)loc = S + A - P;
break;
@@ -236,38 +275,60 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_PLT16_LO_DS:
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
break;
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
case R_PPC64_GOT_PCREL34:
*(ul64 *)loc |= prefix34(G + GOT - P);
break;
case R_PPC64_PCREL34:
*(ul64 *)loc |= prefix34(S + A - P);
break;
case R_PPC64_GOT_TPREL16_HA:
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_GOT_TPREL_PCREL34:
*(ul64 *)loc |= prefix34(sym.get_gottp_addr(ctx) - P);
break;
case R_PPC64_GOT_TLSGD16_HA:
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_LO:
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD_PCREL34:
*(ul64 *)loc |= prefix34(sym.get_tlsgd_addr(ctx) - P);
break;
case R_PPC64_GOT_TLSLD16_HA:
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD16_LO:
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD_PCREL34:
*(ul64 *)loc |= prefix34(ctx.got->get_tlsld_addr(ctx) - P);
break;
case R_PPC64_DTPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_DTPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_DTPREL34:
*(ul64 *)loc |= prefix34(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_PLTSEQ:
case R_PPC64_PLTSEQ_NOTOC:
case R_PPC64_PLTCALL:
case R_PPC64_PLTCALL_NOTOC:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
@@ -284,17 +345,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -342,18 +398,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_PPC64_ADDR64:
@@ -363,21 +414,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
case R_PPC64_GOT_TPREL_PCREL34:
sym.flags |= NEEDS_GOTTP;
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_PPC64_REL24_NOTOC:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
ctx.extra.is_power10 = true;
break;
case R_PPC64_PLT16_HA:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
case R_PPC64_GOT_PCREL34:
sym.flags |= NEEDS_GOT;
break;
case R_PPC64_GOT_TLSGD16_HA:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
case R_PPC64_GOT_TLSGD_PCREL34:
sym.flags |= NEEDS_TLSGD;
break;
case R_PPC64_GOT_TLSLD16_HA:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
case R_PPC64_GOT_TLSLD_PCREL34:
ctx.needs_tlsld = true;
break;
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO:
@@ -388,10 +455,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_PLT16_HI:
case R_PPC64_PLT16_LO:
case R_PPC64_PLT16_LO_DS:
case R_PPC64_PCREL34:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTSEQ_NOTOC:
case R_PPC64_PLTCALL:
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_PLTCALL_NOTOC:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
@@ -400,6 +468,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_TLSLD:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_LO:
case R_PPC64_DTPREL34:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -414,46 +483,70 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
// If the destination is PLT, we read an address from .got.plt or .got
// and jump there.
static const ul32 plt_thunk[] = {
// Save r2 to the r2 save slot reserved in the caller's stack frame
0xf841'0018, // std r2, 24(r1)
// Jump to a PLT entry
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
};
static const ul32 plt_thunk_power10[] = {
0xf841'0018, // std r2, 24(r1)
0x0410'0000, // pld r12, foo@gotplt@pcrel
0xe580'0000,
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
};
// If the destination is a non-imported function, we directly jump
// to its local entry point.
static const ul32 local_thunk[] = {
// Jump to a local entry point
0xf841'0018, // std r2, 24(r1)
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
0x6000'0000, // nop
};
static const ul32 local_thunk_power10[] = {
0xf841'0018, // std r2, 24(r1)
0x0610'0000, // pla r12, foo@pcrel
0x3980'0000,
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
};
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(plt_thunk_power10));
static_assert(E::thunk_size == sizeof(local_thunk));
static_assert(E::thunk_size == sizeof(local_thunk_power10));
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
if (sym.has_plt(ctx)) {
memcpy(loc, plt_thunk, sizeof(plt_thunk));
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
i64 val = got - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
if (ctx.extra.is_power10) {
memcpy(loc, plt_thunk_power10, E::thunk_size);
*(ul64 *)(loc + 1) |= prefix34(got - get_addr(i) - 4);
} else {
i64 val = got - ctx.extra.TOC->value;
memcpy(loc, plt_thunk, E::thunk_size);
loc[1] |= higha(val);
loc[2] |= lo(val);
}
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx) + get_local_entry_offset(ctx, sym) -
ctx.extra.TOC->value;
loc[0] |= higha(val);
loc[1] |= lo(val);
if (ctx.extra.is_power10) {
memcpy(loc, local_thunk_power10, E::thunk_size);
*(ul64 *)(loc + 1) |= prefix34(sym.get_addr(ctx) - get_addr(i) - 4);
} else {
i64 val = sym.get_addr(ctx) - ctx.extra.TOC->value;
memcpy(loc, local_thunk, E::thunk_size);
loc[1] |= higha(val);
loc[2] |= lo(val);
}
}
}
}
+438 -233
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 past the deleted bytes.
// refer to past the deleted bytes.
//
// In mold, we use `r_deltas` to memorize how many bytes have be adjusted
// for relocations. For symbols, we directly mutate their `value` member.
@@ -67,6 +67,8 @@
//
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
#if MOLD_RV64LE || MOLD_RV64BE || MOLD_RV32LE || MOLD_RV32BE
#include "mold.h"
#include <tbb/parallel_for.h>
@@ -74,79 +76,54 @@
namespace mold::elf {
static u32 itype(u32 val) {
return val << 20;
using E = MOLD_TARGET;
static void write_itype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
*(ul32 *)loc |= bits(val, 11, 0) << 20;
}
static u32 stype(u32 val) {
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
static void write_stype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
*(ul32 *)loc |= bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
}
static u32 btype(u32 val) {
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
static void write_btype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
*(ul32 *)loc |= bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
}
static u32 utype(u32 val) {
static void write_utype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
// U-type instructions are used in combination with I-type
// instructions. U-type insn sets an immediate to the upper 20-bits
// of a register. I-type insn sign-extends a 12-bits immediate and
// adds it to a register value to construct a complete value. 0x800
// is added here to compensate for the sign-extension.
return (val + 0x800) & 0xffff'f000;
}
static u32 jtype(u32 val) {
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
}
static u32 cbtype(u32 val) {
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
bit(val, 1) << 3 | bit(val, 5) << 2;
}
static u32 cjtype(u32 val) {
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
bit(val, 1) << 3 | bit(val, 5) << 2;
}
static void write_itype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
*(ul32 *)loc |= itype(val);
}
static void write_stype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
*(ul32 *)loc |= stype(val);
}
static void write_btype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
*(ul32 *)loc |= btype(val);
}
static void write_utype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
*(ul32 *)loc |= utype(val);
*(ul32 *)loc |= (val + 0x800) & 0xffff'f000;
}
static void write_jtype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
*(ul32 *)loc |= jtype(val);
*(ul32 *)loc |= bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
}
static void write_cbtype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'000'111'00000'11;
*(ul16 *)loc |= cbtype(val);
*(ul16 *)loc |= bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
bit(val, 1) << 3 | bit(val, 5) << 2;
}
static void write_cjtype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'00000000000'11;
*(ul16 *)loc |= cjtype(val);
*(ul16 *)loc |= bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
bit(val, 1) << 3 | bit(val, 5) << 2;
}
// Returns the rd register of an R/I/U/J-type instruction.
@@ -160,8 +137,8 @@ static void set_rs1(u8 *loc, u32 rs1) {
*(ul32 *)loc |= rs1 << 15;
}
template <typename E>
void write_plt_header(Context<E> &ctx, u8 *buf) {
template <>
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
static const ul32 insn_64[] = {
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
@@ -184,13 +161,10 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
0x000e'0067, // jr t3
};
if constexpr (E::is_64)
memcpy(buf, insn_64, sizeof(insn_64));
else
memcpy(buf, insn_32, sizeof(insn_32));
u64 gotplt = ctx.gotplt->shdr.sh_addr;
u64 plt = ctx.plt->shdr.sh_addr;
memcpy(buf, E::is_64 ? insn_64 : insn_32, E::plt_hdr_size);
write_utype(buf, gotplt - plt);
write_itype(buf + 8, gotplt - plt);
write_itype(buf + 16, gotplt - plt);
@@ -210,35 +184,29 @@ static const ul32 plt_entry_32[] = {
0x0000'0013, // nop
};
template <typename E>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if constexpr (E::is_64)
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
else
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
template <>
void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u64 gotplt = sym.get_gotplt_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
write_utype(buf, gotplt - plt);
write_itype(buf + 4, gotplt - plt);
}
template <typename E>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if constexpr (E::is_64)
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
else
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
template <>
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u64 got = sym.get_got_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
write_utype(buf, got - plt);
write_itype(buf + 4, got - plt);
}
template <typename E>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -279,7 +247,7 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
}
}
template <typename E>
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
@@ -309,6 +277,28 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
<< lo << ", " << hi << ")";
};
auto is_hi20 = [](const ElfRel<E> &r) {
u32 ty = r.r_type;
return ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20;
};
auto find_paired_reloc = [&] {
assert(sym.get_input_section() == this);
if (sym.value < r_offset) {
for (i64 j = i - 1; j >= 0; j--)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset - get_r_delta(j))
return j;
} else {
for (i64 j = i + 1; j < rels.size(); j++)
if (is_hi20(rels[j]) && 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;
@@ -320,24 +310,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if constexpr (E::is_64)
*(U32<E> *)loc = S + A;
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_RISCV_64:
assert(E::is_64);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_RISCV_BRANCH: {
i64 val = S + A - P;
check(val, -(1 << 12), 1 << 12);
write_btype(loc, val);
case R_RISCV_BRANCH:
check(S + A - P, -(1 << 12), 1 << 12);
write_btype(loc, S + A - P);
break;
}
case R_RISCV_JAL: {
i64 val = S + A - P;
check(val, -(1 << 20), 1 << 20);
write_jtype(loc, val);
case R_RISCV_JAL:
check(S + A - P, -(1 << 20), 1 << 20);
write_jtype(loc, S + A - P);
break;
}
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
@@ -357,6 +343,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_cjtype(loc, S + A - P);
} else {
assert(removed_bytes == 0);
// Calling an undefined weak symbol does not make sense.
// We make such call into an infinite loop. This should
// help debugging of a faulty program.
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
check(val, -(1LL << 31), 1LL << 31);
write_utype(loc, val);
@@ -365,56 +354,82 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
case R_RISCV_GOT_HI20:
*(ul32 *)loc = G + GOT + A - P;
write_utype(loc, G + GOT + A - P);
break;
case R_RISCV_TLS_GOT_HI20:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
write_utype(loc, sym.get_gottp_addr(ctx) + A - P);
break;
case R_RISCV_TLS_GD_HI20:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
write_utype(loc, sym.get_tlsgd_addr(ctx) + A - P);
break;
case R_RISCV_PCREL_HI20:
if (sym.esym().is_undef_weak()) {
// Calling an undefined weak symbol does not make sense.
// We make such call into an infinite loop. This should
// help debugging of a faulty program.
*(ul32 *)loc = 0;
} else {
*(ul32 *)loc = S + A - P;
}
write_utype(loc, S + A - P);
break;
case R_RISCV_HI20: {
i64 val = S + A;
if (removed_bytes == 0) {
check(val, -(1LL << 31), 1LL << 31);
write_utype(loc, val);
} else {
assert(removed_bytes == 4);
assert(sign_extend(val, 11) == val);
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S: {
i64 idx2 = find_paired_reloc();
const ElfRel<E> &rel2 = rels[idx2];
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
u64 S = sym2.get_addr(ctx);
u64 A = rel2.r_addend;
u64 P = get_addr() + rel2.r_offset - get_r_delta(idx2);
u64 G = sym2.get_got_idx(ctx) * sizeof(Word<E>);
u64 val;
switch (rel2.r_type) {
case R_RISCV_GOT_HI20:
val = G + GOT + A - P;
break;
case R_RISCV_TLS_GOT_HI20:
val = sym2.get_gottp_addr(ctx) + A - P;
break;
case R_RISCV_TLS_GD_HI20:
val = sym2.get_tlsgd_addr(ctx) + A - P;
break;
case R_RISCV_PCREL_HI20:
val = S + A - P;
break;
default:
unreachable();
}
break;
}
case R_RISCV_LO12_I:
case R_RISCV_LO12_S: {
i64 val = S + A;
if (rel.r_type == R_RISCV_LO12_I)
if (rel.r_type == R_RISCV_PCREL_LO12_I)
write_itype(loc, val);
else
write_stype(loc, val);
break;
}
case R_RISCV_HI20:
assert(removed_bytes == 0 || removed_bytes == 4);
if (removed_bytes == 0) {
check(S + A, -(1LL << 31), 1LL << 31);
write_utype(loc, S + A);
}
break;
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
if (rel.r_type == R_RISCV_LO12_I)
write_itype(loc, S + A);
else
write_stype(loc, S + A);
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
// accessible relative to the zero register. If the upper 20 bits
// are all zero, the corresponding LUI might have been removed.
if (sign_extend(val, 11) == val)
if (bits(S + A, 31, 12) == 0)
set_rs1(loc, 0);
break;
}
case R_RISCV_TPREL_HI20:
assert(removed_bytes == 0 || removed_bytes == 4);
if (removed_bytes == 0)
write_utype(loc, S + A - ctx.tp_addr);
break;
case R_RISCV_TPREL_ADD:
// This relocation just annotates an ADD instruction that can be
// removed when a TPREL is relaxed. No value is needed to be
// written.
assert(removed_bytes == 0 || removed_bytes == 4);
break;
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S: {
@@ -472,20 +487,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)(loc + i) = 0x0001; // c.nop
break;
}
case R_RISCV_RVC_BRANCH: {
i64 val = S + A - P;
check(val, -(1 << 8), 1 << 8);
write_cbtype(loc, val);
case R_RISCV_RVC_BRANCH:
check(S + A - P, -(1 << 8), 1 << 8);
write_cbtype(loc, S + A - P);
break;
}
case R_RISCV_RVC_JUMP: {
i64 val = S + A - P;
check(val, -(1 << 11), 1 << 11);
write_cjtype(loc, val);
case R_RISCV_RVC_JUMP:
check(S + A - P, -(1 << 11), 1 << 11);
write_cjtype(loc, S + A - P);
break;
}
case R_RISCV_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
break;
case R_RISCV_SET6:
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
@@ -499,72 +510,34 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_RISCV_SET32:
*(U32<E> *)loc = S + A;
break;
case R_RISCV_PLT32:
case R_RISCV_32_PCREL:
*(U32<E> *)loc = S + A - P;
break;
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
// These relocations are handled in the next loop.
case R_RISCV_SET_ULEB128:
overwrite_uleb(loc, S + A);
break;
case R_RISCV_SUB_ULEB128:
overwrite_uleb(loc, read_uleb(loc) - 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>
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)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -607,7 +580,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
*(U64<E> *)loc -= S + A;
break;
case R_RISCV_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
break;
case R_RISCV_SET6:
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
@@ -621,6 +594,12 @@ 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:
overwrite_uleb(loc, read_uleb(loc) - S - A);
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
@@ -629,7 +608,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
}
}
template <typename E>
template <>
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
// If a section is not relaxed, we can copy it as a one big chunk.
if (extra.r_deltas.empty()) {
@@ -637,8 +616,7 @@ void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
return;
}
// Memory-allocated sections may be relaxed, so copy each segment
// individually.
// A relaxed section is copied piece-wise.
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 pos = 0;
@@ -657,7 +635,7 @@ void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
memcpy(buf, contents.data() + pos, contents.size() - pos);
}
template <typename E>
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
@@ -667,18 +645,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_RISCV_32:
@@ -697,33 +670,35 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
case R_RISCV_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_RISCV_GOT_HI20:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_RISCV_TLS_GOT_HI20:
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_RISCV_TLS_GD_HI20:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_RISCV_32_PCREL:
case R_RISCV_PCREL_HI20:
scan_pcrel(ctx, sym, rel);
break;
case R_RISCV_BRANCH:
case R_RISCV_JAL:
case R_RISCV_PCREL_HI20:
case R_RISCV_PCREL_LO12_I:
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:
check_tlsle(ctx, sym, rel);
break;
case R_RISCV_BRANCH:
case R_RISCV_JAL:
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
case R_RISCV_ADD8:
case R_RISCV_ADD16:
case R_RISCV_ADD32:
@@ -741,6 +716,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_SET8:
case R_RISCV_SET16:
case R_RISCV_SET32:
case R_RISCV_SET_ULEB128:
case R_RISCV_SUB_ULEB128:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -791,7 +768,7 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
// Handling R_RISCV_ALIGN is mandatory.
//
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate some
// R_RISCV_ALIGN refers to NOP instructions. We need to eliminate some
// or all of the instructions so that the instruction that immediately
// follows the NOPs is aligned to a specified alignment boundary.
if (r.r_type == R_RISCV_ALIGN) {
@@ -821,15 +798,14 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
switch (r.r_type) {
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
// These relocations refer an AUIPC + JALR instruction pair to
// These relocations refer to an AUIPC + JALR instruction pair to
// allow to jump to anywhere in PC ± 2 GiB. If the jump target is
// close enough to PC, we can use C.J, C.JAL or JAL instead.
i64 dist = compute_distance(ctx, sym, isec, r);
if (dist & 1)
break;
std::string_view contents = isec.contents;
i64 rd = get_rd(*(ul32 *)(contents.data() + r.r_offset + 4));
i64 rd = get_rd(*(ul32 *)(isec.contents.data() + r.r_offset + 4));
if (rd == 0 && sign_extend(dist, 11) == dist && use_rvc) {
// If rd is x0 and the jump target is within ±2 KiB, we can use
@@ -845,41 +821,39 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
}
break;
}
case R_RISCV_HI20: {
case R_RISCV_HI20:
// If the upper 20 bits are all zero, we can remove LUI.
// The corresponding instructions referred by LO12_I/LO12_S
// The corresponding instructions referred to by LO12_I/LO12_S
// relocations will use the zero register instead.
i64 val = sym.get_addr(ctx);
if (sign_extend(val, 11) == val)
if (bits(sym.get_addr(ctx), 31, 12) == 0)
delta += 4;
break;
}
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_ADD: {
// These relocations are used to materialize the upper 20 bits of
// an address relative to the thread pointer as follows:
case R_RISCV_TPREL_ADD:
// These relocations are used to add a high 20-bit value to the
// thread pointer. The following two instructions materializes
// TP + HI20(foo) in %r5, for example.
//
// lui a5,%tprel_hi(foo) # R_RISCV_TPREL_HI20 (symbol)
// add a5,a5,tp,%tprel_add(foo) # R_RISCV_TPREL_ADD (symbol)
//
// Then thread-local variable `foo` is accessed with a 12-bit offset
// like this:
// Then thread-local variable `foo` is accessed with a low 12-bit
// offset like this:
//
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
//
// However, if the offset is ±2 KiB, we don't need to materialize
// the upper 20 bits in a register. We can instead access the
// thread-local variable directly with TP like this:
// However, if the variable is at TP ±2 KiB, TP + HI20(foo) is the
// same as TP, so we can instead access the thread-local variable
// directly using TP like this:
//
// sw t0,%tprel_lo(foo)(tp)
//
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
if (sign_extend(val, 11) == val)
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
sign_extend(val, 11) == val)
delta += 4;
break;
}
}
}
isec.extra.r_deltas[rels.size()] = delta;
@@ -894,8 +868,8 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
// linker to align the location referred to by the relocation to a
// specified byte boundary. We at least have to interpret them to satisfy
// the alignment constraints.
template <typename E>
i64 riscv_resize_sections(Context<E> &ctx) {
template <>
i64 riscv_resize_sections<E>(Context<E> &ctx) {
Timer t(ctx, "riscv_resize_sections");
// True if we can use the 2-byte instructions. This is usually true on
@@ -935,21 +909,252 @@ i64 riscv_resize_sections(Context<E> &ctx) {
return set_osec_offsets(ctx);
}
#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>::copy_contents_riscv(Context<E> &, u8 *); \
template void InputSection<E>::scan_relocations(Context<E> &); \
template i64 riscv_resize_sections(Context<E> &);
// ISA name handlers
//
// An example of ISA name is "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0".
// An ISA name starts with the base name (e.g. "rv64i2p1") followed by
// ISA extensions separated by underscores.
//
// There are lots of ISA extensions defined for RISC-V, and they are
// identified by name. Some extensions are of single-letter alphabet such
// as "m" or "q". Newer extension names start with "z" followed by one or
// more alphabets (i.e. "zicsr"). "s" and "x" prefixes are reserved
// for supervisor-level extensions and private extensions, respectively.
//
// Each extension consists of a name, a major version and a minor version.
// For example, "m2p0" indicates the "m" extension of version 2.0. "p" is
// just a separator. Versions are often omitted in documents, but they are
// mandatory in .riscv.attributes. Likewise, abbreviations as "g" (which
// is short for "IMAFD") are not allowed in .riscv.attributes.
//
// Each RISC-V object file contains an ISA string enumerating extensions
// used by the object file. We need to merge input objects' ISA strings
// into a single ISA string.
//
// In order to guarantee string uniqueness, extensions have to be ordered
// in a specific manner. The exact rule is unfortunately a bit complicated.
//
// The following functions takes care of ISA strings.
INSTANTIATE(RV64LE);
INSTANTIATE(RV64BE);
INSTANTIATE(RV32LE);
INSTANTIATE(RV32BE);
struct Extn {
std::string name;
i64 major;
i64 minor;
};
// As per the RISC-V spec, the extension names must be sorted in a very
// specific way, and unfortunately that's not just an alphabetical order.
// For example, rv64imafd is a legal ISA string, whereas rv64iafdm is not.
// The exact rule is somewhat arbitrary.
//
// This function returns true if the first extension name should precede
// the second one as per the rule.
static bool extn_name_less(const Extn &e1, const Extn &e2) {
auto get_single_letter_rank = [](char c) -> i64 {
std::string_view exts = "iemafdqlcbkjtpvnh";
size_t pos = exts.find_first_of(c);
if (pos != exts.npos)
return pos;
return c - 'a' + exts.size();
};
auto get_rank = [&](std::string_view str) -> i64 {
switch (str[0]) {
case 'x':
return 1 << 20;
case 's':
return 1 << 19;
case 'z':
return (1 << 18) + get_single_letter_rank(str[1]);
default:
return get_single_letter_rank(str[0]);
}
};
return std::tuple{get_rank(e1.name), e1.name} <
std::tuple{get_rank(e2.name), e2.name};
}
static bool extn_version_less(const Extn &e1, const Extn &e2) {
return std::tuple{e1.major, e1.minor} <
std::tuple{e2.major, e2.minor};
}
static std::optional<Extn> read_extn_string(std::string_view &str) {
Extn extn;
size_t pos = str.find_first_of("0123456789");
if (pos == str.npos)
return {};
extn.name = str.substr(0, pos);
str = str.substr(pos);
size_t nread;
extn.major = std::stoul(std::string(str), &nread, 10);
str = str.substr(nread);
if (str.size() < 2 || str[0] != 'p')
return {};
str = str.substr(1);
extn.minor = std::stoul(std::string(str), &nread, 10);
str = str.substr(nread);
if (str.empty() || str[0] == '_')
return extn;
return {};
}
static std::vector<Extn> parse_arch_string(std::string_view str) {
if (str.size() < 5)
return {};
// Parse the base part
std::string_view base = str.substr(0, 5);
if (base != "rv32i" && base != "rv32e" && base != "rv64i" && base != "rv64e")
return {};
str = str.substr(4);
std::optional<Extn> extn = read_extn_string(str);
if (!extn)
return {};
std::vector<Extn> vec;
extn->name = base;
vec.push_back(*extn);
// Parse extensions
while (!str.empty()) {
if (str[0] != '_')
return {};
str = str.substr(1);
std::optional<Extn> extn = read_extn_string(str);
if (!extn)
return {};
vec.push_back(*extn);
}
return vec;
}
static std::vector<Extn> merge_extensions(std::span<Extn> x, std::span<Extn> y) {
std::vector<Extn> vec;
// The base part (i.e. "rv64i" or "rv32i") must match.
if (x[0].name != y[0].name)
return {};
// Merge ISA extension strings
while (!x.empty() && !y.empty()) {
if (x[0].name == y[0].name) {
vec.push_back(extn_version_less(x[0], y[0]) ? y[0] : x[0]);
x = x.subspan(1);
y = y.subspan(1);
} else if (extn_name_less(x[0], y[0])) {
vec.push_back(x[0]);
x = x.subspan(1);
} else {
vec.push_back(y[0]);
y = y.subspan(1);
}
}
vec.insert(vec.end(), x.begin(), x.end());
vec.insert(vec.end(), y.begin(), y.end());
return vec;
}
static std::string to_string(std::span<Extn> v) {
std::string str = v[0].name + std::to_string(v[0].major) + "p" +
std::to_string(v[0].minor);
for (i64 i = 1; i < v.size(); i++)
str += "_" + v[i].name + std::to_string(v[i].major) + "p" +
std::to_string(v[i].minor);
return str;
}
//
// Output .riscv.attributes class
//
template <>
void RiscvAttributesSection<E>::update_shdr(Context<E> &ctx) {
if (!contents.empty())
return;
i64 stack = -1;
std::vector<Extn> arch;
bool unaligned = false;
for (ObjectFile<E> *file : ctx.objs) {
if (file->extra.stack_align) {
i64 val = *file->extra.stack_align;
if (stack != -1 && stack != val)
Error(ctx) << *file << ": stack alignment requirement mistmatch";
stack = val;
}
if (file->extra.arch) {
std::vector<Extn> arch2 = parse_arch_string(*file->extra.arch);
if (arch2.empty())
Error(ctx) << *file << ": corrupted .riscv.attributes ISA string: "
<< *file->extra.arch;
if (arch.empty()) {
arch = arch2;
} else {
arch = merge_extensions(arch, arch2);
if (arch.empty())
Error(ctx) << *file << ": incompatible .riscv.attributes ISA string: "
<< *file->extra.arch;
}
}
if (file->extra.unaligned_access)
unaligned = true;
}
if (arch.empty())
return;
std::string arch_str = to_string(arch);
contents.resize(arch_str.size() + 100);
u8 *p = (u8 *)contents.data();
*p++ = 'A'; // Format version
U32<E> *sub_sz = (U32<E> *)p; // Sub-section length
p += 4;
p += write_string(p, "riscv"); // Vendor name
u8 *sub_sub_start = p;
*p++ = ELF_TAG_FILE; // Sub-section tag
U32<E> *sub_sub_sz = (U32<E> *)p; // Sub-sub-section length
p += 4;
if (stack != -1) {
p += write_uleb(p, ELF_TAG_RISCV_STACK_ALIGN);
p += write_uleb(p, stack);
}
p += write_uleb(p, ELF_TAG_RISCV_ARCH);
p += write_string(p, arch_str);
if (unaligned) {
p += write_uleb(p, ELF_TAG_RISCV_UNALIGNED_ACCESS);
p += write_uleb(p, 1);
}
i64 sz = p - (u8 *)contents.data();
*sub_sz = sz - 1;
*sub_sub_sz = p - sub_sub_start;
contents.resize(sz);
this->shdr.sh_size = sz;
}
template <>
void RiscvAttributesSection<E>::copy_buf(Context<E> &ctx) {
memcpy(ctx.buf + this->shdr.sh_offset, contents.data(), contents.size());
}
} // namespace mold::elf
#endif
+111 -162
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 a 64-bit i386 in a parallel universe.
// just feels like an x86-64 in a parallel universe.
//
// Here is the register usage in this ABI:
//
@@ -25,15 +25,15 @@
// a1: upper 32 bits of TP (thread pointer)
// a2: lower 32 bits of TP (thread pointer)
//
// TLS is supported on s390x in the same way as it is on other targets
// with one exeption. On other targets, __tls_get_addr is used to get an
// address of a thread-local variable. On s390x, __tls_get_offset is used
// instead. The difference is __tls_get_offset returns an address of a
// thread-local variable as an offset from TP. So we need to add TP to a
// return value before use. I don't know why it is different, but that is
// the way it is.
// Thread-local storage (TLS) is supported on s390x in the same way as it
// is on other targets with one exeption. On other targets, __tls_get_addr
// is used to get an address of a thread-local variable. On s390x,
// __tls_get_offset is used instead. The difference is __tls_get_offset
// returns an address of a thread-local variable as an offset from TP. So
// we need to add TP to a return value before use. I don't know why it is
// different, but that is the way it is.
//
// https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
// https://github.com/rui314/psabi/blob/main/s390x.pdf
#include "mold.h"
@@ -90,8 +90,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -149,136 +149,97 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_390_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_390_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_390_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_390_12: {
i64 val = S + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
case R_390_12:
check(S + A, 0, 1 << 12);
*(ul16 *)loc |= bits(S + A, 11, 0);
break;
}
case R_390_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
case R_390_16:
check(S + A, 0, 1 << 16);
*(ub16 *)loc = S + A;
break;
}
case R_390_20: {
i64 val = S + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
case R_390_20:
check(S + A, 0, 1 << 20);
write_mid20(loc, S + A);
break;
}
case R_390_32:
case R_390_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_390_PLT32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_390_PLT64:
*(ub64 *)loc = S + A;
break;
case R_390_PC12DBL:
case R_390_PLT12DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 12), 1 << 12);
*(ul16 *)loc = (val >> 1) & 0x0fff;
case R_390_PLT12DBL:
check_dbl(S + A - P, -(1 << 12), 1 << 12);
*(ul16 *)loc |= bits(S + A - P, 12, 1);
break;
}
case R_390_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
case R_390_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - P;
break;
}
case R_390_PC32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
case R_390_PC32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - P;
break;
}
case R_390_PC64:
*(ub64 *)loc = S + A - P;
break;
case R_390_PC16DBL:
case R_390_PLT16DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 16), 1 << 16);
*(ub16 *)loc = val >> 1;
case R_390_PLT16DBL:
check_dbl(S + A - P, -(1 << 16), 1 << 16);
*(ub16 *)loc = (S + A - P) >> 1;
break;
}
case R_390_PC24DBL:
case R_390_PLT24DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 24), 1 << 24);
*(ub32 *)loc |= bits(val, 24, 1);
case R_390_PLT24DBL:
check_dbl(S + A - P, -(1 << 24), 1 << 24);
*(ub32 *)loc |= bits(S + A - P, 24, 1);
break;
}
case R_390_PC32DBL:
case R_390_PLT32DBL:
if (ctx.is_static && &sym == ctx.tls_get_offset) {
// __tls_get_offset() in libc.a is stub code that calls abort().
// So we provide a replacement function.
*(ub32 *)loc = (ctx.extra.tls_get_offset->shdr.sh_addr - P) >> 1;
} else {
i64 val = S + A - P;
check_dbl(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
}
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (S + A - P) >> 1;
break;
case R_390_GOT12:
case R_390_GOTPLT12: {
i64 val = G + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
case R_390_GOTPLT12:
check(G + A, 0, 1 << 12);
*(ul16 *)loc |= bits(G + A, 11, 0);
break;
}
case R_390_GOT16:
case R_390_GOTPLT16: {
i64 val = G + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
case R_390_GOTPLT16:
check(G + A, 0, 1 << 16);
*(ub16 *)loc = G + A;
break;
}
case R_390_GOT20:
case R_390_GOTPLT20: {
i64 val = G + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
case R_390_GOTPLT20:
check(G + A, 0, 1 << 20);
write_mid20(loc, G + A);
break;
}
case R_390_GOT32:
case R_390_GOTPLT32: {
i64 val = G + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_390_GOTPLT32:
check(G + A, 0, 1LL << 32);
*(ub32 *)loc = G + A;
break;
}
case R_390_GOT64:
case R_390_GOTPLT64:
*(ub64 *)loc = G + A;
break;
case R_390_GOTOFF16:
case R_390_PLTOFF16: {
i64 val = S + A - GOT;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
case R_390_PLTOFF16:
check(S + A - GOT, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - GOT;
break;
}
case R_390_GOTOFF32:
case R_390_PLTOFF32: {
i64 val = S + A - GOT;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
case R_390_PLTOFF32:
check(S + A - GOT, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - GOT;
break;
}
case R_390_GOTOFF64:
case R_390_PLTOFF64:
*(ub64 *)loc = S + A - GOT;
@@ -286,18 +247,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_390_GOTPC:
*(ub64 *)loc = GOT + A - P;
break;
case R_390_GOTPCDBL: {
i64 val = GOT + A - P;
check_dbl(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
case R_390_GOTPCDBL:
check_dbl(GOT + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (GOT + A - P) >> 1;
break;
}
case R_390_GOTENT: {
i64 val = GOT + G + A - P;
check(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
case R_390_GOTENT:
check(GOT + G + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (GOT + G + A - P) >> 1;
break;
}
case R_390_TLS_LE32:
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
@@ -313,19 +270,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_390_TLS_GD32:
if (sym.has_tlsgd(ctx))
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
else if (sym.has_gottp(ctx))
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
else
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_GD64:
if (sym.has_tlsgd(ctx))
*(ub64 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
else if (sym.has_gottp(ctx))
*(ub64 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
else
*(ub64 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_GDCALL:
if (!sym.has_tlsgd(ctx)) {
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
if (sym.has_tlsgd(ctx)) {
// do nothing
} else if (sym.has_gottp(ctx)) {
// lg %r2, 0(%r2, %r12)
static u8 insn[] = { 0xe3, 0x22, 0xc0, 0x00, 0x00, 0x04 };
memcpy(loc, insn, sizeof(insn));
} else {
// nop
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, insn, sizeof(insn));
}
break;
case R_390_TLS_LDM32:
@@ -350,8 +318,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_390_TLS_LDCALL:
if (!ctx.got->has_tlsld(ctx)) {
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
// nop
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, insn, sizeof(insn));
}
break;
default:
@@ -366,17 +335,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -426,18 +390,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_390_64:
@@ -473,7 +432,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_GOTPC:
case R_390_GOTPCDBL:
case R_390_GOTENT:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_390_PLT12DBL:
case R_390_PLT16DBL:
@@ -485,57 +444,47 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_PLTOFF32:
case R_390_PLTOFF64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_390_TLS_GOTIE20:
case R_390_TLS_IEENT:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_390_TLS_GD32:
case R_390_TLS_GD64:
if (!relax_tlsgd(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
// We always want to relax calls to __tls_get_offset() in statically-
// linked executables because __tls_get_offset() in libc.a just calls
// abort().
if (ctx.arg.is_static ||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
// do nothing
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
!ctx.arg.z_dlopen) {
sym.flags |= NEEDS_GOTTP;
} else {
sym.flags |= NEEDS_TLSGD;
}
break;
case R_390_TLS_LDM32:
case R_390_TLS_LDM64:
if (!relax_tlsld(ctx))
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
case R_390_TLS_LDM64: {
bool do_relax = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
if (!do_relax)
ctx.needs_tlsld = true;
break;
}
case R_390_TLS_LE32:
case R_390_TLS_LE64:
check_tlsle(ctx, sym, rel);
break;
case R_390_TLS_LDO32:
case R_390_TLS_LDO64:
case R_390_TLS_GDCALL:
case R_390_TLS_LDCALL:
break;
default:
Fatal(ctx) << *this << ": scan_relocations: " << rel;
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
// __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
+20 -21
View File
@@ -26,6 +26,13 @@
// small to contain large immediates. On SH-4, large immediates are
// loaded from memory using `mov.l` PC-relative load instruction.
//
// - Many RISC ISAs are, despite their name, actually fairly complex.
// They tend to have hundreds if not thousands of different instructions.
// SH-4 doesn't really have that many instructions because its 16-bit
// machine code simply can't encode many different opcodes. As a
// result, the number of relocations the linker has to support is also
// small.
//
// Beside these, SH-4 has a delay branch slot just like contemporary MIPS
// and SPARC. That is, one instruction after a branch instruction will
// always be executed even if the branch is taken. Delay branch slot allows
@@ -179,8 +186,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -222,7 +229,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_SH_DIR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_SH_REL32:
case R_SH_PLT32:
@@ -264,17 +271,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -305,16 +307,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
@@ -326,25 +323,27 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_SH_GOT32:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_SH_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_SH_TLS_GD_32:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_SH_TLS_LD_32:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_SH_TLS_IE_32:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_SH_TLS_LE_32:
check_tlsle(ctx, sym, rel);
break;
case R_SH_GOTPC:
case R_SH_GOTOFF:
case R_SH_TLS_LDO_32:
case R_SH_TLS_LE_32:
break;
default:
Fatal(ctx) << *this << ": unknown relocation: " << rel;
+75 -112
View File
@@ -54,8 +54,7 @@
// This scheme is very similar to i386. That may not be a coincidence
// because the i386 ELF psABI is created by Sun Microsystems too.
//
// https://docs.oracle.com/cd/E36784_01/html/E36857/chapter6-62988.html
// https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-40/index.html
// https://github.com/rui314/psabi/blob/main/sparc.pdf
#include "mold.h"
@@ -120,8 +119,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -171,99 +170,73 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_SPARC_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_SPARC_5: {
i64 val = S + A;
check(val, 0, 1 << 5);
*(ub32 *)loc |= bits(val, 4, 0);
case R_SPARC_5:
check(S + A, 0, 1 << 5);
*(ub32 *)loc |= bits(S + A, 4, 0);
break;
}
case R_SPARC_6: {
i64 val = S + A;
check(val, 0, 1 << 6);
*(ub32 *)loc |= bits(val, 5, 0);
case R_SPARC_6:
check(S + A, 0, 1 << 6);
*(ub32 *)loc |= bits(S + A, 5, 0);
break;
}
case R_SPARC_7: {
i64 val = S + A;
check(val, 0, 1 << 7);
*(ub32 *)loc |= bits(val, 6, 0);
case R_SPARC_7:
check(S + A, 0, 1 << 7);
*(ub32 *)loc |= bits(S + A, 6, 0);
break;
}
case R_SPARC_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*(u8 *)loc = val;
case R_SPARC_8:
check(S + A, 0, 1 << 8);
*(u8 *)loc = S + A;
break;
}
case R_SPARC_10: {
i64 val = S + A;
check(val, 0, 1 << 10);
*(ub32 *)loc |= bits(val, 9, 0);
case R_SPARC_10:
check(S + A, 0, 1 << 10);
*(ub32 *)loc |= bits(S + A, 9, 0);
break;
}
case R_SPARC_LO10:
case R_SPARC_LOPLT10:
*(ub32 *)loc |= bits(S + A, 9, 0);
break;
case R_SPARC_11: {
i64 val = S + A;
check(val, 0, 1 << 11);
*(ub32 *)loc |= bits(val, 10, 0);
case R_SPARC_11:
check(S + A, 0, 1 << 11);
*(ub32 *)loc |= bits(S + A, 10, 0);
break;
}
case R_SPARC_13: {
i64 val = S + A;
check(val, 0, 1 << 13);
*(ub32 *)loc |= bits(val, 12, 0);
case R_SPARC_13:
check(S + A, 0, 1 << 13);
*(ub32 *)loc |= bits(S + A, 12, 0);
break;
}
case R_SPARC_16:
case R_SPARC_UA16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
case R_SPARC_UA16:
check(S + A, 0, 1 << 16);
*(ub16 *)loc = S + A;
break;
}
case R_SPARC_22: {
i64 val = S + A;
check(val, 0, 1 << 22);
*(ub32 *)loc |= bits(val, 21, 0);
case R_SPARC_22:
check(S + A, 0, 1 << 22);
*(ub32 *)loc |= bits(S + A, 21, 0);
break;
}
case R_SPARC_32:
case R_SPARC_UA32:
case R_SPARC_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_SPARC_PLT32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_SPARC_PLT64:
case R_SPARC_UA64:
case R_SPARC_REGISTER:
*(ub64 *)loc = S + A;
break;
case R_SPARC_DISP8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*(u8 *)loc = val;
case R_SPARC_DISP8:
check(S + A - P, -(1 << 7), 1 << 7);
*(u8 *)loc = S + A - P;
break;
}
case R_SPARC_DISP16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
case R_SPARC_DISP16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - P;
break;
}
case R_SPARC_DISP32:
case R_SPARC_PCPLT32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
case R_SPARC_PCPLT32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - P;
break;
}
case R_SPARC_DISP64:
*(ub64 *)loc = S + A - P;
break;
@@ -273,25 +246,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub16 *)loc |= (bit(val, 16) << 21) | bits(val, 15, 2);
break;
}
case R_SPARC_WDISP19: {
i64 val = S + A - P;
check(val, -(1 << 20), 1 << 20);
*(ub32 *)loc |= bits(val, 20, 2);
case R_SPARC_WDISP19:
check(S + A - P, -(1 << 20), 1 << 20);
*(ub32 *)loc |= bits(S + A - P, 20, 2);
break;
}
case R_SPARC_WDISP22: {
i64 val = S + A - P;
check(val, -(1 << 23), 1 << 23);
*(ub32 *)loc |= bits(val, 23, 2);
case R_SPARC_WDISP22:
check(S + A - P, -(1 << 23), 1 << 23);
*(ub32 *)loc |= bits(S + A - P, 23, 2);
break;
}
case R_SPARC_WDISP30:
case R_SPARC_WPLT30: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(val, 31, 2);
case R_SPARC_WPLT30:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(S + A - P, 31, 2);
break;
}
case R_SPARC_HI22:
case R_SPARC_HIPLT22:
case R_SPARC_LM22:
@@ -402,12 +369,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
case R_SPARC_TLS_LDM_CALL: {
u64 addr;
if (ctx.arg.is_static)
*(ub32 *)loc |= bits(ctx.extra.tls_get_addr->shdr.sh_addr + A - P, 31, 2);
addr = ctx.extra.tls_get_addr_sec->shdr.sh_addr;
else
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + A - P, 31, 2);
addr = ctx.extra.tls_get_addr_sym->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
break;
}
case R_SPARC_TLS_LDM_HI22:
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
break;
@@ -454,17 +425,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -516,18 +482,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_SPARC_64:
@@ -571,17 +532,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SPARC_PCPLT10:
case R_SPARC_PLT64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_SPARC_GOT13:
case R_SPARC_GOT10:
case R_SPARC_GOT22:
case R_SPARC_GOTDATA_HIX22:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_SPARC_GOTDATA_OP_HIX22:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_SPARC_DISP16:
case R_SPARC_DISP32:
@@ -597,18 +558,22 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_SPARC_TLS_GD_HI22:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_SPARC_TLS_LDM_HI22:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_SPARC_TLS_IE_HI22:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
ctx.tls_get_addr->flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
if (!ctx.arg.is_static && ctx.extra.tls_get_addr_sym->is_imported)
ctx.extra.tls_get_addr_sym->flags |= NEEDS_PLT;
break;
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
check_tlsle(ctx, sym, rel);
break;
case R_SPARC_GOTDATA_OP_LOX10:
case R_SPARC_GOTDATA_OP:
@@ -624,12 +589,10 @@ 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:
Fatal(ctx) << *this << ": scan_relocations: " << rel;
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
+225 -205
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/mold/wiki/i386-psabi.pdf
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
// https://github.com/rui314/psabi/blob/main/x86-64.pdf
// https://github.com/rui314/psabi/blob/main/i386.pdf
#include "mold.h"
@@ -89,8 +89,8 @@ 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) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -187,6 +187,142 @@ static u32 relax_gotpc32_tlsdesc(u8 *loc) {
return 0;
}
// Rewrite a function call to __tls_get_addr to a cheaper instruction
// sequence. We can do this when we know the thread-local variable's TP-
// relative address at link-time.
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
//
// or
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
};
memcpy(loc - 4, insn, sizeof(insn));
*(ul32 *)(loc + 8) = val;
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 9) = val;
break;
}
default:
unreachable();
}
}
static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
};
memcpy(loc - 4, insn, sizeof(insn));
*(ul32 *)(loc + 8) = val - 12;
break;
}
case R_X86_64_PLTOFF64: {
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 9) = val - 13;
break;
}
default:
unreachable();
}
}
// Rewrite a function call to __tls_get_addr to a cheaper instruction
// sequence. The difference from relax_gd_to_le is that we are
// materializing a Dynamic Thread Pointer for the current ELF module
// instead of an address for a particular thread-local variable.
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// e8 00 00 00 00 call __tls_get_addr
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
}
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x90, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
}
default:
unreachable();
}
}
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
// mapped to memory at runtime) based on the result of
// scan_relocations().
@@ -231,18 +367,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
switch (rel.r_type) {
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_X86_64_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
case R_X86_64_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
}
case R_X86_64_32:
write32(S + A);
break;
@@ -250,20 +382,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write32s(S + A);
break;
case R_X86_64_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_X86_64_PC8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*loc = val;
case R_X86_64_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
break;
}
case R_X86_64_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
case R_X86_64_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A - P;
break;
}
case R_X86_64_PC32:
case R_X86_64_PLT32:
write32s(S + A - P);
@@ -294,79 +422,43 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc = G + GOTPLT + A - P;
break;
case R_X86_64_GOTPCRELX:
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
// We always want to relax GOTPCRELX relocs even if --no-relax
// was given because some static PIE runtime code depends on these
// relaxations.
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
u32 insn = relax_gotpcrelx(loc - 2);
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
i64 val = S + A - P;
if (insn && (i32)val == val) {
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = val;
break;
}
}
write32s(G + GOTPLT + A - P);
break;
case R_X86_64_REX_GOTPCRELX:
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
u32 insn = relax_rex_gotpcrelx(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
i64 val = S + A - P;
if (insn && (i32)val == val) {
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = val;
break;
}
}
write32s(G + GOTPLT + A - P);
break;
case R_X86_64_TLSGD:
if (sym.has_tlsgd(ctx)) {
write32s(sym.get_tlsgd_addr(ctx) + A - P);
} else if (sym.has_gottp(ctx)) {
relax_gd_to_ie(loc, rels[i + 1], sym.get_gottp_addr(ctx) - P);
i++;
} else {
// Relax GD to LE. If we are creating an exectuable, the offset of
// a thread-local variable from TP is a link-time constant. So we
// don't need to call __tls_get_addr to obtain the address of a TLV.
i64 val = S - ctx.tp_addr;
assert(A == -4);
check(val, -(1LL << 31), 1LL << 31);
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
//
// or
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
};
memcpy(loc - 4, insn, sizeof(insn));
*(ul32 *)(loc + 8) = val;
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 9) = val;
break;
}
default:
unreachable();
}
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
i++;
}
break;
@@ -374,64 +466,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (ctx.got->has_tlsld(ctx)) {
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
} else {
// Relax LD to LE. If we are creating an executable, we don't need
// to call __tls_get_addr to obtain the address of the beginning
// of the current TLS block. TP points past the end of the TLS
// block, and we know the exact size of the TLS block we have
// created, so we can just subtract it from TP.
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// e8 00 00 00 00 call __tls_get_addr
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x90, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
default:
unreachable();
}
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
assert(A == -4);
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
i++;
}
break;
@@ -508,17 +543,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
@@ -544,18 +574,14 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_X86_64_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
case R_X86_64_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
}
case R_X86_64_32:
write32(S + A);
break;
@@ -580,6 +606,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_X86_64_GOTOFF64:
*(ul64 *)loc = S + A - ctx.gotplt->shdr.sh_addr;
break;
case R_X86_64_GOTPC64:
// PC-relative relocation doesn't make sense for non-memory-allocated
// section, but GCC 6.3.0 seems to create this reloc for
// _GLOBAL_OFFSET_TABLE_.
*(ul64 *)loc = ctx.gotplt->shdr.sh_addr + A;
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
break;
@@ -609,19 +644,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_X86_64_8:
@@ -645,34 +675,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_GOTPC64:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCREL64:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
case R_X86_64_GOTPCRELX:
case R_X86_64_REX_GOTPCRELX:
sym.flags |= NEEDS_GOT;
break;
case R_X86_64_GOTPCRELX: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_gotpcrelx(loc - 2);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_X86_64_REX_GOTPCRELX: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_X86_64_PLT32:
case R_X86_64_PLTOFF64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_X86_64_TLSGD: {
case R_X86_64_TLSGD:
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
@@ -685,13 +697,20 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (relax_tlsgd(ctx, sym))
if (ctx.arg.is_static ||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
i++;
else
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
!ctx.arg.z_dlopen) {
sym.flags |= NEEDS_GOTTP;
i++;
} else {
sym.flags |= NEEDS_TLSGD;
}
break;
}
case R_X86_64_TLSLD: {
case R_X86_64_TLSLD:
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
@@ -704,22 +723,21 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (relax_tlsld(ctx))
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
i++;
else
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
}
case R_X86_64_GOTTPOFF: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
!sym.is_imported && relax_gottpoff(loc - 3);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
}
case R_X86_64_GOTPC32_TLSDESC: {
@@ -731,14 +749,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
<< " against an invalid code sequence";
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSDESC;
break;
}
case R_X86_64_TPOFF32:
case R_X86_64_TPOFF64:
check_tlsle(ctx, sym, rel);
break;
case R_X86_64_GOTOFF64:
case R_X86_64_DTPOFF32:
case R_X86_64_DTPOFF64:
case R_X86_64_TPOFF32:
case R_X86_64_TPOFF64:
case R_X86_64_SIZE32:
case R_X86_64_SIZE64:
case R_X86_64_TLSDESC_CALL:
+67 -29
View File
@@ -1,6 +1,7 @@
#include "mold.h"
#include "../cmdline.h"
#include "../common/cmdline.h"
#include <random>
#include <regex>
#include <sstream>
#include <sys/stat.h>
@@ -98,7 +99,7 @@ Options:
--gc-sections Remove unreferenced sections
--no-gc-sections
--gdb-index Create .gdb_index for faster gdb startup
--hash-style [sysv,gnu,both]
--hash-style [sysv,gnu,both,none]
Set hash style
--icf=[all,safe,none] Fold identical code
--no-icf
@@ -187,18 +188,21 @@ Options:
-z origin Mark object requiring immediate $ORIGIN processing at runtime
-z pack-relative-relocs Alias for --pack-dyn-relocs=relr
-z nopack-relative-relocs
-z sectionheader Do not omit section header (default)
-z nosectionheader Omit section header
-z separate-loadable-segments
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 elf64-loongarch elf32-loongarch
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 elf64loongarch elf32loongarch)";
static std::vector<std::string> add_dashes(std::string name) {
// Single-letter option
@@ -405,6 +409,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
bool warn_shared_textrel = false;
std::optional<SeparateCodeKind> z_separate_code;
std::optional<bool> z_relro;
std::optional<u64> shuffle_sections_seed;
std::unordered_set<std::string_view> rpaths;
auto add_rpath = [&](std::string_view arg) {
@@ -423,7 +428,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
// It looks like the SPARC's dynamic linker takes both RELA's r_addend
// and the value at the relocated place. So we don't want to write
// values to relocated places.
if (is_sparc<E>)
if constexpr (is_sparc<E>)
ctx.arg.apply_dynamic_relocs = false;
auto read_arg = [&](std::string name) {
@@ -520,8 +525,17 @@ 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 elf64ltsmip\n elf64btsmip\n"
<< " elf64loongarch\n elf32loongarch";
version_shown = true;
} else if (read_flag("mips32") || read_flag("mips32r2") ||
read_flag("mips32r3") || read_flag("mips32r4") ||
read_flag("mips32r5") || read_flag("mips32r6") ||
read_flag("mips64") || read_flag("mips64r2") ||
read_flag("mips64r3") || read_flag("mips64r4") ||
read_flag("mips64r5") || read_flag("mips64r6")) {
// Ignore useless MIPS-specific flags
} else if (read_arg("m")) {
if (arg == "elf_x86_64") {
ctx.arg.emulation = X86_64::target_name;
@@ -555,6 +569,14 @@ 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 == "elf64loongarch") {
ctx.arg.emulation = LOONGARCH64::target_name;
} else if (arg == "elf32loongarch") {
ctx.arg.emulation = LOONGARCH32::target_name;
} else {
Fatal(ctx) << "unknown -m argument: " << arg;
}
@@ -582,9 +604,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.Map = arg;
ctx.arg.print_map = true;
} else if (read_flag("print-dependencies")) {
ctx.arg.print_dependencies = 1;
} else if (read_flag("print-dependencies=full")) {
ctx.arg.print_dependencies = 2;
ctx.arg.print_dependencies = true;
} else if (read_flag("print-map") || read_flag("M")) {
ctx.arg.print_map = true;
} else if (read_flag("Bstatic") || read_flag("dn") || read_flag("static")) {
@@ -625,7 +645,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
} else if (read_eq("shuffle-sections")) {
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
ctx.arg.shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
} else if (read_flag("reverse-sections")) {
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_REVERSE;
} else if (read_flag("rosegment")) {
@@ -652,7 +672,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.unresolved_symbols = UNRESOLVED_IGNORE;
else
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
} else if (read_arg("u") || read_arg("undefined")) {
} else if (read_arg("undefined") || read_arg("u")) {
ctx.arg.undefined.push_back(arg);
} else if (read_arg("require-defined")) {
ctx.arg.require_defined.push_back(arg);
@@ -670,6 +690,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (arg == "both") {
ctx.arg.hash_style_sysv = true;
ctx.arg.hash_style_gnu = true;
} else if (arg == "none") {
ctx.arg.hash_style_sysv = false;
ctx.arg.hash_style_gnu = false;
} else {
Fatal(ctx) << "invalid --hash-style argument: " << arg;
}
@@ -691,7 +714,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("pie") || read_flag("pic-executable")) {
ctx.arg.pic = true;
ctx.arg.pie = true;
} else if (read_flag("no-pie") || read_flag("no-pic-executable")) {
} else if (read_flag("no-pie") || read_flag("no-pic-executable") ||
read_flag("nopie")) {
ctx.arg.pic = false;
ctx.arg.pie = false;
} else if (read_flag("relax")) {
@@ -710,9 +734,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.relocatable_merge_sections = true;
} else if (read_flag("perf")) {
ctx.arg.perf = true;
} else if (read_flag("pack-dyn-relocs=relr")) {
} else if (read_flag("pack-dyn-relocs=relr") ||
read_z_flag("pack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = true;
} else if (read_flag("pack-dyn-relocs=none")) {
} else if (read_flag("pack-dyn-relocs=none") ||
read_z_flag("nopack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = false;
} else if (read_arg("package-metadata")) {
ctx.arg.package_metadata = arg;
@@ -854,20 +880,22 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.z_origin = true;
} else if (read_z_flag("nodefaultlib")) {
ctx.arg.z_nodefaultlib = true;
} else if (read_z_flag("pack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = true;
} else if (read_z_flag("nopack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = false;
} else if (read_z_flag("separate-loadable-segments")) {
z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
} else if (read_z_flag("separate-code")) {
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")) {
ctx.arg.z_dynamic_undefined_weak = false;
} else if (read_z_flag("sectionheader")) {
ctx.arg.z_sectionheader = true;
} else if (read_z_flag("nosectionheader")) {
ctx.arg.z_sectionheader = false;
} else if (read_flag("no-undefined")) {
ctx.arg.z_defs = true;
} else if (read_flag("fatal-warnings")) {
@@ -993,7 +1021,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("strip-all") || read_flag("s")) {
ctx.arg.strip_all = true;
} else if (read_flag("strip-debug") || read_flag("S")) {
ctx.arg.strip_all = true;
ctx.arg.strip_debug = true;
} else if (read_flag("warn-unresolved-symbols")) {
ctx.arg.unresolved_symbols = UNRESOLVED_WARN;
} else if (read_flag("error-unresolved-symbols")) {
@@ -1046,6 +1074,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("filter") || read_arg("F")) {
ctx.arg.filter.push_back(arg);
} else if (read_arg("O")) {
} else if (read_flag("EB")) {
} else if (read_flag("EL")) {
} else if (read_flag("O0")) {
} else if (read_flag("O1")) {
} else if (read_flag("O2")) {
@@ -1086,7 +1116,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("max-cache-size")) {
} else if (read_arg("version-script")) {
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
// are treated as positional arguments even if they are actually not
// are treated as positional arguments even though they are actually not
// positional. This is because linker scripts (a positional argument)
// can also specify a version script, and it's better to consolidate
// parsing in read_input_files. In particular, version scripts can
@@ -1123,10 +1153,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Warn(ctx) << "unknown command line option: -z " << args[1];
args = args.subspan(2);
} else if (args[0] == "-dynamic") {
Fatal(ctx) << "unknown command line option: -dynamic;"
<< " -dynamic is a macOS linker's option. If you are trying"
<< " to build a binary for an Apple platform, you need to use"
<< " ld64.mold instead of mold or ld.mold.";
Fatal(ctx) << "unknown command line option: -dynamic; -dynamic is a "
<< "macOS linker's option. mold does not support macOS.";
} else {
if (args[0][0] == '-')
Fatal(ctx) << "unknown command line option: " << args[0];
@@ -1163,6 +1191,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.relocatable)
ctx.arg.is_static = true;
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_SHUFFLE) {
if (shuffle_sections_seed)
ctx.arg.shuffle_sections_seed = *shuffle_sections_seed;
else
ctx.arg.shuffle_sections_seed =
((u64)std::random_device()() << 32) | std::random_device()();
}
// --section-order implies `-z separate-loadable-segments`
if (z_separate_code)
ctx.arg.z_separate_code = *z_separate_code;
@@ -1182,12 +1218,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Fatal(ctx) << "-auxiliary may not be used without -shared";
}
if (!ctx.arg.apply_dynamic_relocs && !E::is_rela)
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
<< E::target_name;
if constexpr (!E::is_rela || is_mips<E>)
if (!ctx.arg.apply_dynamic_relocs)
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
<< E::target_name;
if (is_sparc<E> && ctx.arg.apply_dynamic_relocs)
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
if constexpr (is_sparc<E>)
if (ctx.arg.apply_dynamic_relocs)
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
if (!ctx.arg.section_start.empty() && !ctx.arg.section_order.empty())
Fatal(ctx) << "--section-start may not be used with --section-order";
+20 -20
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() == 0xffffffff)
if (*(U32<E> *)data.data() == 0xffff'ffff)
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
i64 len = *(U32<E> *)data.data() + 4;
vec.push_back(data.substr(0, len));
@@ -216,7 +216,7 @@ find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
<< dwarf_version;
}
u32 abbrev_code = read_uleb(cu);
u32 abbrev_code = read_uleb(&cu);
// Find a .debug_abbrev record corresponding to the .debug_info record.
// We assume the .debug_info record at a given offset is of
@@ -224,14 +224,14 @@ find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
for (;;) {
u32 code = read_uleb(abbrev);
u32 code = read_uleb(&abbrev);
if (code == 0)
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
<< " a record for the first .debug_info record";
if (code == abbrev_code) {
// Found a record
u64 abbrev_tag = read_uleb(abbrev);
u64 abbrev_tag = read_uleb(&abbrev);
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
@@ -240,15 +240,15 @@ find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
}
// Skip an uninteresting record
read_uleb(abbrev); // tag
read_uleb(&abbrev); // tag
abbrev++; // has_children byte
for (;;) {
u64 name = read_uleb(abbrev);
u64 form = read_uleb(abbrev);
u64 name = read_uleb(&abbrev);
u64 form = read_uleb(&abbrev);
if (name == 0 && form == 0)
break;
if (form == DW_FORM_implicit_const)
read_uleb(abbrev);
read_uleb(&abbrev);
}
}
@@ -295,7 +295,7 @@ public:
// Read value of the given DW_FORM_* form. If a value is not scalar,
// returns a dummy value 0.
template <typename E>
inline u64 DebugInfoReader<E>::read(u64 form) {
u64 DebugInfoReader<E>::read(u64 form) {
switch (form) {
case DW_FORM_flag_present:
return 0;
@@ -348,7 +348,7 @@ inline u64 DebugInfoReader<E>::read(u64 form) {
case DW_FORM_ref_udata:
case DW_FORM_loclistx:
case DW_FORM_rnglistx:
return read_uleb(cu);
return read_uleb(&cu);
case DW_FORM_string:
cu += strlen((char *)cu) + 1;
return 0;
@@ -391,19 +391,19 @@ read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
case DW_RLE_end_of_list:
return vec;
case DW_RLE_base_addressx:
base = addrx[read_uleb(rnglist)];
base = addrx[read_uleb(&rnglist)];
break;
case DW_RLE_startx_endx:
vec.push_back(addrx[read_uleb(rnglist)]);
vec.push_back(addrx[read_uleb(rnglist)]);
vec.push_back(addrx[read_uleb(&rnglist)]);
vec.push_back(addrx[read_uleb(&rnglist)]);
break;
case DW_RLE_startx_length:
vec.push_back(addrx[read_uleb(rnglist)]);
vec.push_back(vec.back() + read_uleb(rnglist));
vec.push_back(addrx[read_uleb(&rnglist)]);
vec.push_back(vec.back() + read_uleb(&rnglist));
break;
case DW_RLE_offset_pair:
vec.push_back(base + read_uleb(rnglist));
vec.push_back(base + read_uleb(rnglist));
vec.push_back(base + read_uleb(&rnglist));
vec.push_back(base + read_uleb(&rnglist));
break;
case DW_RLE_base_address:
base = *(Word<E> *)rnglist;
@@ -418,7 +418,7 @@ read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
case DW_RLE_start_length:
vec.push_back(*(Word<E> *)rnglist);
rnglist += sizeof(Word<E>);
vec.push_back(vec.back() + read_uleb(rnglist));
vec.push_back(vec.back() + read_uleb(&rnglist));
break;
}
}
@@ -455,8 +455,8 @@ read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
// Read all interesting debug records.
for (;;) {
u64 name = read_uleb(abbrev);
u64 form = read_uleb(abbrev);
u64 name = read_uleb(&abbrev);
u64 form = read_uleb(&abbrev);
if (name == 0 && form == 0)
break;
+223 -12
View File
@@ -2,6 +2,12 @@
namespace mold::elf {
static std::string unknown_type(u32 r_type) {
char buf[50];
snprintf(buf, sizeof(buf), "unknown (0x%x)", r_type);
return buf;
}
template <>
std::string rel_to_string<X86_64>(u32 r_type) {
switch (r_type) {
@@ -46,7 +52,7 @@ std::string rel_to_string<X86_64>(u32 r_type) {
case R_X86_64_GOTPCRELX: return "R_X86_64_GOTPCRELX";
case R_X86_64_REX_GOTPCRELX: return "R_X86_64_REX_GOTPCRELX";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -95,7 +101,7 @@ std::string rel_to_string<I386>(u32 r_type) {
case R_386_IRELATIVE: return "R_386_IRELATIVE";
case R_386_GOT32X: return "R_386_GOT32X";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -206,7 +212,7 @@ std::string rel_to_string<ARM64>(u32 r_type) {
case R_AARCH64_TLSDESC: return "R_AARCH64_TLSDESC";
case R_AARCH64_IRELATIVE: return "R_AARCH64_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -348,7 +354,7 @@ std::string rel_to_string<ARM32>(u32 r_type) {
case R_ARM_THM_BF18: return "R_ARM_THM_BF18";
case R_ARM_IRELATIVE: return "R_ARM_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -403,8 +409,11 @@ 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) + ")";
return unknown_type(r_type);
}
template <>
@@ -501,7 +510,7 @@ std::string rel_to_string<PPC32>(u32 r_type) {
case R_PPC_REL16_HI: return "R_PPC_REL16_HI";
case R_PPC_REL16_HA: return "R_PPC_REL16_HA";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -602,9 +611,13 @@ std::string rel_to_string<PPC64V1>(u32 r_type) {
case R_PPC64_REL24_NOTOC: return "R_PPC64_REL24_NOTOC";
case R_PPC64_PLTSEQ: return "R_PPC64_PLTSEQ";
case R_PPC64_PLTCALL: return "R_PPC64_PLTCALL";
case R_PPC64_PLTSEQ_NOTOC: return "R_PPC64_PLTSEQ_NOTOC";
case R_PPC64_PLTCALL_NOTOC: return "R_PPC64_PLTCALL_NOTOC";
case R_PPC64_PCREL_OPT: return "R_PPC64_PCREL_OPT";
case R_PPC64_PCREL34: return "R_PPC64_PCREL34";
case R_PPC64_GOT_PCREL34: return "R_PPC64_GOT_PCREL34";
case R_PPC64_PLT_PCREL34: return "R_PPC64_PLT_PCREL34";
case R_PPC64_PLT_PCREL34_NOTOC: return "R_PPC64_PLT_PCREL34_NOTOC";
case R_PPC64_TPREL34: return "R_PPC64_TPREL34";
case R_PPC64_DTPREL34: return "R_PPC64_DTPREL34";
case R_PPC64_GOT_TLSGD_PCREL34: return "R_PPC64_GOT_TLSGD_PCREL34";
@@ -616,7 +629,7 @@ std::string rel_to_string<PPC64V1>(u32 r_type) {
case R_PPC64_REL16_HI: return "R_PPC64_REL16_HI";
case R_PPC64_REL16_HA: return "R_PPC64_REL16_HA";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -713,7 +726,7 @@ std::string rel_to_string<SPARC64>(u32 r_type) {
case R_SPARC_GOTDATA_OP: return "R_SPARC_GOTDATA_OP";
case R_SPARC_IRELATIVE: return "R_SPARC_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -786,7 +799,7 @@ std::string rel_to_string<S390X>(u32 r_type) {
case R_390_PC24DBL: return "R_390_PC24DBL";
case R_390_PLT24DBL: return "R_390_PLT24DBL";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -834,7 +847,7 @@ std::string rel_to_string<M68K>(u32 r_type) {
case R_68K_TLS_DTPREL32: return "R_68K_TLS_DTPREL32";
case R_68K_TLS_TPREL32: return "R_68K_TLS_TPREL32";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -868,7 +881,7 @@ std::string rel_to_string<SH4>(u32 r_type) {
case R_SH_GOTPC: return "R_SH_GOTPC";
case R_SH_GOTPLT32: return "R_SH_GOTPLT32";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
@@ -908,7 +921,205 @@ std::string rel_to_string<ALPHA>(u32 r_type) {
case R_ALPHA_TPRELLO: return "R_ALPHA_TPRELLO";
case R_ALPHA_TPREL16: return "R_ALPHA_TPREL16";
}
return "unknown (" + std::to_string(r_type) + ")";
return unknown_type(r_type);
}
template <>
std::string rel_to_string<MIPS64LE>(u32 r_type) {
switch (r_type) {
case R_MIPS_NONE: return "R_MIPS_NONE";
case R_MIPS_16: return "R_MIPS_16";
case R_MIPS_32: return "R_MIPS_32";
case R_MIPS_REL32: return "R_MIPS_REL32";
case R_MIPS_26: return "R_MIPS_26";
case R_MIPS_HI16: return "R_MIPS_HI16";
case R_MIPS_LO16: return "R_MIPS_LO16";
case R_MIPS_GPREL16: return "R_MIPS_GPREL16";
case R_MIPS_LITERAL: return "R_MIPS_LITERAL";
case R_MIPS_GOT16: return "R_MIPS_GOT16";
case R_MIPS_PC16: return "R_MIPS_PC16";
case R_MIPS_CALL16: return "R_MIPS_CALL16";
case R_MIPS_GPREL32: return "R_MIPS_GPREL32";
case R_MIPS_UNUSED1: return "R_MIPS_UNUSED1";
case R_MIPS_UNUSED2: return "R_MIPS_UNUSED2";
case R_MIPS_UNUSED3: return "R_MIPS_UNUSED3";
case R_MIPS_SHIFT5: return "R_MIPS_SHIFT5";
case R_MIPS_SHIFT6: return "R_MIPS_SHIFT6";
case R_MIPS_64: return "R_MIPS_64";
case R_MIPS_GOT_DISP: return "R_MIPS_GOT_DISP";
case R_MIPS_GOT_PAGE: return "R_MIPS_GOT_PAGE";
case R_MIPS_GOT_OFST: return "R_MIPS_GOT_OFST";
case R_MIPS_GOT_HI16: return "R_MIPS_GOT_HI16";
case R_MIPS_GOT_LO16: return "R_MIPS_GOT_LO16";
case R_MIPS_SUB: return "R_MIPS_SUB";
case R_MIPS_INSERT_A: return "R_MIPS_INSERT_A";
case R_MIPS_INSERT_B: return "R_MIPS_INSERT_B";
case R_MIPS_DELETE: return "R_MIPS_DELETE";
case R_MIPS_HIGHER: return "R_MIPS_HIGHER";
case R_MIPS_HIGHEST: return "R_MIPS_HIGHEST";
case R_MIPS_CALL_HI16: return "R_MIPS_CALL_HI16";
case R_MIPS_CALL_LO16: return "R_MIPS_CALL_LO16";
case R_MIPS_SCN_DISP: return "R_MIPS_SCN_DISP";
case R_MIPS_REL16: return "R_MIPS_REL16";
case R_MIPS_ADD_IMMEDIATE: return "R_MIPS_ADD_IMMEDIATE";
case R_MIPS_PJUMP: return "R_MIPS_PJUMP";
case R_MIPS_RELGOT: return "R_MIPS_RELGOT";
case R_MIPS_JALR: return "R_MIPS_JALR";
case R_MIPS_TLS_DTPMOD32: return "R_MIPS_TLS_DTPMOD32";
case R_MIPS_TLS_DTPREL32: return "R_MIPS_TLS_DTPREL32";
case R_MIPS_TLS_DTPMOD64: return "R_MIPS_TLS_DTPMOD64";
case R_MIPS_TLS_DTPREL64: return "R_MIPS_TLS_DTPREL64";
case R_MIPS_TLS_GD: return "R_MIPS_TLS_GD";
case R_MIPS_TLS_LDM: return "R_MIPS_TLS_LDM";
case R_MIPS_TLS_DTPREL_HI16: return "R_MIPS_TLS_DTPREL_HI16";
case R_MIPS_TLS_DTPREL_LO16: return "R_MIPS_TLS_DTPREL_LO16";
case R_MIPS_TLS_GOTTPREL: return "R_MIPS_TLS_GOTTPREL";
case R_MIPS_TLS_TPREL32: return "R_MIPS_TLS_TPREL32";
case R_MIPS_TLS_TPREL64: return "R_MIPS_TLS_TPREL64";
case R_MIPS_TLS_TPREL_HI16: return "R_MIPS_TLS_TPREL_HI16";
case R_MIPS_TLS_TPREL_LO16: return "R_MIPS_TLS_TPREL_LO16";
case R_MIPS_GLOB_DAT: return "R_MIPS_GLOB_DAT";
case R_MIPS_PC21_S2: return "R_MIPS_PC21_S2";
case R_MIPS_PC26_S2: return "R_MIPS_PC26_S2";
case R_MIPS_PC18_S3: return "R_MIPS_PC18_S3";
case R_MIPS_PC19_S2: return "R_MIPS_PC19_S2";
case R_MIPS_PCHI16: return "R_MIPS_PCHI16";
case R_MIPS_PCLO16: return "R_MIPS_PCLO16";
case R_MIPS16_26: return "R_MIPS16_26";
case R_MIPS16_GPREL: return "R_MIPS16_GPREL";
case R_MIPS16_GOT16: return "R_MIPS16_GOT16";
case R_MIPS16_CALL16: return "R_MIPS16_CALL16";
case R_MIPS16_HI16: return "R_MIPS16_HI16";
case R_MIPS16_LO16: return "R_MIPS16_LO16";
case R_MIPS16_TLS_GD: return "R_MIPS16_TLS_GD";
case R_MIPS16_TLS_LDM: return "R_MIPS16_TLS_LDM";
case R_MIPS16_TLS_DTPREL_HI16: return "R_MIPS16_TLS_DTPREL_HI16";
case R_MIPS16_TLS_DTPREL_LO16: return "R_MIPS16_TLS_DTPREL_LO16";
case R_MIPS16_TLS_GOTTPREL: return "R_MIPS16_TLS_GOTTPREL";
case R_MIPS16_TLS_TPREL_HI16: return "R_MIPS16_TLS_TPREL_HI16";
case R_MIPS16_TLS_TPREL_LO16: return "R_MIPS16_TLS_TPREL_LO16";
case R_MIPS_COPY: return "R_MIPS_COPY";
case R_MIPS_JUMP_SLOT: return "R_MIPS_JUMP_SLOT";
case R_MIPS_NUM: return "R_MIPS_NUM";
case R_MIPS_PC32: return "R_MIPS_PC32";
case R_MIPS_EH: return "R_MIPS_EH";
}
return unknown_type(r_type);
}
template <>
std::string rel_to_string<MIPS64BE>(u32 r_type) {
return rel_to_string<MIPS64LE>(r_type);
}
template <>
std::string rel_to_string<LOONGARCH64>(u32 r_type) {
switch (r_type) {
case R_LARCH_NONE: return "R_LARCH_NONE";
case R_LARCH_32: return "R_LARCH_32";
case R_LARCH_64: return "R_LARCH_64";
case R_LARCH_RELATIVE: return "R_LARCH_RELATIVE";
case R_LARCH_COPY: return "R_LARCH_COPY";
case R_LARCH_JUMP_SLOT: return "R_LARCH_JUMP_SLOT";
case R_LARCH_TLS_DTPMOD32: return "R_LARCH_TLS_DTPMOD32";
case R_LARCH_TLS_DTPMOD64: return "R_LARCH_TLS_DTPMOD64";
case R_LARCH_TLS_DTPREL32: return "R_LARCH_TLS_DTPREL32";
case R_LARCH_TLS_DTPREL64: return "R_LARCH_TLS_DTPREL64";
case R_LARCH_TLS_TPREL32: return "R_LARCH_TLS_TPREL32";
case R_LARCH_TLS_TPREL64: return "R_LARCH_TLS_TPREL64";
case R_LARCH_IRELATIVE: return "R_LARCH_IRELATIVE";
case R_LARCH_MARK_LA: return "R_LARCH_MARK_LA";
case R_LARCH_MARK_PCREL: return "R_LARCH_MARK_PCREL";
case R_LARCH_SOP_PUSH_PCREL: return "R_LARCH_SOP_PUSH_PCREL";
case R_LARCH_SOP_PUSH_ABSOLUTE: return "R_LARCH_SOP_PUSH_ABSOLUTE";
case R_LARCH_SOP_PUSH_DUP: return "R_LARCH_SOP_PUSH_DUP";
case R_LARCH_SOP_PUSH_GPREL: return "R_LARCH_SOP_PUSH_GPREL";
case R_LARCH_SOP_PUSH_TLS_TPREL: return "R_LARCH_SOP_PUSH_TLS_TPREL";
case R_LARCH_SOP_PUSH_TLS_GOT: return "R_LARCH_SOP_PUSH_TLS_GOT";
case R_LARCH_SOP_PUSH_TLS_GD: return "R_LARCH_SOP_PUSH_TLS_GD";
case R_LARCH_SOP_PUSH_PLT_PCREL: return "R_LARCH_SOP_PUSH_PLT_PCREL";
case R_LARCH_SOP_ASSERT: return "R_LARCH_SOP_ASSERT";
case R_LARCH_SOP_NOT: return "R_LARCH_SOP_NOT";
case R_LARCH_SOP_SUB: return "R_LARCH_SOP_SUB";
case R_LARCH_SOP_SL: return "R_LARCH_SOP_SL";
case R_LARCH_SOP_SR: return "R_LARCH_SOP_SR";
case R_LARCH_SOP_ADD: return "R_LARCH_SOP_ADD";
case R_LARCH_SOP_AND: return "R_LARCH_SOP_AND";
case R_LARCH_SOP_IF_ELSE: return "R_LARCH_SOP_IF_ELSE";
case R_LARCH_SOP_POP_32_S_10_5: return "R_LARCH_SOP_POP_32_S_10_5";
case R_LARCH_SOP_POP_32_U_10_12: return "R_LARCH_SOP_POP_32_U_10_12";
case R_LARCH_SOP_POP_32_S_10_12: return "R_LARCH_SOP_POP_32_S_10_12";
case R_LARCH_SOP_POP_32_S_10_16: return "R_LARCH_SOP_POP_32_S_10_16";
case R_LARCH_SOP_POP_32_S_10_16_S2: return "R_LARCH_SOP_POP_32_S_10_16_S2";
case R_LARCH_SOP_POP_32_S_5_20: return "R_LARCH_SOP_POP_32_S_5_20";
case R_LARCH_SOP_POP_32_S_0_5_10_16_S2: return "R_LARCH_SOP_POP_32_S_0_5_10_16_S2";
case R_LARCH_SOP_POP_32_S_0_10_10_16_S2: return "R_LARCH_SOP_POP_32_S_0_10_10_16_S2";
case R_LARCH_SOP_POP_32_U: return "R_LARCH_SOP_POP_32_U";
case R_LARCH_ADD8: return "R_LARCH_ADD8";
case R_LARCH_ADD16: return "R_LARCH_ADD16";
case R_LARCH_ADD24: return "R_LARCH_ADD24";
case R_LARCH_ADD32: return "R_LARCH_ADD32";
case R_LARCH_ADD64: return "R_LARCH_ADD64";
case R_LARCH_SUB8: return "R_LARCH_SUB8";
case R_LARCH_SUB16: return "R_LARCH_SUB16";
case R_LARCH_SUB24: return "R_LARCH_SUB24";
case R_LARCH_SUB32: return "R_LARCH_SUB32";
case R_LARCH_SUB64: return "R_LARCH_SUB64";
case R_LARCH_GNU_VTINHERIT: return "R_LARCH_GNU_VTINHERIT";
case R_LARCH_GNU_VTENTRY: return "R_LARCH_GNU_VTENTRY";
case R_LARCH_B16: return "R_LARCH_B16";
case R_LARCH_B21: return "R_LARCH_B21";
case R_LARCH_B26: return "R_LARCH_B26";
case R_LARCH_ABS_HI20: return "R_LARCH_ABS_HI20";
case R_LARCH_ABS_LO12: return "R_LARCH_ABS_LO12";
case R_LARCH_ABS64_LO20: return "R_LARCH_ABS64_LO20";
case R_LARCH_ABS64_HI12: return "R_LARCH_ABS64_HI12";
case R_LARCH_PCALA_HI20: return "R_LARCH_PCALA_HI20";
case R_LARCH_PCALA_LO12: return "R_LARCH_PCALA_LO12";
case R_LARCH_PCALA64_LO20: return "R_LARCH_PCALA64_LO20";
case R_LARCH_PCALA64_HI12: return "R_LARCH_PCALA64_HI12";
case R_LARCH_GOT_PC_HI20: return "R_LARCH_GOT_PC_HI20";
case R_LARCH_GOT_PC_LO12: return "R_LARCH_GOT_PC_LO12";
case R_LARCH_GOT64_PC_LO20: return "R_LARCH_GOT64_PC_LO20";
case R_LARCH_GOT64_PC_HI12: return "R_LARCH_GOT64_PC_HI12";
case R_LARCH_GOT_HI20: return "R_LARCH_GOT_HI20";
case R_LARCH_GOT_LO12: return "R_LARCH_GOT_LO12";
case R_LARCH_GOT64_LO20: return "R_LARCH_GOT64_LO20";
case R_LARCH_GOT64_HI12: return "R_LARCH_GOT64_HI12";
case R_LARCH_TLS_LE_HI20: return "R_LARCH_TLS_LE_HI20";
case R_LARCH_TLS_LE_LO12: return "R_LARCH_TLS_LE_LO12";
case R_LARCH_TLS_LE64_LO20: return "R_LARCH_TLS_LE64_LO20";
case R_LARCH_TLS_LE64_HI12: return "R_LARCH_TLS_LE64_HI12";
case R_LARCH_TLS_IE_PC_HI20: return "R_LARCH_TLS_IE_PC_HI20";
case R_LARCH_TLS_IE_PC_LO12: return "R_LARCH_TLS_IE_PC_LO12";
case R_LARCH_TLS_IE64_PC_LO20: return "R_LARCH_TLS_IE64_PC_LO20";
case R_LARCH_TLS_IE64_PC_HI12: return "R_LARCH_TLS_IE64_PC_HI12";
case R_LARCH_TLS_IE_HI20: return "R_LARCH_TLS_IE_HI20";
case R_LARCH_TLS_IE_LO12: return "R_LARCH_TLS_IE_LO12";
case R_LARCH_TLS_IE64_LO20: return "R_LARCH_TLS_IE64_LO20";
case R_LARCH_TLS_IE64_HI12: return "R_LARCH_TLS_IE64_HI12";
case R_LARCH_TLS_LD_PC_HI20: return "R_LARCH_TLS_LD_PC_HI20";
case R_LARCH_TLS_LD_HI20: return "R_LARCH_TLS_LD_HI20";
case R_LARCH_TLS_GD_PC_HI20: return "R_LARCH_TLS_GD_PC_HI20";
case R_LARCH_TLS_GD_HI20: return "R_LARCH_TLS_GD_HI20";
case R_LARCH_32_PCREL: return "R_LARCH_32_PCREL";
case R_LARCH_RELAX: return "R_LARCH_RELAX";
case R_LARCH_DELETE: return "R_LARCH_DELETE";
case R_LARCH_ALIGN: return "R_LARCH_ALIGN";
case R_LARCH_PCREL20_S2: return "R_LARCH_PCREL20_S2";
case R_LARCH_CFA: return "R_LARCH_CFA";
case R_LARCH_ADD6: return "R_LARCH_ADD6";
case R_LARCH_SUB6: return "R_LARCH_SUB6";
case R_LARCH_ADD_ULEB128: return "R_LARCH_ADD_ULEB128";
case R_LARCH_SUB_ULEB128: return "R_LARCH_SUB_ULEB128";
case R_LARCH_64_PCREL: return "R_LARCH_64_PCREL";
}
return unknown_type(r_type);
}
template <>
std::string rel_to_string<LOONGARCH32>(u32 r_type) {
return rel_to_string<LOONGARCH64>(r_type);
}
} // namespace mold::elf
+386 -15
View File
@@ -1,8 +1,7 @@
#pragma once
#include "../integers.h"
#include "../common/integers.h"
#include <cstdint>
#include <ostream>
#include <string>
#include <type_traits>
@@ -25,6 +24,10 @@ struct SPARC64;
struct M68K;
struct SH4;
struct ALPHA;
struct MIPS64LE;
struct MIPS64BE;
struct LOONGARCH64;
struct LOONGARCH32;
template <typename E> struct ElfSym;
template <typename E> struct ElfShdr;
@@ -54,9 +57,9 @@ enum : u32 {
enum : u32 {
SHN_UNDEF = 0,
SHN_LORESERVE = 0xff00,
SHN_ABS = 0xfff1,
SHN_COMMON = 0xfff2,
SHN_LORESERVE = 0xff00,
SHN_XINDEX = 0xffff,
};
@@ -87,6 +90,8 @@ enum : u32 {
SHT_X86_64_UNWIND = 0x70000001,
SHT_ARM_EXIDX = 0x70000001,
SHT_ARM_ATTRIBUTES = 0x70000003,
SHT_RISCV_ATTRIBUTES = 0x70000003,
SHT_MIPS_ABIFLAGS = 0x7000002a,
};
enum : u32 {
@@ -101,6 +106,7 @@ enum : u32 {
SHF_TLS = 0x400,
SHF_COMPRESSED = 0x800,
SHF_GNU_RETAIN = 0x200000,
SHF_MIPS_GPREL = 0x10000000,
SHF_EXCLUDE = 0x80000000,
};
@@ -182,7 +188,10 @@ enum : u32 {
PT_GNU_EH_FRAME = 0x6474e550,
PT_GNU_STACK = 0x6474e551,
PT_GNU_RELRO = 0x6474e552,
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
PT_ARM_EXIDX = 0x70000001,
PT_RISCV_ATTRIBUTES = 0x70000003,
PT_MIPS_ABIFLAGS = 0x70000003,
};
enum : u32 {
@@ -217,6 +226,7 @@ enum : u32 {
EM_NONE = 0,
EM_386 = 3,
EM_68K = 4,
EM_MIPS = 8,
EM_PPC = 20,
EM_PPC64 = 21,
EM_S390X = 22,
@@ -226,6 +236,7 @@ enum : u32 {
EM_X86_64 = 62,
EM_AARCH64 = 183,
EM_RISCV = 243,
EM_LOONGARCH = 258,
EM_ALPHA = 0x9026,
};
@@ -285,6 +296,13 @@ enum : u32 {
DT_VERNEEDNUM = 0x6fffffff,
DT_PPC_GOT = 0x70000000,
DT_PPC64_GLINK = 0x70000000,
DT_MIPS_RLD_VERSION = 0x70000001,
DT_MIPS_FLAGS = 0x70000005,
DT_MIPS_BASE_ADDRESS = 0x70000006,
DT_MIPS_LOCAL_GOTNO = 0x7000000a,
DT_MIPS_SYMTABNO = 0x70000011,
DT_MIPS_GOTSYM = 0x70000013,
DT_MIPS_OPTIONS = 0x70000029,
DT_AUXILIARY = 0x7ffffffd,
DT_FILTER = 0x7fffffff,
};
@@ -370,6 +388,24 @@ enum : u32 {
STO_ALPHA_STD_GPLOAD = 0x22,
};
enum : u32 {
ELF_TAG_FILE = 1,
ELF_TAG_SECTION = 2,
ELF_TAG_SYMBOL = 3,
ELF_TAG_RISCV_STACK_ALIGN = 4,
ELF_TAG_RISCV_ARCH = 5,
ELF_TAG_RISCV_UNALIGNED_ACCESS = 6,
};
enum : u32 {
EF_LOONGARCH_ABI_SOFT_FLOAT = 0x1,
EF_LOONGARCH_ABI_SINGLE_FLOAT = 0x2,
EF_LOONGARCH_ABI_DOUBLE_FLOAT = 0x3,
EF_LOONGARCH_ABI_MODIFIER_MASK = 0x7,
EF_LOONGARCH_OBJABI_V1 = 0x40,
EF_LOONGARCH_OBJABI_MASK = 0xC0,
};
//
// Relocation types
//
@@ -757,6 +793,9 @@ enum : u32 {
R_RISCV_SET32 = 56,
R_RISCV_32_PCREL = 57,
R_RISCV_IRELATIVE = 58,
R_RISCV_PLT32 = 59,
R_RISCV_SET_ULEB128 = 60,
R_RISCV_SUB_ULEB128 = 61,
};
enum : u32 {
@@ -933,9 +972,13 @@ enum : u32 {
R_PPC64_REL24_NOTOC = 116,
R_PPC64_PLTSEQ = 119,
R_PPC64_PLTCALL = 120,
R_PPC64_PLTSEQ_NOTOC = 121,
R_PPC64_PLTCALL_NOTOC = 122,
R_PPC64_PCREL_OPT = 123,
R_PPC64_PCREL34 = 132,
R_PPC64_GOT_PCREL34 = 133,
R_PPC64_PLT_PCREL34 = 134,
R_PPC64_PLT_PCREL34_NOTOC = 135,
R_PPC64_TPREL34 = 146,
R_PPC64_DTPREL34 = 147,
R_PPC64_GOT_TLSGD_PCREL34 = 148,
@@ -1217,6 +1260,186 @@ enum : u32 {
R_ALPHA_TPREL16 = 41,
};
enum : u32 {
R_MIPS_NONE = 0,
R_MIPS_16 = 1,
R_MIPS_32 = 2,
R_MIPS_REL32 = 3,
R_MIPS_26 = 4,
R_MIPS_HI16 = 5,
R_MIPS_LO16 = 6,
R_MIPS_GPREL16 = 7,
R_MIPS_LITERAL = 8,
R_MIPS_GOT16 = 9,
R_MIPS_PC16 = 10,
R_MIPS_CALL16 = 11,
R_MIPS_GPREL32 = 12,
R_MIPS_UNUSED1 = 13,
R_MIPS_UNUSED2 = 14,
R_MIPS_UNUSED3 = 15,
R_MIPS_SHIFT5 = 16,
R_MIPS_SHIFT6 = 17,
R_MIPS_64 = 18,
R_MIPS_GOT_DISP = 19,
R_MIPS_GOT_PAGE = 20,
R_MIPS_GOT_OFST = 21,
R_MIPS_GOT_HI16 = 22,
R_MIPS_GOT_LO16 = 23,
R_MIPS_SUB = 24,
R_MIPS_INSERT_A = 25,
R_MIPS_INSERT_B = 26,
R_MIPS_DELETE = 27,
R_MIPS_HIGHER = 28,
R_MIPS_HIGHEST = 29,
R_MIPS_CALL_HI16 = 30,
R_MIPS_CALL_LO16 = 31,
R_MIPS_SCN_DISP = 32,
R_MIPS_REL16 = 33,
R_MIPS_ADD_IMMEDIATE = 34,
R_MIPS_PJUMP = 35,
R_MIPS_RELGOT = 36,
R_MIPS_JALR = 37,
R_MIPS_TLS_DTPMOD32 = 38,
R_MIPS_TLS_DTPREL32 = 39,
R_MIPS_TLS_DTPMOD64 = 40,
R_MIPS_TLS_DTPREL64 = 41,
R_MIPS_TLS_GD = 42,
R_MIPS_TLS_LDM = 43,
R_MIPS_TLS_DTPREL_HI16 = 44,
R_MIPS_TLS_DTPREL_LO16 = 45,
R_MIPS_TLS_GOTTPREL = 46,
R_MIPS_TLS_TPREL32 = 47,
R_MIPS_TLS_TPREL64 = 48,
R_MIPS_TLS_TPREL_HI16 = 49,
R_MIPS_TLS_TPREL_LO16 = 50,
R_MIPS_GLOB_DAT = 51,
R_MIPS_PC21_S2 = 60,
R_MIPS_PC26_S2 = 61,
R_MIPS_PC18_S3 = 62,
R_MIPS_PC19_S2 = 63,
R_MIPS_PCHI16 = 64,
R_MIPS_PCLO16 = 65,
R_MIPS16_26 = 100,
R_MIPS16_GPREL = 101,
R_MIPS16_GOT16 = 102,
R_MIPS16_CALL16 = 103,
R_MIPS16_HI16 = 104,
R_MIPS16_LO16 = 105,
R_MIPS16_TLS_GD = 106,
R_MIPS16_TLS_LDM = 107,
R_MIPS16_TLS_DTPREL_HI16 = 108,
R_MIPS16_TLS_DTPREL_LO16 = 109,
R_MIPS16_TLS_GOTTPREL = 110,
R_MIPS16_TLS_TPREL_HI16 = 111,
R_MIPS16_TLS_TPREL_LO16 = 112,
R_MIPS_COPY = 126,
R_MIPS_JUMP_SLOT = 127,
R_MIPS_NUM = 218,
R_MIPS_PC32 = 248,
R_MIPS_EH = 249,
};
enum : u32 {
R_LARCH_NONE = 0,
R_LARCH_32 = 1,
R_LARCH_64 = 2,
R_LARCH_RELATIVE = 3,
R_LARCH_COPY = 4,
R_LARCH_JUMP_SLOT = 5,
R_LARCH_TLS_DTPMOD32 = 6,
R_LARCH_TLS_DTPMOD64 = 7,
R_LARCH_TLS_DTPREL32 = 8,
R_LARCH_TLS_DTPREL64 = 9,
R_LARCH_TLS_TPREL32 = 10,
R_LARCH_TLS_TPREL64 = 11,
R_LARCH_IRELATIVE = 12,
R_LARCH_MARK_LA = 20,
R_LARCH_MARK_PCREL = 21,
R_LARCH_SOP_PUSH_PCREL = 22,
R_LARCH_SOP_PUSH_ABSOLUTE = 23,
R_LARCH_SOP_PUSH_DUP = 24,
R_LARCH_SOP_PUSH_GPREL = 25,
R_LARCH_SOP_PUSH_TLS_TPREL = 26,
R_LARCH_SOP_PUSH_TLS_GOT = 27,
R_LARCH_SOP_PUSH_TLS_GD = 28,
R_LARCH_SOP_PUSH_PLT_PCREL = 29,
R_LARCH_SOP_ASSERT = 30,
R_LARCH_SOP_NOT = 31,
R_LARCH_SOP_SUB = 32,
R_LARCH_SOP_SL = 33,
R_LARCH_SOP_SR = 34,
R_LARCH_SOP_ADD = 35,
R_LARCH_SOP_AND = 36,
R_LARCH_SOP_IF_ELSE = 37,
R_LARCH_SOP_POP_32_S_10_5 = 38,
R_LARCH_SOP_POP_32_U_10_12 = 39,
R_LARCH_SOP_POP_32_S_10_12 = 40,
R_LARCH_SOP_POP_32_S_10_16 = 41,
R_LARCH_SOP_POP_32_S_10_16_S2 = 42,
R_LARCH_SOP_POP_32_S_5_20 = 43,
R_LARCH_SOP_POP_32_S_0_5_10_16_S2 = 44,
R_LARCH_SOP_POP_32_S_0_10_10_16_S2 = 45,
R_LARCH_SOP_POP_32_U = 46,
R_LARCH_ADD8 = 47,
R_LARCH_ADD16 = 48,
R_LARCH_ADD24 = 49,
R_LARCH_ADD32 = 50,
R_LARCH_ADD64 = 51,
R_LARCH_SUB8 = 52,
R_LARCH_SUB16 = 53,
R_LARCH_SUB24 = 54,
R_LARCH_SUB32 = 55,
R_LARCH_SUB64 = 56,
R_LARCH_GNU_VTINHERIT = 57,
R_LARCH_GNU_VTENTRY = 58,
R_LARCH_B16 = 64,
R_LARCH_B21 = 65,
R_LARCH_B26 = 66,
R_LARCH_ABS_HI20 = 67,
R_LARCH_ABS_LO12 = 68,
R_LARCH_ABS64_LO20 = 69,
R_LARCH_ABS64_HI12 = 70,
R_LARCH_PCALA_HI20 = 71,
R_LARCH_PCALA_LO12 = 72,
R_LARCH_PCALA64_LO20 = 73,
R_LARCH_PCALA64_HI12 = 74,
R_LARCH_GOT_PC_HI20 = 75,
R_LARCH_GOT_PC_LO12 = 76,
R_LARCH_GOT64_PC_LO20 = 77,
R_LARCH_GOT64_PC_HI12 = 78,
R_LARCH_GOT_HI20 = 79,
R_LARCH_GOT_LO12 = 80,
R_LARCH_GOT64_LO20 = 81,
R_LARCH_GOT64_HI12 = 82,
R_LARCH_TLS_LE_HI20 = 83,
R_LARCH_TLS_LE_LO12 = 84,
R_LARCH_TLS_LE64_LO20 = 85,
R_LARCH_TLS_LE64_HI12 = 86,
R_LARCH_TLS_IE_PC_HI20 = 87,
R_LARCH_TLS_IE_PC_LO12 = 88,
R_LARCH_TLS_IE64_PC_LO20 = 89,
R_LARCH_TLS_IE64_PC_HI12 = 90,
R_LARCH_TLS_IE_HI20 = 91,
R_LARCH_TLS_IE_LO12 = 92,
R_LARCH_TLS_IE64_LO20 = 93,
R_LARCH_TLS_IE64_HI12 = 94,
R_LARCH_TLS_LD_PC_HI20 = 95,
R_LARCH_TLS_LD_HI20 = 96,
R_LARCH_TLS_GD_PC_HI20 = 97,
R_LARCH_TLS_GD_HI20 = 98,
R_LARCH_32_PCREL = 99,
R_LARCH_RELAX = 100,
R_LARCH_DELETE = 101,
R_LARCH_ALIGN = 102,
R_LARCH_PCREL20_S2 = 103,
R_LARCH_CFA = 104,
R_LARCH_ADD6 = 105,
R_LARCH_SUB6 = 106,
R_LARCH_ADD_ULEB128 = 107,
R_LARCH_SUB_ULEB128 = 108,
R_LARCH_64_PCREL = 109,
};
//
// DWARF data types
//
@@ -1453,14 +1676,8 @@ struct ElfPhdr<E> {
// with a newly computed value.
//
// We don't want to have too many `if (REL)`s and `if (RELA)`s in our
// codebase, so we write dynamic relocations in the following manner:
//
// - We always create a dynamic relocation with an addend. If it's REL,
// the addend will be discarded.
//
// - We also always write an addend to the relocated place by default
// even though it's redundant for RELA. If RELA, the written value
// will be ovewritten by the dynamic linker at load-time.
// codebase, so ElfRel always takes r_addend as a constructor argument.
// If it's REL, the argument will simply be ignored.
template <typename E> requires E::is_le && E::is_rela
struct ElfRel<E> {
ElfRel() = default;
@@ -1582,6 +1799,9 @@ struct ElfSym<PPC64V2> {
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_weak() const { return is_undef() && is_weak(); }
bool preserves_r2() const { return ppc_local_entry != 1; }
bool uses_toc() const { return ppc_local_entry > 1; }
ul32 st_name;
#ifdef __LITTLE_ENDIAN__
@@ -1644,10 +1864,48 @@ struct ElfRel<SPARC64> {
ib64 r_addend;
};
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_addend(addend) {}
// In the little-endian MIPS64, r_sym and r_type are reversed, with
// r_type being stored in big-endian order. It's extremely odd though.
ul64 r_offset;
ul32 r_sym;
ub32 r_type;
il64 r_addend;
};
// .MIPS.options section
template <typename E>
struct MipsOptions {
u8 kind;
u8 size;
U16<E> section;
U32<E> info;
};
template <typename E>
struct MipsRegInfo {
U32<E> ri_gprmask;
U32<E> ri_pad;
U32<E> ri_cprmask[4];
U64<E> ri_gp_value;
};
enum : u32 {
ODK_REGINFO = 1,
};
//
// Machine descriptions
//
template <typename E>
static constexpr bool supports_ifunc = requires { E::R_IRELATIVE; };
template <typename E>
static constexpr bool supports_tlsdesc = requires { E::R_TLSDESC; };
@@ -1670,6 +1928,10 @@ 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_loongarch64 = std::is_same_v<E, LOONGARCH64>;
template <typename E> static constexpr bool is_loongarch32 = std::is_same_v<E, LOONGARCH32>;
template <typename E> static constexpr bool is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> static constexpr bool is_arm = is_arm64<E> || is_arm32<E>;
@@ -1679,6 +1941,9 @@ 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_loongarch = is_loongarch64<E> || is_loongarch32<E>;
struct X86_64 {
static constexpr std::string_view target_name = "x86_64";
@@ -1695,6 +1960,7 @@ struct X86_64 {
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
static constexpr u32 R_ABS = R_X86_64_64;
static constexpr u32 R_DYNAMIC = R_X86_64_64;
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
@@ -1718,6 +1984,7 @@ struct I386 {
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
static constexpr u32 R_ABS = R_386_32;
static constexpr u32 R_DYNAMIC = R_386_32;
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
@@ -1743,6 +2010,7 @@ struct ARM64 {
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_AARCH64_JUMP_SLOT;
static constexpr u32 R_ABS = R_AARCH64_ABS64;
static constexpr u32 R_DYNAMIC = R_AARCH64_ABS64;
static constexpr u32 R_RELATIVE = R_AARCH64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_AARCH64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_AARCH64_TLS_DTPREL64;
@@ -1768,6 +2036,7 @@ struct ARM32 {
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ARM_JUMP_SLOT;
static constexpr u32 R_ABS = R_ARM_ABS32;
static constexpr u32 R_DYNAMIC = R_ARM_ABS32;
static constexpr u32 R_RELATIVE = R_ARM_RELATIVE;
static constexpr u32 R_IRELATIVE = R_ARM_IRELATIVE;
static constexpr u32 R_DTPOFF = R_ARM_TLS_DTPOFF32;
@@ -1791,6 +2060,7 @@ struct RV64LE {
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_64;
static constexpr u32 R_DYNAMIC = R_RISCV_64;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
@@ -1813,6 +2083,7 @@ struct RV64BE {
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_64;
static constexpr u32 R_DYNAMIC = R_RISCV_64;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
@@ -1835,6 +2106,7 @@ struct RV32LE {
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_32;
static constexpr u32 R_DYNAMIC = R_RISCV_32;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
@@ -1857,6 +2129,7 @@ struct RV32BE {
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_32;
static constexpr u32 R_DYNAMIC = R_RISCV_32;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
@@ -1881,6 +2154,7 @@ struct PPC32 {
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_PPC_JMP_SLOT;
static constexpr u32 R_ABS = R_PPC_ADDR32;
static constexpr u32 R_DYNAMIC = R_PPC_ADDR32;
static constexpr u32 R_RELATIVE = R_PPC_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC_DTPREL32;
@@ -1896,7 +2170,6 @@ struct PPC64V1 {
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_PPC64;
static constexpr u32 plt_hdr_size = 52;
static constexpr u32 plt_size = 8;
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 28;
@@ -1905,6 +2178,7 @@ struct PPC64V1 {
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
static constexpr u32 R_ABS = R_PPC64_ADDR64;
static constexpr u32 R_DYNAMIC = R_PPC64_ADDR64;
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
@@ -1929,6 +2203,7 @@ struct PPC64V2 {
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
static constexpr u32 R_ABS = R_PPC64_ADDR64;
static constexpr u32 R_DYNAMIC = R_PPC64_ADDR64;
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
@@ -1951,6 +2226,7 @@ struct S390X {
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_390_JMP_SLOT;
static constexpr u32 R_ABS = R_390_64;
static constexpr u32 R_DYNAMIC = R_390_64;
static constexpr u32 R_RELATIVE = R_390_RELATIVE;
static constexpr u32 R_IRELATIVE = R_390_IRELATIVE;
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
@@ -1973,6 +2249,7 @@ struct SPARC64 {
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_SPARC_JMP_SLOT;
static constexpr u32 R_ABS = R_SPARC_64;
static constexpr u32 R_DYNAMIC = R_SPARC_64;
static constexpr u32 R_RELATIVE = R_SPARC_RELATIVE;
static constexpr u32 R_IRELATIVE = R_SPARC_IRELATIVE;
static constexpr u32 R_DTPOFF = R_SPARC_TLS_DTPOFF64;
@@ -1995,8 +2272,8 @@ struct M68K {
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_68K_JMP_SLOT;
static constexpr u32 R_ABS = R_68K_32;
static constexpr u32 R_DYNAMIC = R_68K_32;
static constexpr u32 R_RELATIVE = R_68K_RELATIVE;
static constexpr u32 R_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;
@@ -2017,8 +2294,8 @@ struct SH4 {
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_SH_JMP_SLOT;
static constexpr u32 R_ABS = R_SH_DIR32;
static constexpr u32 R_DYNAMIC = R_SH_DIR32;
static constexpr u32 R_RELATIVE = R_SH_RELATIVE;
static constexpr u32 R_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;
@@ -2039,11 +2316,105 @@ struct ALPHA {
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
static constexpr u32 R_DYNAMIC = R_ALPHA_REFQUAD;
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
static constexpr u32 R_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 = 0;
static constexpr u32 pltgot_size = 0;
static constexpr u32 R_COPY = R_MIPS_COPY;
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT | (R_MIPS_64 << 8);
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
static constexpr u32 R_ABS = R_MIPS_64;
static constexpr u32 R_DYNAMIC = R_MIPS_REL32 | (R_MIPS_64 << 8);
static constexpr u32 R_RELATIVE = R_MIPS_REL32 | (R_MIPS_64 << 8);
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
};
struct MIPS64BE {
static constexpr std::string_view target_name = "mips64";
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_MIPS;
static constexpr u32 plt_hdr_size = 0;
static constexpr u32 plt_size = 0;
static constexpr u32 pltgot_size = 0;
static constexpr u32 R_COPY = R_MIPS_COPY;
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT | (R_MIPS_64 << 8);
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
static constexpr u32 R_ABS = R_MIPS_64;
static constexpr u32 R_DYNAMIC = R_MIPS_REL32 | (R_MIPS_64 << 8);
static constexpr u32 R_RELATIVE = R_MIPS_REL32 | (R_MIPS_64 << 8);
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 LOONGARCH64 {
static constexpr std::string_view target_name = "loongarch64";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 16384;
static constexpr u32 e_machine = EM_LOONGARCH;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 16;
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
static constexpr u32 R_ABS = R_LARCH_64;
static constexpr u32 R_DYNAMIC = R_LARCH_64;
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
};
struct LOONGARCH32 {
static constexpr std::string_view target_name = "loongarch32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 16384;
static constexpr u32 e_machine = EM_LOONGARCH;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 16;
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
static constexpr u32 R_ABS = R_LARCH_32;
static constexpr u32 R_DYNAMIC = R_LARCH_32;
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
};
} // namespace mold::elf
+11 -29
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 && fast_mark(isec->is_visited);
return isec && isec->is_alive && !isec->is_visited.test_and_set();
}
template <typename E>
@@ -53,19 +53,18 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
// Symbol can refer either a section fragment or an input section.
// Mark a fragment as alive.
if (SectionFragment<E> *frag = sym.get_frag()) {
frag->is_alive.store(true, std::memory_order_relaxed);
frag->is_alive = true;
continue;
}
if (!mark_section(sym.get_input_section()))
continue;
// Mark a section alive. For better performacne, we don't call
// `feeder.add` too often.
if (depth < 3)
visit(ctx, sym.get_input_section(), feeder, depth + 1);
else
feeder.add(sym.get_input_section());
if (mark_section(sym.get_input_section())) {
if (depth < 3)
visit(ctx, sym.get_input_section(), feeder, depth + 1);
else
feeder.add(sym.get_input_section());
}
}
}
@@ -82,7 +81,7 @@ static void collect_root_set(Context<E> &ctx,
auto enqueue_symbol = [&](Symbol<E> *sym) {
if (sym) {
if (SectionFragment<E> *frag = sym->get_frag())
frag->is_alive.store(true, std::memory_order_relaxed);
frag->is_alive = true;
else
enqueue_section(sym->get_input_section());
}
@@ -94,10 +93,10 @@ static void collect_root_set(Context<E> &ctx,
if (!isec || !isec->is_alive)
continue;
// -gc-sections discards only SHF_ALLOC sections. If you want to
// --gc-sections discards only SHF_ALLOC sections. If you want to
// reduce the amount of non-memory-mapped segments, you should
// use `strip` command, compile without debug info or use
// -strip-all linker option.
// --strip-all linker option.
u32 flags = isec->shdr().sh_flags;
if (!(flags & SHF_ALLOC))
isec->is_visited = true;
@@ -163,27 +162,10 @@ static void sweep(Context<E> &ctx) {
});
}
// Non-alloc section fragments are not subject of garbage collection.
// This function marks such fragments.
template <typename E>
static void mark_nonalloc_fragments(Context<E> &ctx) {
Timer t(ctx, "mark_nonalloc_fragments");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
if (m)
for (SectionFragment<E> *frag : m->fragments)
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
frag->is_alive.store(true, std::memory_order_relaxed);
});
}
template <typename E>
void gc_sections(Context<E> &ctx) {
Timer t(ctx, "gc");
mark_nonalloc_fragments(ctx);
tbb::concurrent_vector<InputSection<E> *> rootset;
collect_root_set(ctx, rootset);
mark(ctx, rootset);
+1 -11
View File
@@ -65,7 +65,7 @@
// conditions.
#include "mold.h"
#include "../sha.h"
#include "../common/sha.h"
#include <array>
#include <cstdio>
@@ -90,16 +90,6 @@ template<> struct hash<Digest> {
namespace mold::elf {
class BitVector {
public:
BitVector(u32 size) : vec((size + 7) / 8) {}
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
private:
std::vector<u8> vec;
};
template <typename E>
static void uniquify_cies(Context<E> &ctx) {
Timer t(ctx, "uniquify_cies");
+206 -226
View File
@@ -42,6 +42,12 @@ InputFile<E>::InputFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
shstrtab = this->get_string(ctx, shstrtab_idx);
}
template <typename E>
std::span<ElfPhdr<E>> InputFile<E>::get_phdrs() {
ElfEhdr<E> &ehdr = get_ehdr();
return {(ElfPhdr<E> *)(mf->data + ehdr.e_phoff), ehdr.e_phnum};
}
template <typename E>
ElfShdr<E> *InputFile<E>::find_section(i64 type) {
for (ElfShdr<E> &sec : elf_sections)
@@ -50,21 +56,20 @@ ElfShdr<E> *InputFile<E>::find_section(i64 type) {
return nullptr;
}
// This function is actually racy, in that "tearing" might be observed
// during the read or write of `sym->file`. However, on most platforms
// read/write of pointers never tears, and even if it did, it would be
// harmless, as `sym->file` will not become equal to `sym`, or even if
// it did by chance, we just wipe a symbol twice.
//
// Doing this in a standard-compliant way is too cumbersome, hence
// here we just pretend a compiler will never optimize this into a
// form that actually exploits the UB.
template <typename E>
__attribute__((no_sanitize("thread")))
void InputFile<E>::clear_symbols() {
for (Symbol<E> *sym : get_global_syms())
if (sym->file == this)
sym->clear();
for (Symbol<E> *sym : get_global_syms()) {
if (__atomic_load_n(&sym->file, __ATOMIC_ACQUIRE) == this) {
sym->origin = 0;
sym->value = -1;
sym->sym_idx = -1;
sym->ver_idx = 0;
sym->is_weak = false;
sym->is_imported = false;
sym->is_exported = false;
__atomic_store_n(&sym->file, nullptr, __ATOMIC_RELEASE);
}
}
}
// Find the source filename. It should be listed in symtab as STT_FILE.
@@ -135,6 +140,82 @@ ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
}
}
static inline std::string_view read_string(std::string_view &str) {
i64 pos = str.find_first_of('\0');
std::string_view val = str.substr(0, pos);
str = str.substr(pos + 1);
return val;
}
// <format-version>
// [ <section-length> "vendor-name" <file-tag> <size> <attribute>*]+ ]*
template <typename E>
static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
std::string_view data) {
const char *begin = data.data();
if (data.empty())
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
if (u8 format_version = data[0]; format_version != 'A')
return;
data = data.substr(1);
while (!data.empty()) {
i64 sz = *(U32<E> *)data.data();
if (data.size() < sz)
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
std::string_view p(data.data() + 4, sz - 4);
data = data.substr(sz);
if (!p.starts_with("riscv\0"sv))
continue;
p = p.substr(6);
if (!p.starts_with(ELF_TAG_FILE))
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
p = p.substr(5); // skip the tag and the sub-sub-section size
while (!p.empty()) {
i64 tag = read_uleb(&p);
switch (tag) {
case ELF_TAG_RISCV_STACK_ALIGN:
file.extra.stack_align = read_uleb(&p);
break;
case ELF_TAG_RISCV_ARCH:
file.extra.arch = read_string(p);
break;
case ELF_TAG_RISCV_UNALIGNED_ACCESS:
file.extra.unaligned_access = read_uleb(&p);
break;
default:
break;
}
}
}
}
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
@@ -145,6 +226,17 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
shdr.sh_type != SHT_LLVM_ADDRSIG && !ctx.arg.relocatable)
continue;
if constexpr (is_arm<E>)
if (shdr.sh_type == SHT_ARM_ATTRIBUTES)
continue;
if constexpr (is_riscv<E>) {
if (shdr.sh_type == SHT_RISCV_ATTRIBUTES) {
read_riscv_attributes(ctx, *this, this->get_string(ctx, shdr));
continue;
}
}
switch (shdr.sh_type) {
case SHT_GROUP: {
// Get the signature of this section group.
@@ -155,7 +247,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
std::string_view signature;
if (esym.st_type == STT_SECTION) {
signature = this->shstrtab.data() +
this->elf_sections[esym.st_shndx].sh_name;
this->elf_sections[get_shndx(esym)].sh_name;
} else {
signature = this->symbol_strtab.data() + esym.st_name;
}
@@ -181,15 +273,20 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
comdat_groups.push_back({group, (u32)i, entries.subspan(1)});
break;
}
case SHT_SYMTAB_SHNDX:
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, shdr);
case SHT_REL:
if constexpr (E::is_rela)
Fatal(ctx) << *this << ": REL-type relocation table is not supported"
<< " for this target";
break;
case SHT_RELA:
if constexpr (!E::is_rela)
Fatal(ctx) << *this <<": RELA-type relocation table is not supported"
<< " for this target";
break;
case SHT_SYMTAB:
case SHT_SYMTAB_SHNDX:
case SHT_STRTAB:
case SHT_REL:
case SHT_RELA:
case SHT_NULL:
case SHT_ARM_ATTRIBUTES:
break;
default: {
std::string_view name = this->shstrtab.data() + shdr.sh_name;
@@ -198,8 +295,11 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// area in GNU linkers. We ignore that section because silently
// making the stack area executable is too dangerous. Tell our
// users about the difference if that matters.
//
// MIPS object files don't contain .note.GNU-stack for some reason,
// so ignore this error on MIPS.
if (name == ".note.GNU-stack" && !ctx.arg.relocatable) {
if (shdr.sh_flags & SHF_EXECINSTR) {
if ((shdr.sh_flags & SHF_EXECINSTR) && !is_mips<E>) {
if (!ctx.arg.z_execstack && !ctx.arg.z_execstack_if_needed)
Warn(ctx) << *this << ": this file may cause a segmentation"
" fault because it requires an executable stack. See"
@@ -235,12 +335,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
is_debug_section(shdr, name))
continue;
// Save .llvm_addrsig for --icf=safe.
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
llvm_addrsig = std::make_unique<InputSection<E>>(ctx, *this, name, i);
continue;
}
// If an output file doesn't have a section header (i.e.
// --oformat=binary is given), we discard all non-memory-allocated
// sections. This is because without a section header, we can't find
@@ -250,9 +344,25 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
// Save .llvm_addrsig for --icf=safe.
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
llvm_addrsig = std::move(this->sections[i]);
continue;
}
if constexpr (is_ppc32<E>)
if (name == ".got2")
ppc32_got2 = this->sections[i].get();
extra.got2 = this->sections[i].get();
if constexpr (is_mips<E>) {
if (name == ".MIPS.abiflags") {
extra.abi_flags = std::move(this->sections[i]);
continue;
}
if (name == ".MIPS.options")
extra.gp0 = read_mips_gp0(ctx, *this->sections[i]);
}
// Save debug sections for --gdb-index.
if (ctx.arg.gdb_index) {
@@ -319,7 +429,6 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
std::unique_ptr<InputSection<E>> &isec = sections[i];
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
read_ehframe(ctx, *isec);
isec->is_alive = false;
}
}
}
@@ -490,7 +599,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
this->symbols.resize(this->elf_syms.size());
i64 num_globals = this->elf_syms.size() - this->first_global;
symvers.resize(num_globals);
has_symver.resize(num_globals);
for (i64 i = 0; i < this->first_global; i++)
this->symbols[i] = &this->local_syms[i];
@@ -505,14 +614,13 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
// Parse symbol version after atsign
if (i64 pos = name.find('@'); pos != name.npos) {
std::string_view ver = name.substr(pos + 1);
std::string_view ver = name.substr(pos);
name = name.substr(0, pos);
if (!ver.empty() && ver != "@") {
if (ver.starts_with('@'))
if (ver != "@" && ver != "@@") {
if (ver.starts_with("@@"))
key = name;
if (!esym.is_undef())
symvers[i - this->first_global] = ver.data();
has_symver.set(i - this->first_global);
}
}
@@ -527,7 +635,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
// We expect them to be sorted, so sort them if necessary.
template <typename E>
void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
if constexpr (is_riscv<E>) {
if constexpr (is_riscv<E> || is_loongarch<E>) {
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
return a.r_offset < b.r_offset;
};
@@ -575,7 +683,7 @@ static size_t find_null(std::string_view data, u64 entsize) {
template <typename E>
static std::unique_ptr<MergeableSection<E>>
split_section(Context<E> &ctx, InputSection<E> &sec) {
if (!sec.is_alive || sec.sh_size == 0 || sec.relsec_idx != -1)
if (!sec.is_alive || sec.relsec_idx != -1)
return nullptr;
const ElfShdr<E> &shdr = sec.shdr();
@@ -587,6 +695,9 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
shdr.sh_flags);
rec->p2align = sec.p2align;
if (sec.sh_size == 0)
return rec;
// If thes section contents are compressed, uncompress them.
sec.uncompress(ctx);
@@ -656,13 +767,14 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
// atomic unit of mergeable section "section pieces".
//
// This feature is typically used for string literals. String literals
// are usually put into a mergeable section by a compiler. If the same
// string literal happen to occur in two different translation units,
// a linker merges them into a single instance of a string, so that
// a linker's output doesn't contain duplicate string literals.
// are usually put into a mergeable section by the compiler. If the same
// string literal happens to occur in two different translation units,
// the linker merges them into a single instance of a string, so that
// the linker's output doesn't contain duplicate string literals.
//
// Handling symbols in mergeable sections is a bit tricky. Assume that
// we have a mergeable section with the following contents and symbols:
// Handling symbols in the mergeable sections is a bit tricky. Assume
// that we have a mergeable section with the following contents and
// symbols:
//
// Hello world\0foo bar\0
// ^ ^
@@ -671,23 +783,23 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
//
// '\0' represents a NUL byte. This mergeable section contains two
// section pieces, "Hello world" and "foo bar". The first string is
// referred by two symbols, .rodata and .L.str0, and the second by
// referred to by two symbols, .rodata and .L.str0, and the second by
// .L.str1. .rodata is a section symbol and therefore a local symbol
// and refers the begining of the section.
// and refers to the beginning of the section.
//
// In this example, there are actually two different ways to point to
// string "foo bar", because .rodata+12 and .L.str1+0 refer the same
// string "foo bar", because .rodata+12 and .L.str1+0 refer to the same
// place in the section. This kind of "out-of-bound" reference occurs
// only when a symbol is a section symbol. In other words, compiler
// may use an offset from the beginning of a section to refer any
// only when a symbol is a section symbol. In other words, the compiler
// may use an offset from the beginning of a section to refer to any
// section piece in a section, but it doesn't do for any other types
// of symbols.
//
// In mold, we attach section pieces symbols. If a relocation refers a
// section symbol whose section is a mergeable section, we create a
// new dummy symbol with a section piece and redirect the relocation
// to the symbol. If a non-section symbol refers a section piece, the
// section piece is attached to the symbol.
// In mold, we attach symbols to section pieces. If a relocation refers
// to a section symbol, and that symbol's section is a mergeable one,
// we create a new dummy symbol for a section piece and redirect the
// relocation to this new symbol. If a non-section symbol refers to a
// section piece, the section piece is attached to the symbol.
template <typename E>
void ObjectFile<E>::initialize_mergeable_sections(Context<E> &ctx) {
mergeable_sections.resize(sections.size());
@@ -708,7 +820,7 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
if (m) {
m->fragments.reserve(m->strings.size());
for (i64 i = 0; i < m->strings.size(); i++)
m->fragments.push_back(m->parent->insert(m->strings[i], m->hashes[i],
m->fragments.push_back(m->parent->insert(ctx, m->strings[i], m->hashes[i],
m->p2align));
// Shrink vectors that we will never use again to reclaim memory.
@@ -726,7 +838,7 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
continue;
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[get_shndx(esym)];
if (!m)
if (!m || m->fragments.empty())
continue;
SectionFragment<E> *frag;
@@ -803,7 +915,7 @@ void ObjectFile<E>::mark_addrsig(Context<E> &ctx) {
u8 *end = cur + llvm_addrsig->contents.size();
while (cur != end) {
Symbol<E> &sym = *this->symbols[read_uleb(cur)];
Symbol<E> &sym = *this->symbols[read_uleb(&cur)];
if (sym.file == this)
if (InputSection<E> *isec = sym.get_input_section())
isec->address_significant = true;
@@ -833,6 +945,9 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
this->first_global = symtab_sec->sh_info;
this->elf_syms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
if (ElfShdr<E> *shdr = this->find_section(SHT_SYMTAB_SHNDX))
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, *shdr);
}
initialize_sections(ctx);
@@ -854,22 +969,22 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
//
// Ties are broken by file priority.
template <typename E>
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_lazy) {
if (esym.is_common()) {
assert(!file->is_dso);
if (is_lazy)
return (6 << 24) + file->priority;
return (5 << 24) + file->priority;
}
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_in_archive) {
auto get_sym_rank = [&] {
if (esym.is_common()) {
assert(!file->is_dso);
return is_in_archive ? 6 : 5;
}
if (file->is_dso || is_in_archive)
return (esym.st_bind == STB_WEAK) ? 4 : 3;
if (file->is_dso || is_lazy) {
if (esym.st_bind == STB_WEAK)
return (4 << 24) + file->priority;
return (3 << 24) + file->priority;
}
if (esym.st_bind == STB_WEAK)
return (2 << 24) + file->priority;
return (1 << 24) + file->priority;
return 2;
return 1;
};
return (get_sym_rank() << 24) + file->priority;
}
template <typename E>
@@ -943,8 +1058,6 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
sym.sym_idx = i;
sym.ver_idx = ctx.default_version;
sym.is_weak = esym.is_weak();
sym.is_imported = false;
sym.is_exported = false;
}
}
}
@@ -974,7 +1087,7 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
continue;
bool keep = esym.is_undef() || (esym.is_common() && !sym.esym().is_common());
if (keep && fast_mark(sym.file->is_alive)) {
if (keep && !sym.file->is_alive.test_and_set()) {
feeder(sym.file);
if (sym.is_traced)
@@ -984,135 +1097,6 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
}
}
// Comdat groups are used to de-duplicate functions and data that may
// be included into multiple object files. C++ compiler uses comdat
// groups to de-duplicate instantiated templates.
//
// For example, if a compiler decides to instantiate `std::vector<int>`,
// it generates code and data for `std::vector<int>` and put them into a
// comdat group whose name is the mangled name of `std::vector<int>`.
// The instantiation may happen multiple times for different translation
// units. Then linker de-duplicates them so that the resulting executable
// contains only a single copy of `std::vector<int>`.
template <typename E>
void ObjectFile<E>::resolve_comdat_groups() {
for (ComdatGroupRef<E> &ref : comdat_groups)
update_minimum(ref.group->owner, this->priority);
}
template <typename E>
void ObjectFile<E>::eliminate_duplicate_comdat_groups() {
for (ComdatGroupRef<E> &ref : comdat_groups)
if (ref.group->owner != this->priority)
for (u32 i : ref.members)
if (sections[i])
sections[i]->kill();
}
template <typename E>
void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
if (!this->is_alive)
return;
auto report_undef = [&](Symbol<E> &sym) {
std::stringstream ss;
if (std::string_view source = this->get_source_name(); !source.empty())
ss << ">>> referenced by " << source << "\n";
else
ss << ">>> referenced by " << *this << "\n";
typename decltype(ctx.undef_errors)::accessor acc;
ctx.undef_errors.insert(acc, {sym.name(), {}});
acc->second.push_back(ss.str());
};
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
const ElfSym<E> &esym = this->elf_syms[i];
Symbol<E> &sym = *this->symbols[i];
if (!esym.is_undef())
continue;
std::scoped_lock lock(sym.mu);
// If a protected/hidden undefined symbol is resolved to an
// imported symbol, it's handled as if no symbols were found.
if (sym.file && sym.file->is_dso &&
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
report_undef(sym);
continue;
}
if (sym.file &&
(!sym.esym().is_undef() || sym.file->priority <= this->priority))
continue;
// If a symbol name is in the form of "foo@version", search for
// symbol "foo" and check if the symbol has version "version".
std::string_view key = this->symbol_strtab.data() + esym.st_name;
if (i64 pos = key.find('@'); pos != key.npos) {
Symbol<E> *sym2 = get_symbol(ctx, key.substr(0, pos));
if (sym2->file && sym2->file->is_dso &&
sym2->get_version() == key.substr(pos + 1)) {
this->symbols[i] = sym2;
continue;
}
}
auto claim = [&](bool is_imported) {
if (sym.is_traced)
SyncOut(ctx) << "trace-symbol: " << *this << ": unresolved"
<< (esym.is_weak() ? " weak" : "")
<< " symbol " << sym;
sym.file = this;
sym.origin = 0;
sym.value = 0;
sym.sym_idx = i;
sym.is_weak = false;
sym.is_imported = is_imported;
sym.is_exported = false;
sym.ver_idx = is_imported ? 0 : ctx.default_version;
};
if (esym.is_undef_weak()) {
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
ctx.arg.z_dynamic_undefined_weak) {
// Global weak undefined symbols are promoted to dynamic symbols
// when when linking a DSO, unless `-z nodynamic_undefined_weak`
// was given.
claim(true);
} else {
// Otherwise, weak undefs are converted to absolute symbols with value 0.
claim(false);
}
continue;
}
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
report_undef(sym);
// Traditionally, remaining undefined symbols cause a link failure
// only when we are creating an executable. Undefined symbols in
// shared objects are promoted to dynamic symbols, so that they'll
// get another chance to be resolved at run-time. You can change the
// behavior by passing `-z defs` to the linker.
//
// Even if `-z defs` is given, weak undefined symbols are still
// promoted to dynamic symbols for compatibility with other linkers.
// Some major programs, notably Firefox, depend on the behavior
// (they use this loophole to export symbols from libxul.so).
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
(!ctx.arg.z_defs || ctx.arg.unresolved_symbols != UNRESOLVED_ERROR)) {
claim(true);
continue;
}
// Convert remaining undefined symbols to absolute symbols with value 0.
if (ctx.arg.unresolved_symbols != UNRESOLVED_ERROR || ctx.arg.noinhibit_exec)
claim(false);
}
}
template <typename E>
void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations against seciton contents
@@ -1125,6 +1109,12 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
for (ElfRel<E> &rel : cie.get_rels()) {
Symbol<E> &sym = *this->symbols[rel.r_sym];
if constexpr (!is_mips<E>)
if (ctx.arg.pic && rel.r_type == E::R_ABS)
Error(ctx) << *this << ": relocation " << rel << " in .eh_frame can"
<< " not be used when making a position-independent output;"
<< " recompile with -fPIE or -fPIC";
if (sym.is_imported) {
if (sym.get_type() != STT_FUNC)
Fatal(ctx) << *this << ": " << sym
@@ -1200,8 +1190,6 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
sym.sym_idx = i;
sym.ver_idx = ctx.default_version;
sym.is_weak = false;
sym.is_imported = false;
sym.is_exported = false;
sections.push_back(std::move(isec));
}
@@ -1289,8 +1277,7 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
auto write_sym = [&](Symbol<E> &sym, i64 &symtab_idx) {
U32<E> *xindex = nullptr;
if (ctx.symtab_shndx)
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
symtab_idx * 4);
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + symtab_idx;
symtab_base[symtab_idx++] = to_output_esym(ctx, sym, strtab_off, xindex);
strtab_off += write_string(strtab_base + strtab_off, sym.name());
@@ -1481,8 +1468,6 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
sym.sym_idx = i;
sym.ver_idx = versyms[i];
sym.is_weak = false;
sym.is_imported = false;
sym.is_exported = false;
}
}
}
@@ -1498,8 +1483,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_dso &&
fast_mark(sym.file->is_alive)) {
if (esym.is_undef() && !esym.is_weak() && sym.file &&
!sym.file->is_alive.test_and_set()) {
feeder(sym.file);
if (sym.is_traced)
@@ -1510,7 +1495,7 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
}
template <typename E>
std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
std::span<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
assert(sym->file == this);
std::call_once(init_aliases, [&] {
@@ -1526,16 +1511,12 @@ std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
});
});
struct Cmp {
bool operator()(Symbol<E> *sym, u64 val) { return sym->esym().st_value < val; }
bool operator()(u64 val, Symbol<E> *sym) { return val < sym->esym().st_value; }
};
auto [begin, end] = std::equal_range(aliases.begin(), aliases.end(), sym,
[&](Symbol<E> *x, Symbol<E> *y) {
return x->esym().st_value < y->esym().st_value;
});
auto [begin, end] =
std::equal_range(aliases.begin(), aliases.end(), sym->esym().st_value, Cmp{});
std::vector<Symbol<E> *> vec{begin, end};
std::erase(vec, sym);
return vec;
return {&*begin, &*end};
}
// Infer an alignment of a DSO symbol. An alignment of a symbol in other
@@ -1555,13 +1536,12 @@ i64 SharedFile<E>::get_alignment(Symbol<E> *sym) {
}
template <typename E>
bool SharedFile<E>::is_readonly(Context<E> &ctx, Symbol<E> *sym) {
ElfPhdr<E> *phdr = this->get_phdr();
bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
u64 val = sym->esym().st_value;
for (i64 i = 0; i < this->get_ehdr().e_phnum; i++)
if (phdr[i].p_type == PT_LOAD && !(phdr[i].p_flags & PF_W) &&
phdr[i].p_vaddr <= val && val < phdr[i].p_vaddr + phdr[i].p_memsz)
for (ElfPhdr<E> &phdr : this->get_phdrs())
if (phdr.p_type == PT_LOAD && !(phdr.p_flags & PF_W) &&
phdr.p_vaddr <= val && val < phdr.p_vaddr + phdr.p_memsz)
return true;
return false;
}
@@ -1601,8 +1581,8 @@ void SharedFile<E>::populate_symtab(Context<E> &ctx) {
U32<E> *xindex = nullptr;
if (ctx.symtab_shndx)
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
(this->global_symtab_idx + i) * 4);
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) +
this->global_symtab_idx + i;
*symtab++ = to_output_esym(ctx, sym, strtab_off, xindex);
strtab_off += write_string(strtab + strtab_off, sym.name());
+115 -97
View File
@@ -57,7 +57,10 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
// early for REL-type ELF types to read relocation addends from
// section contents. For RELA-type, we don't need to do this because
// addends are in relocations.
if constexpr (!E::is_rela)
//
// SH-4 stores addends to sections despite being RELA, which is a
// special (and buggy) case.
if constexpr (!E::is_rela || is_sh4<E>)
uncompress(ctx);
}
@@ -104,30 +107,6 @@ void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
}
}
template <typename E>
static Action get_rel_action(Context<E> &ctx, Symbol<E> &sym,
const Action table[3][4]) {
auto get_output_type = [&] {
if (ctx.arg.shared)
return 0;
if (ctx.arg.pie)
return 1;
return 2;
};
auto get_sym_type = [&] {
if (sym.is_absolute())
return 0;
if (!sym.is_imported)
return 1;
if (sym.get_type() != STT_FUNC)
return 2;
return 3;
};
return table[get_output_type()][get_sym_type()];
}
template <typename E>
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
const ElfRel<E> &rel, Action action) {
@@ -206,26 +185,46 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
break;
case IFUNC:
dynrel();
ctx.num_ifunc_dynrels.fetch_add(1, std::memory_order_relaxed);
ctx.num_ifunc_dynrels++;
break;
default:
unreachable();
}
}
template <typename E>
static inline i64 get_output_type(Context<E> &ctx) {
if (ctx.arg.shared)
return 0;
if (ctx.arg.pie)
return 1;
return 2;
}
template <typename E>
static inline i64 get_sym_type(Symbol<E> &sym) {
if (sym.is_absolute())
return 0;
if (!sym.is_imported)
return 1;
if (sym.get_type() != STT_FUNC)
return 2;
return 3;
}
template <typename E>
static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
// We cannot promote them to dynamic relocations because the dynamic
// linker generally does not support PC-relative relocations.
constexpr static Action table[3][4] = {
static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, PLT }, // Shared object
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
return table[get_output_type(ctx)][get_sym_type(sym)];
}
template <typename E>
@@ -235,14 +234,14 @@ static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
// generally does not support dynamic relocations smaller than the
// word size, we need to report an error if a relocation cannot be
// resolved at link-time.
constexpr static Action table[3][4] = {
static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, ERROR, ERROR, ERROR }, // Shared object
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
return table[get_output_type(ctx)][get_sym_type(sym)];
}
template <typename E>
@@ -253,14 +252,14 @@ static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
// This is a decision table for absolute relocations for the word
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
// a dynamic relocation if we cannot resolve an address at link-time.
constexpr static Action table[3][4] = {
static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
{ NONE, NONE, DYN_COPYREL, DYN_CPLT }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
return table[get_output_type(ctx)][get_sym_type(sym)];
}
template <typename E>
@@ -272,14 +271,14 @@ static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
// GOT-like section, and no user-generated code directly uses values
// in it.
constexpr static Action table[3][4] = {
static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
return table[get_output_type(ctx)][get_sym_type(sym)];
}
template <typename E>
@@ -306,6 +305,15 @@ void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
}
template <typename E>
void InputSection<E>::check_tlsle(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
if (ctx.arg.shared)
Error(ctx) << *this << ": relocation " << rel << " against `" << sym
<< "` can not be used when making a shared object;"
<< " recompile with -fPIC";
}
template <typename E>
static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel, u8 *loc,
@@ -313,8 +321,8 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
Action action) {
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
auto apply_dynrel = [&] {
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
auto emit_abs_dynrel = [&] {
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, sym.get_dynsym_idx(ctx), A);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = A;
};
@@ -336,26 +344,29 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
break;
case DYN_COPYREL:
if (writable || !ctx.arg.z_copyreloc)
apply_dynrel();
emit_abs_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYN_CPLT:
if (writable)
apply_dynrel();
emit_abs_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYNREL:
apply_dynrel();
emit_abs_dynrel();
break;
case IFUNC: {
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = addr;
case IFUNC:
if constexpr (supports_ifunc<E>) {
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = addr;
} else {
unreachable();
}
break;
}
default:
unreachable();
}
@@ -365,8 +376,8 @@ template <typename E>
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P,
ElfRel<E> *&dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
ElfRel<E> **dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
get_dyn_absrel_action(ctx, sym));
}
@@ -374,8 +385,8 @@ template <typename E>
void InputSection<E>::apply_toc_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P,
ElfRel<E> *&dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
ElfRel<E> **dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
get_ppc64_toc_action(ctx, sym));
}
@@ -385,11 +396,10 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
return;
// Copy data
if constexpr (is_riscv<E>) {
if constexpr (is_riscv<E>)
copy_contents_riscv(ctx, buf);
} else {
else
uncompress_to(ctx, buf);
}
// Apply relocations
if (!ctx.arg.relocatable) {
@@ -402,7 +412,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
// Get the name of a function containin a given offset.
template <typename E>
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
for (const ElfSym<E> &esym : file.elf_syms) {
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
@@ -415,61 +425,69 @@ std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
return "";
}
// Record an undefined symbol error which will be displayed all at
// once by report_undef_errors().
// Test if the symbol a given relocation refers to has already been resolved.
// If not, record that error and returns true.
template <typename E>
void InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
std::stringstream ss;
if (std::string_view source = file.get_source_name(); !source.empty())
ss << ">>> referenced by " << source << "\n";
else
ss << ">>> referenced by " << *this << "\n";
ss << ">>> " << file;
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
ss << ":(" << func << ")";
bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
// If a relocation refers to a linker-synthesized symbol for a
// section fragment, it's always been resolved.
if (file.elf_syms.size() <= rel.r_sym)
return false;
Symbol<E> &sym = *file.symbols[rel.r_sym];
const ElfSym<E> &esym = file.elf_syms[rel.r_sym];
typename decltype(ctx.undef_errors)::accessor acc;
ctx.undef_errors.insert(acc, {sym.name(), {}});
acc->second.push_back(ss.str());
}
// Report all undefined symbols, grouped by symbol.
template <typename E>
void report_undef_errors(Context<E> &ctx) {
constexpr i64 max_errors = 3;
for (auto &pair : ctx.undef_errors) {
std::string_view sym_name = pair.first;
std::span<std::string> errors = pair.second;
if (ctx.arg.demangle)
sym_name = demangle(sym_name);
std::stringstream ss;
ss << "undefined symbol: " << sym_name << "\n";
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
ss << errors[i];
if (errors.size() > max_errors)
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
Error(ctx) << ss.str();
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
Warn(ctx) << ss.str();
// If a symbol is defined in a comdat group, and the comdat group is
// discarded, the symbol may not have an owner. It is technically an
// violation of the One Definition Rule, so it is a programmer's fault.
if (!sym.file) {
Error(ctx) << *this << ": " << sym << " refers to a discarded COMDAT section"
<< " probably due to an ODR violation";
return true;
}
ctx.checkpoint();
auto record = [&] {
std::stringstream ss;
if (std::string_view source = file.get_source_name(); !source.empty())
ss << ">>> referenced by " << source << "\n";
else
ss << ">>> referenced by " << *this << "\n";
ss << ">>> " << file;
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
ss << ":(" << func << ")";
typename decltype(ctx.undef_errors)::accessor acc;
ctx.undef_errors.insert(acc, {sym.name(), {}});
acc->second.push_back(ss.str());
};
// A non-weak undefined symbol must be promoted to an imported
// symbol or resolved to an defined symbol. Otherwise, it's an
// undefined symbol error.
//
// Every ELF file has an absolute local symbol as its first symbol.
// Referring to that symbol is always valid.
bool is_undef = esym.is_undef() && !esym.is_weak() && sym.sym_idx;
if (!sym.is_imported && is_undef && sym.esym().is_undef()) {
record();
return true;
}
// If a protected/hidden undefined symbol is resolved to other .so,
// it's handled as if no symbols were found.
if (sym.file->is_dso &&
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
record();
return true;
}
return false;
}
using E = MOLD_TARGET;
template struct CieRecord<E>;
template class InputSection<E>;
template void report_undef_errors(Context<E> &);
} // namespace mold::elf
+49
View File
@@ -0,0 +1,49 @@
#include "mold.h"
#ifndef _WIN32
# include <fcntl.h>
# include <pwd.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
#endif
namespace mold::elf {
template <typename E>
void acquire_global_lock(Context<E> &ctx) {
#ifndef _WIN32
char *jobs = getenv("MOLD_JOBS");
if (!jobs || std::string(jobs) != "1")
return;
char *home = getenv("HOME");
if (!home)
home = getpwuid(getuid())->pw_dir;
std::string path = std::string(home) + "/.mold-lock";
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
if (fd == -1)
return;
if (lockf(fd, F_LOCK, 0) == -1)
return;
ctx.global_lock_fd = fd;
#endif
}
template <typename E>
void release_global_lock(Context<E> &ctx) {
#ifndef _WIN32
if (ctx.global_lock_fd)
close(*ctx.global_lock_fd);
#endif
}
using E = MOLD_TARGET;
template void acquire_global_lock(Context<E> &);
template void release_global_lock(Context<E> &);
} // namespace mold::elf
+30 -13
View File
@@ -641,14 +641,34 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
// Initialize object symbols
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
obj->has_symver.resize(plugin_symbols.size());
obj->lto_symbol_versions.resize(plugin_symbols.size());
for (PluginSymbol &psym : plugin_symbols) {
for (i64 i = 0; i < plugin_symbols.size(); i++) {
PluginSymbol &psym = plugin_symbols[i];
esyms->push_back(to_elf_sym<E>(psym));
obj->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
std::string_view key = save_string(ctx, psym.name);
std::string_view name = key;
// Parse symbol version after atsign
if (i64 pos = name.find('@'); pos != name.npos) {
std::string_view ver = name.substr(pos);
name = name.substr(0, pos);
if (ver != "@" && ver != "@@") {
if (ver.starts_with("@@"))
key = name;
obj->has_symver.set(i);
obj->lto_symbol_versions[i] = ver.substr(1);
}
}
obj->symbols.push_back(get_symbol(ctx, key, name));
}
obj->elf_syms = *esyms;
obj->symvers.resize(esyms->size());
obj->has_symver.resize(esyms->size());
plugin_symbols.clear();
return obj;
}
@@ -666,16 +686,13 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
// Set `referenced_by_regular_obj` bit.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
if (file->is_lto_obj)
return;
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
Symbol<E> &sym = *file->symbols[i];
if (sym.file && !sym.file->is_dso &&
((ObjectFile<E> *)sym.file)->is_lto_obj) {
std::scoped_lock lock(sym.mu);
sym.referenced_by_regular_obj = true;
if (!file->is_lto_obj) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file && !sym->file->is_dso &&
((ObjectFile<E> *)sym->file)->is_lto_obj) {
std::scoped_lock lock(sym->mu);
sym->referenced_by_regular_obj = true;
}
}
}
});
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "../integers.h"
#include "../common/integers.h"
namespace mold {
+39 -17
View File
@@ -1,7 +1,7 @@
#include "mold.h"
#include "../archive-file.h"
#include "../cmdline.h"
#include "../output-file.h"
#include "../common/archive-file.h"
#include "../common/cmdline.h"
#include "../common/output-file.h"
#include <cstring>
#include <functional>
@@ -70,26 +70,30 @@ 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_LOONGARCH:
return is_64 ? LOONGARCH64::target_name : LOONGARCH32::target_name;
default:
return "";
}
};
bool opt_plugin = !ctx.arg.plugin.empty();
switch (get_file_type(mf, opt_plugin)) {
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
case FileType::ELF_DSO:
case FileType::GCC_LTO_OBJ:
return get_elf_type(mf->data);
case FileType::AR:
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
return get_elf_type(child->data);
return "";
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
return get_elf_type(child->data);
return "";
case FileType::TEXT:
@@ -163,9 +167,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
if (ctx.visited.contains(mf->name))
return;
bool opt_plugin = !ctx.arg.plugin.empty();
FileType type = get_file_type(mf, opt_plugin);
switch (type) {
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, mf, ""));
return;
@@ -176,7 +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(child, opt_plugin)) {
switch (get_file_type(ctx, child)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
break;
@@ -185,6 +187,10 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
ctx.objs.push_back(file);
break;
case FileType::ELF_DSO:
Warn(ctx) << mf->name << "(" << child->name
<< "): shared object file in an archive is ignored";
break;
default:
break;
}
@@ -371,6 +377,14 @@ 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 == LOONGARCH32::target_name)
return elf_main<LOONGARCH32>(argc, argv);
if (target == LOONGARCH64::target_name)
return elf_main<LOONGARCH64>(argc, argv);
unreachable();
}
@@ -416,6 +430,8 @@ int elf_main(int argc, char **argv) {
on_complete = fork_child();
#endif
acquire_global_lock(ctx);
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
@@ -442,7 +458,6 @@ int elf_main(int argc, char **argv) {
});
}
Timer t_total(ctx, "total");
Timer t_before_copy(ctx, "before_copy");
// Apply -exclude-libs
@@ -464,6 +479,9 @@ int elf_main(int argc, char **argv) {
// put together in a single phase.
resolve_symbols(ctx);
// "Kill" .eh_frame input sections after symbol resolution.
kill_eh_frame_sections(ctx);
// Resolve mergeable section pieces to merge them.
resolve_section_pieces(ctx);
@@ -546,10 +564,8 @@ int elf_main(int argc, char **argv) {
// Beyond this point, no new symbols will be added to the result.
// Handle --print-dependencies
if (ctx.arg.print_dependencies == 1)
if (ctx.arg.print_dependencies)
print_dependencies(ctx);
else if (ctx.arg.print_dependencies == 2)
print_dependencies_full(ctx);
// Handle -repro
if (ctx.arg.repro)
@@ -711,7 +727,6 @@ int elf_main(int argc, char **argv) {
if (ctx.has_lto_object)
lto_cleanup(ctx);
t_total.stop();
t_all.stop();
if (ctx.arg.print_map)
@@ -729,6 +744,8 @@ int elf_main(int argc, char **argv) {
if (on_complete)
on_complete();
release_global_lock(ctx);
if (ctx.arg.quick_exit)
_exit(0);
@@ -741,6 +758,7 @@ int elf_main(int argc, char **argv) {
using E = MOLD_TARGET;
template void read_file(Context<E> &, MappedFile<Context<E>> *);
template MappedFile<Context<E>> *open_library(Context<E> &, std::string);
#ifdef MOLD_X86_64
@@ -759,6 +777,10 @@ 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<LOONGARCH32>(int, char **);
extern template int elf_main<LOONGARCH64>(int, char **);
int main(int argc, char **argv) {
return elf_main<X86_64>(argc, argv);
+2 -1
View File
@@ -61,7 +61,8 @@ static bool is_ld(const char *path) {
ptr--;
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd") ||
!strcmp(ptr, "ld.mold");
}
int execvpe(const char *file, char *const *argv, char *const *envp) {
+280 -144
View File
@@ -1,7 +1,7 @@
#pragma once
#include "elf.h"
#include "../mold.h"
#include "../common/common.h"
#include <atomic>
#include <bitset>
@@ -50,6 +50,7 @@ template <typename E> struct CieRecord;
template <typename E> struct Context;
template <typename E> struct FdeRecord;
template <typename E> class RelocSection;
template <typename E> class MipsGotSection;
template <typename E>
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
@@ -60,18 +61,15 @@ std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
template <typename E>
struct SectionFragment {
SectionFragment(MergedSection<E> *sec) : output_section(*sec) {}
SectionFragment(const SectionFragment &other)
: output_section(other.output_section), offset(other.offset),
p2align(other.p2align.load()), is_alive(other.is_alive.load()) {}
SectionFragment(MergedSection<E> *sec, bool is_alive)
: output_section(*sec), is_alive(is_alive) {}
u64 get_addr(Context<E> &ctx) const;
MergedSection<E> &output_section;
u32 offset = -1;
std::atomic_uint8_t p2align = 0;
std::atomic_bool is_alive = false;
Atomic<u8> p2align = 0;
Atomic<bool> is_alive = false;
};
// Additional class members for dynamic symbols. Because most symbols
@@ -105,7 +103,9 @@ class RangeExtensionThunk {};
template <typename E> requires needs_thunk<E>
class RangeExtensionThunk<E> {
public:
RangeExtensionThunk(OutputSection<E> &osec) : output_section(osec) {}
RangeExtensionThunk(OutputSection<E> &osec, i64 offset)
: output_section(osec), offset(offset) {}
i64 size() const { return E::thunk_hdr_size + symbols.size() * E::thunk_size; }
void copy_buf(Context<E> &ctx);
@@ -114,18 +114,17 @@ public:
idx * E::thunk_size;
}
static constexpr i64 alignment = 4;
static constexpr i64 alignment = 16;
OutputSection<E> &output_section;
i32 thunk_idx = -1;
i64 offset = -1;
i64 offset;
std::mutex mu;
std::vector<Symbol<E> *> symbols;
};
struct RangeExtensionRef {
i32 thunk_idx = -1;
i32 sym_idx = -1;
i16 thunk_idx = -1;
i16 sym_idx = -1;
};
template <typename E>
@@ -202,20 +201,6 @@ struct FdeRecord {
FdeRecord(u32 input_offset, u32 rel_idx)
: input_offset(input_offset), rel_idx(rel_idx) {}
FdeRecord(const FdeRecord &other)
: input_offset(other.input_offset), output_offset(other.output_offset),
rel_idx(other.rel_idx), cie_idx(other.cie_idx),
is_alive(other.is_alive.load()) {}
FdeRecord &operator=(const FdeRecord<E> &other) {
input_offset = other.input_offset;
output_offset = other.output_offset;
rel_idx = other.rel_idx;
cie_idx = other.cie_idx;
is_alive = other.is_alive.load();
return *this;
}
i64 size(ObjectFile<E> &file) const;
std::string_view get_contents(ObjectFile<E> &file) const;
std::span<ElfRel<E>> get_rels(ObjectFile<E> &file) const;
@@ -224,7 +209,7 @@ struct FdeRecord {
u32 output_offset = -1;
u32 rel_idx = -1;
u16 cie_idx = -1;
std::atomic_bool is_alive = true;
Atomic<bool> is_alive = true;
};
// A struct to hold target-dependent input section members.
@@ -262,11 +247,11 @@ public:
const ElfShdr<E> &shdr() const;
std::span<ElfRel<E>> get_rels(Context<E> &ctx) const;
std::span<FdeRecord<E>> get_fdes() const;
std::string_view get_func_name(Context<E> &ctx, i64 offset);
bool is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel);
std::string_view get_func_name(Context<E> &ctx, i64 offset) const;
bool is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) const;
bool is_killed_by_icf() const;
void record_undef_error(Context<E> &ctx, const ElfRel<E> &rel);
bool record_undef_error(Context<E> &ctx, const ElfRel<E> &rel);
ObjectFile<E> &file;
OutputSection<E> *output_section = nullptr;
@@ -292,7 +277,7 @@ public:
bool uncompressed : 1 = false;
// For garbage collection
std::atomic_bool is_visited = false;
Atomic<bool> is_visited = false;
// For ICF
//
@@ -312,11 +297,13 @@ 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);
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> **dynrel);
void apply_toc_rel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel,
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> *&dynrel);
u8 *loc, u64 S, i64 A, u64 P, ElfRel<E> **dynrel);
void copy_contents_riscv(Context<E> &ctx, u8 *buf);
@@ -328,8 +315,13 @@ private:
std::optional<u64> get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag);
};
template <typename E>
void report_undef_errors(Context<E> &ctx);
//
// tls.cc
//
template<typename E> u64 get_tls_begin(Context<E> &);
template<typename E> u64 get_tp_addr(Context<E> &);
template<typename E> u64 get_dtp_addr(Context<E> &);
//
// output-chunks.cc
@@ -341,9 +333,6 @@ u64 get_eflags(Context<E> &ctx);
template <typename E>
i64 to_phdr_flags(Context<E> &ctx, Chunk<E> *chunk);
template <typename E>
bool is_relro(Context<E> &ctx, Chunk<E> *chunk);
template <typename E>
std::string_view get_output_name(Context<E> &ctx, std::string_view name, u64 flags);
@@ -365,6 +354,7 @@ public:
virtual ~Chunk() = default;
virtual ChunkKind kind() { return SYNTHETIC; }
virtual OutputSection<E> *to_osec() { return nullptr; }
virtual i64 get_reldyn_size(Context<E> &ctx) const { return 0; }
virtual void copy_buf(Context<E> &ctx) {}
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
virtual void update_shdr(Context<E> &ctx) {}
@@ -373,8 +363,9 @@ public:
virtual u8 *get_uncompressed_data() { return nullptr; }
std::string_view name;
ElfShdr<E> shdr = {};
ElfShdr<E> shdr = { .sh_addralign = 1 };
i64 shndx = 0;
bool is_relro = false;
// Some synethetic sections add local symbols to the output.
// For example, range extension thunks adds function_name@thunk
@@ -388,11 +379,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
@@ -458,12 +449,7 @@ public:
template <typename E>
class OutputSection : public Chunk<E> {
public:
OutputSection(std::string_view name, u32 type, u64 flags) {
this->name = name;
this->shdr.sh_type = type;
this->shdr.sh_flags = flags;
}
OutputSection(std::string_view name, u32 type, u64 flags);
ChunkKind kind() override { return OUTPUT_SECTION; }
OutputSection<E> *to_osec() override { return this; }
void copy_buf(Context<E> &ctx) override;
@@ -486,13 +472,14 @@ class GotSection : public Chunk<E> {
public:
GotSection() {
this->name = ".got";
this->is_relro = true;
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
this->shdr.sh_addralign = sizeof(Word<E>);
// We always create a .got so that _GLOBAL_OFFSET_TABLE_ has
// something to point to. s390x psABI defines GOT[1] as a
// reserved slot, so we allocate one more on s390x.
// something to point to. s390x/MIPS psABIs define GOT[1] as a
// reserved slot, so we allocate one more for them.
this->shdr.sh_size = (is_s390x<E> ? 2 : 1) * sizeof(Word<E>);
}
@@ -504,16 +491,16 @@ public:
u64 get_tlsld_addr(Context<E> &ctx) const;
bool has_tlsld(Context<E> &ctx) const { return tlsld_idx != -1; }
i64 get_reldyn_size(Context<E> &ctx) const;
i64 get_reldyn_size(Context<E> &ctx) const override;
void copy_buf(Context<E> &ctx) override;
void compute_symtab_size(Context<E> &ctx) override;
void populate_symtab(Context<E> &ctx) override;
std::vector<Symbol<E> *> got_syms;
std::vector<Symbol<E> *> gottp_syms;
std::vector<Symbol<E> *> tlsgd_syms;
std::vector<Symbol<E> *> tlsdesc_syms;
std::vector<Symbol<E> *> gottp_syms;
u32 tlsld_idx = -1;
void construct_relr(Context<E> &ctx);
@@ -523,8 +510,9 @@ public:
template <typename E>
class GotPltSection : public Chunk<E> {
public:
GotPltSection() {
GotPltSection(Context<E> &ctx) {
this->name = ".got.plt";
this->is_relro = ctx.arg.z_now;
this->shdr.sh_type = is_ppc64<E> ? SHT_NOBITS : SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
this->shdr.sh_addralign = sizeof(Word<E>);
@@ -610,7 +598,6 @@ public:
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
void sort(Context<E> &ctx);
};
@@ -677,6 +664,7 @@ class DynamicSection : public Chunk<E> {
public:
DynamicSection() {
this->name = ".dynamic";
this->is_relro = true;
this->shdr.sh_type = SHT_DYNAMIC;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
this->shdr.sh_addralign = sizeof(Word<E>);
@@ -779,7 +767,9 @@ public:
static MergedSection<E> *
get_instance(Context<E> &ctx, std::string_view name, u64 type, u64 flags);
SectionFragment<E> *insert(std::string_view data, u64 hash, i64 p2align);
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
u64 hash, i64 p2align);
void assign_offsets(Context<E> &ctx);
void copy_buf(Context<E> &ctx) override;
void write_to(Context<E> &ctx, u8 *buf) override;
@@ -806,7 +796,7 @@ public:
}
void construct(Context<E> &ctx);
void apply_reloc(Context<E> &ctx, const ElfRel<E> &rel, u64 offset, u64 val);
void apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel, u64 offset, u64 val);
void copy_buf(Context<E> &ctx) override;
};
@@ -847,17 +837,18 @@ public:
template <typename E>
class CopyrelSection : public Chunk<E> {
public:
CopyrelSection(bool is_relro) : is_relro(is_relro) {
CopyrelSection(bool is_relro) {
this->name = is_relro ? ".copyrel.rel.ro" : ".copyrel";
this->is_relro = is_relro;
this->shdr.sh_type = SHT_NOBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
}
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;
std::vector<Symbol<E> *> symbols;
};
@@ -1045,6 +1036,7 @@ class RelroPaddingSection : public Chunk<E> {
public:
RelroPaddingSection() {
this->name = ".relro_padding";
this->is_relro = true;
this->shdr.sh_type = SHT_NOBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
this->shdr.sh_addralign = 1;
@@ -1151,7 +1143,7 @@ public:
std::string_view get_string(Context<E> &ctx, i64 idx);
ElfEhdr<E> &get_ehdr() { return *(ElfEhdr<E> *)mf->data; }
ElfPhdr<E> *get_phdr() { return (ElfPhdr<E> *)(mf->data + get_ehdr().e_phoff); }
std::span<ElfPhdr<E>> get_phdrs();
ElfShdr<E> *find_section(i64 type);
@@ -1174,7 +1166,7 @@ public:
std::string filename;
bool is_dso = false;
u32 priority;
std::atomic_bool is_alive = false;
Atomic<bool> is_alive = false;
std::string_view shstrtab;
std::string_view symbol_strtab;
@@ -1194,6 +1186,26 @@ protected:
std::vector<Symbol<E>> frag_syms;
};
template <typename E> struct ObjectFileExtras {};
template <typename E> requires is_riscv<E>
struct ObjectFileExtras<E> {
std::optional<i64> stack_align;
std::optional<std::string_view> arch;
bool unaligned_access = false;
};
template <> struct ObjectFileExtras<PPC32> {
InputSection<PPC32> *got2 = nullptr;
};
template <typename E> requires is_mips<E>
struct ObjectFileExtras<E> {
std::unique_ptr<InputSection<E>> abi_flags;
MipsGotSection<E> *got = nullptr;
u64 gp0 = 0;
};
// ObjectFile represents an input .o file.
template <typename E>
class ObjectFile : public InputFile<E> {
@@ -1210,10 +1222,7 @@ public:
void mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) override;
void convert_undefined_weak_symbols(Context<E> &ctx);
void resolve_comdat_groups();
void mark_addrsig(Context<E> &ctx);
void eliminate_duplicate_comdat_groups();
void claim_unresolved_symbols(Context<E> &ctx);
void scan_relocations(Context<E> &ctx);
void convert_common_symbols(Context<E> &ctx);
void compute_symtab_size(Context<E> &ctx);
@@ -1229,7 +1238,7 @@ public:
std::vector<ElfShdr<E>> elf_sections2;
std::vector<CieRecord<E>> cies;
std::vector<FdeRecord<E>> fdes;
std::vector<const char *> symvers;
BitVector has_symver;
std::vector<ComdatGroupRef<E>> comdat_groups;
bool exclude_libs = false;
std::map<u32, u32> gnu_properties;
@@ -1262,8 +1271,11 @@ public:
i64 num_areas = 0;
i64 area_offset = 0;
// For PPC32
InputSection<E> *ppc32_got2 = nullptr;
// For LTO
std::vector<std::string_view> lto_symbol_versions;
// Target-specific member
[[no_unique_address]] ObjectFileExtras<E> extra;
private:
ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
@@ -1293,9 +1305,9 @@ public:
void parse(Context<E> &ctx);
void resolve_symbols(Context<E> &ctx) override;
std::vector<Symbol<E> *> find_aliases(Symbol<E> *sym);
std::span<Symbol<E> *> find_aliases(Symbol<E> *sym);
i64 get_alignment(Symbol<E> *sym);
bool is_readonly(Context<E> &ctx, Symbol<E> *sym);
bool is_readonly(Symbol<E> *sym);
void mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) override;
@@ -1391,6 +1403,13 @@ template <typename E>
[[noreturn]]
void process_run_subcommand(Context<E> &ctx, int argc, char **argv);
//
// jobs.cc
//
template <typename E> void acquire_global_lock(Context<E> &ctx);
template <typename E> void release_global_lock(Context<E> &ctx);
//
// commandline.cc
//
@@ -1407,6 +1426,7 @@ template <typename E> void apply_exclude_libs(Context<E> &);
template <typename E> void create_synthetic_sections(Context<E> &);
template <typename E> void set_file_priority(Context<E> &);
template <typename E> void resolve_symbols(Context<E> &);
template <typename E> void kill_eh_frame_sections(Context<E> &);
template <typename E> void resolve_section_pieces(Context<E> &);
template <typename E> void convert_common_symbols(Context<E> &);
template <typename E> void compute_merged_section_sizes(Context<E> &);
@@ -1414,8 +1434,6 @@ template <typename E> void create_output_sections(Context<E> &);
template <typename E> void add_synthetic_symbols(Context<E> &);
template <typename E> void check_cet_errors(Context<E> &);
template <typename E> void print_dependencies(Context<E> &);
template <typename E> void print_dependencies(Context<E> &);
template <typename E> void print_dependencies_full(Context<E> &);
template <typename E> void write_repro_file(Context<E> &);
template <typename E> void check_duplicate_symbols(Context<E> &);
template <typename E> void check_symbol_types(Context<E> &);
@@ -1428,6 +1446,7 @@ template <typename E> void claim_unresolved_symbols(Context<E> &);
template <typename E> void scan_relocations(Context<E> &);
template <typename E> void construct_relr(Context<E> &);
template <typename E> void create_output_symtab(Context<E> &);
template <typename E> void report_undef_errors(Context<E> &);
template <typename E> void create_reloc_sections(Context<E> &);
template <typename E> void copy_chunks(Context<E> &);
template <typename E> void apply_version_script(Context<E> &);
@@ -1452,6 +1471,20 @@ void fixup_arm_exidx_section(Context<ARM32> &ctx);
// arch-riscv64.cc
//
template <typename E> requires is_riscv<E>
class RiscvAttributesSection : public Chunk<E> {
public:
RiscvAttributesSection() {
this->name = ".riscv.attributes";
this->shdr.sh_type = SHT_RISCV_ATTRIBUTES;
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
std::vector<u8> contents;
};
template <typename E>
i64 riscv_resize_sections(Context<E> &ctx);
@@ -1472,6 +1505,7 @@ public:
}
void add_symbol(Context<PPC64V1> &ctx, Symbol<PPC64V1> *sym);
i64 get_reldyn_size(Context<PPC64V1> &ctx) const override;
void copy_buf(Context<PPC64V1> &ctx) override;
static constexpr i64 ENTRY_SIZE = sizeof(Word<PPC64V1>) * 3;
@@ -1479,23 +1513,6 @@ public:
std::vector<Symbol<PPC64V1> *> symbols;
};
//
// arch-s390x.cc
//
class S390XTlsGetOffsetSection : public Chunk<S390X> {
public:
S390XTlsGetOffsetSection() {
this->name = ".tls_get_offset";
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
this->shdr.sh_addralign = 4;
this->shdr.sh_size = 28;
}
void copy_buf(Context<S390X> &ctx) override;
};
//
// arch-sparc.cc
//
@@ -1521,6 +1538,7 @@ class AlphaGotSection : public Chunk<ALPHA> {
public:
AlphaGotSection() {
this->name = ".alpha_got";
this->is_relro = true;
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
this->shdr.sh_addralign = 8;
@@ -1529,7 +1547,7 @@ public:
void add_symbol(Symbol<ALPHA> &sym, i64 addend);
void finalize();
u64 get_addr(Symbol<ALPHA> &sym, i64 addend);
i64 get_reldyn_size(Context<ALPHA> &ctx);
i64 get_reldyn_size(Context<ALPHA> &ctx) const override;
void copy_buf(Context<ALPHA> &ctx) override;
struct Entry {
@@ -1538,13 +1556,91 @@ public:
i64 addend;
};
i64 reldyn_offset = 0;
private:
std::vector<Entry> entries;
std::mutex mu;
};
//
// arch-mips64.cc
//
template <typename E>
class MipsQuickstartSection : public Chunk<E> {
public:
MipsQuickstartSection() {
this->name = ".mips_quickstart";
this->is_relro = true;
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_MIPS_GPREL;
this->shdr.sh_addralign = 8;
}
static constexpr i64 NUM_RESERVED = 2;
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
};
template <typename E>
class MipsGotSection : public Chunk<E> {
public:
MipsGotSection(Context<E> &ctx, const ObjectFile<E> &file) {
this->name = save_string(ctx, ".mips_got." + std::to_string(file.priority));
this->is_relro = true;
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_MIPS_GPREL;
this->shdr.sh_addralign = 8;
}
u64 get_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
u64 get_gotpage_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
u64 get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const;
u64 get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const;
u64 get_tlsld_addr(Context<E> &ctx) const;
void update_shdr(Context<E> &ctx) override;
i64 get_reldyn_size(Context<E> &ctx) const override;
void copy_buf(Context<E> &ctx) override;
struct SymbolAddend {
bool operator==(const SymbolAddend &) const = default;
bool operator<(const SymbolAddend &) const;
u64 get_addr(Context<E> &ctx, i64 flags = 0) const;
Symbol<E> *sym;
i64 addend;
};
std::vector<SymbolAddend> got_syms;
std::vector<SymbolAddend> gotpage_syms;
std::vector<Symbol<E> *> tlsgd_syms;
std::vector<Symbol<E> *> gottp_syms;
bool has_tlsld = false;
};
template <typename E>
class MipsABIFlagsSection : public Chunk<E> {
public:
MipsABIFlagsSection() {
this->name = ".MIPS.abiflags";
this->shdr.sh_type = SHT_MIPS_ABIFLAGS;
this->shdr.sh_flags = SHF_ALLOC;
this->shdr.sh_addralign = 8;
}
std::string_view contents;
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
};
template <typename E>
void mips_merge_got_sections(Context<E> &ctx);
template <typename E>
void mips_rewrite_cie(Context<E> &ctx, u8 *buf, CieRecord<E> &cie);
//
// main.cc
//
@@ -1597,9 +1693,14 @@ struct SectionOrder {
u64 value = 0;
};
// Target-specific context clss
// Target-specific context members
template <typename E> struct ContextExtras {};
template <typename E> requires is_riscv<E>
struct ContextExtras<E> {
RiscvAttributesSection<E> *riscv_attributes = nullptr;
};
template <> struct ContextExtras<PPC32> {
Symbol<PPC32> *_SDA_BASE_ = nullptr;
};
@@ -1611,20 +1712,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 = nullptr;
};
template <> struct ContextExtras<S390X> {
S390XTlsGetOffsetSection *tls_get_offset = nullptr;
SparcTlsGetAddrSection *tls_get_addr_sec = nullptr;
Symbol<SPARC64> *tls_get_addr_sym = nullptr;
};
template <> struct ContextExtras<ALPHA> {
AlphaGotSection *got = nullptr;
};
template <typename E> requires is_mips<E>
struct ContextExtras<E> {
MipsQuickstartSection<E> *quickstart = nullptr;
MipsABIFlagsSection<E> *abi_flags = nullptr;
};
// Context represents a context object for each invocation of the linker.
// It contains command line flags, pointers to singleton objects
// (such as linker-synthesized output sections), unique_ptrs for
@@ -1652,11 +1757,11 @@ struct Context {
bool Bsymbolic = false;
bool Bsymbolic_functions = false;
bool allow_multiple_definition = false;
bool apply_dynamic_relocs = true;
bool color_diagnostics = false;
bool default_symver = false;
bool demangle = true;
bool discard_all = false;
bool apply_dynamic_relocs = true;
bool discard_locals = false;
bool eh_frame_hdr = true;
bool emit_relocs = false;
@@ -1667,7 +1772,7 @@ struct Context {
bool fork = true;
bool gc_sections = false;
bool gdb_index = false;
bool hash_style_gnu = true;
bool hash_style_gnu = !is_mips<E>;
bool hash_style_sysv = true;
bool icf = false;
bool icf_all = false;
@@ -1681,6 +1786,7 @@ struct Context {
bool perf = false;
bool pic = false;
bool pie = false;
bool print_dependencies = false;
bool print_gc_sections = false;
bool print_icf_sections = false;
bool print_map = false;
@@ -1695,6 +1801,7 @@ struct Context {
bool stats = false;
bool strip_all = false;
bool strip_debug = false;
bool suppress_warnings = false;
bool trace = false;
bool undefined_version = false;
bool warn_common = false;
@@ -1716,22 +1823,23 @@ struct Context {
bool z_now = false;
bool z_origin = false;
bool z_relro = true;
bool z_sectionheader = 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;
u64 shuffle_sections_seed;
std::string_view emulation;
std::optional<Glob> unique;
std::optional<u64> physical_image_base;
std::optional<u64> shuffle_sections_seed;
std::string Map;
std::string chroot;
std::string dependency_file;
std::string directory;
std::string dynamic_linker;
std::string entry = "_start";
std::string entry = is_mips<E> ? "__start" : "_start";
std::string fini = "_fini";
std::string init = "_init";
std::string output = "a.out";
@@ -1761,8 +1869,11 @@ struct Context {
std::vector<VersionPattern> version_patterns;
u16 default_version = VER_NDX_GLOBAL;
bool default_version_from_version_script = false; // true if default_version is set by a wildcard in version script.
i64 page_size = -1;
std::optional<int> global_lock_fd;
// true if default_version is set by a wildcard in version script.
bool default_version_from_version_script = false;
// Reader context
bool as_needed = false;
@@ -1811,7 +1922,6 @@ struct Context {
std::vector<Chunk<E> *> chunks;
std::atomic_bool needs_tlsld = false;
std::atomic_bool has_gottp_rel = false;
std::atomic_bool has_textrel = false;
std::atomic_uint32_t num_ifunc_dynrels = 0;
@@ -1854,10 +1964,6 @@ struct Context {
[[no_unique_address]] ContextExtras<E> extra;
// Frequently accessed symbols
Symbol<E> *tls_get_addr = nullptr;
Symbol<E> *tls_get_offset = nullptr;
// For --gdb-index
Chunk<E> *debug_info = nullptr;
Chunk<E> *debug_abbrev = nullptr;
@@ -1894,6 +2000,7 @@ struct Context {
Symbol<E> *_edata = nullptr;
Symbol<E> *_end = nullptr;
Symbol<E> *_etext = nullptr;
Symbol<E> *_gp = nullptr;
Symbol<E> *edata = nullptr;
Symbol<E> *end = nullptr;
Symbol<E> *etext = nullptr;
@@ -1932,7 +2039,6 @@ enum {
NEEDS_COPYREL = 1 << 5,
NEEDS_TLSDESC = 1 << 6,
NEEDS_PPC_OPD = 1 << 7, // for PPCv1
NEEDS_ALPHA_GOT = 1 << 7, // for Alpha
};
// A struct to hold target-dependent symbol members.
@@ -1942,8 +2048,8 @@ struct SymbolExtras {};
template <typename E> requires needs_thunk<E>
struct SymbolExtras<E> {
// For range extension thunks
i32 thunk_idx = -1;
i32 thunk_sym_idx = -1;
i16 thunk_idx = -1;
i16 thunk_sym_idx = -1;
};
// Flags for Symbol<E>::get_addr()
@@ -2022,7 +2128,7 @@ public:
std::string_view get_version() const;
i64 get_output_sym_idx(Context<E> &ctx) const;
const ElfSym<E> &esym() const;
void clear();
void add_aux(Context<E> &ctx);
// A symbol is owned by a file. If two or more files define the
// same symbol, the one with the strongest definition owns the symbol.
@@ -2059,10 +2165,10 @@ public:
u16 ver_idx = 0;
// `flags` has NEEDS_ flags.
std::atomic_uint8_t flags = 0;
Atomic<u8> flags = 0;
tbb::spin_mutex mu;
std::atomic_uint8_t visibility = STV_DEFAULT;
Atomic<u8> visibility = STV_DEFAULT;
bool is_weak : 1 = false;
bool write_to_symtab : 1 = false; // for --strip-all and the like
@@ -2168,10 +2274,10 @@ public:
//
// `has_copyrel` is true if we need to emit a copy relocation for this
// symbol. If the original symbol in a DSO is in a read-only memory
// region, `copyrel_readonly` is set to true so that the copied data
// region, `is_copyrel_readonly` is set to true so that the copied data
// will become read-only at run-time.
bool has_copyrel : 1 = false;
bool copyrel_readonly : 1 = false;
bool is_copyrel_readonly : 1 = false;
// For LTO. True if the symbol is referenced by a regular object (as
// opposed to IR object).
@@ -2282,6 +2388,9 @@ i64 get_addend(InputSection<E> &isec, const ElfRel<E> &rel) {
template <typename E>
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel);
template <typename E> requires E::is_rela
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {}
template <typename E>
inline const ElfShdr<E> &InputSection<E>::shdr() const {
if (shndx < file.elf_sections.size())
@@ -2367,7 +2476,8 @@ InputSection<E>::get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag) {
}
template <typename E>
inline bool InputSection<E>::is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) {
inline bool
InputSection<E>::is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) const {
return ctx.arg.pack_dyn_relocs_relr &&
!(shdr().sh_flags & SHF_EXECINSTR) &&
(shdr().sh_addralign % sizeof(Word<E>)) == 0 &&
@@ -2445,6 +2555,24 @@ inline InputSection<E> *ObjectFile<E>::get_section(const ElfSym<E> &esym) {
return sections[get_shndx(esym)].get();
}
template <typename E>
OutputSection<E> *find_section(Context<E> &ctx, u32 sh_type) {
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_type == sh_type)
return osec;
return nullptr;
}
template <typename E>
OutputSection<E> *find_section(Context<E> &ctx, std::string_view name) {
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->name == name)
return osec;
return nullptr;
}
template <typename E>
u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
if (SectionFragment<E> *frag = get_frag()) {
@@ -2460,7 +2588,7 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
}
if (has_copyrel) {
return copyrel_readonly
return is_copyrel_readonly
? ctx.copyrel_relro->shdr.sh_addr + value
: ctx.copyrel->shdr.sh_addr + value;
}
@@ -2487,11 +2615,17 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
// so pointing to a specific location in a source .eh_frame
// section doesn't make much sense. However, CRT files contain
// symbols pointing to the very beginning and ending of the section.
if (name() == "__EH_FRAME_BEGIN__" || name() == "__EH_FRAME_LIST__" ||
name() == ".eh_frame_seg" || esym().st_type == STT_SECTION)
//
// If LTO is enabled, GCC may add `.lto_priv.<whatever>` as a symbol
// suffix. That's why we use starts_with() instead of `==` here.
if (name().starts_with("__EH_FRAME_BEGIN__") ||
name().starts_with("__EH_FRAME_LIST__") ||
name().starts_with(".eh_frame_seg") ||
esym().st_type == STT_SECTION)
return ctx.eh_frame->shdr.sh_addr;
if (name() == "__FRAME_END__" || name() == "__EH_FRAME_LIST_END__")
if (name().starts_with("__FRAME_END__") ||
name().starts_with("__EH_FRAME_LIST_END__"))
return ctx.eh_frame->shdr.sh_addr + ctx.eh_frame->shdr.sh_size;
// ARM object files contain "$d" local symbol at the beginning
@@ -2500,7 +2634,7 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
if (name() == "$d" || name().starts_with("$d."))
return ctx.eh_frame->shdr.sh_addr;
Fatal(ctx) << "symbol referring .eh_frame is not supported: "
Fatal(ctx) << "symbol referring to .eh_frame is not supported: "
<< *this << " " << *file;
}
@@ -2545,10 +2679,25 @@ inline u64 Symbol<E>::get_tlsdesc_addr(Context<E> &ctx) const {
return ctx.got->shdr.sh_addr + get_tlsdesc_idx(ctx) * sizeof(Word<E>);
}
template <typename E>
inline u64 to_plt_offset(i32 pltidx) {
if constexpr (is_ppc64v1<E>) {
// The PPC64 ELFv1 ABI requires PLT entries to vary in size
// depending on their indices. For entries whose PLT index is
// less than 32768, the entry size is 8 bytes. Other entries are
// 12 bytes long.
if (pltidx < 0x8000)
return E::plt_hdr_size + pltidx * 8;
return E::plt_hdr_size + 0x8000 * 8 + (pltidx - 0x8000) * 12;
} else {
return E::plt_hdr_size + pltidx * E::plt_size;
}
}
template <typename E>
inline u64 Symbol<E>::get_plt_addr(Context<E> &ctx) const {
if (i32 idx = get_plt_idx(ctx); idx != -1)
return ctx.plt->shdr.sh_addr + E::plt_hdr_size + idx * E::plt_size;
return ctx.plt->shdr.sh_addr + to_plt_offset<E>(idx);
return ctx.pltgot->shdr.sh_addr + get_pltgot_idx(ctx) * E::pltgot_size;
}
@@ -2777,15 +2926,12 @@ inline std::string_view Symbol<E>::name() const {
}
template <typename E>
inline void Symbol<E>::clear() {
file = nullptr;
origin = 0;
value = -1;
sym_idx = -1;
ver_idx = 0;
is_weak = false;
is_imported = false;
is_exported = false;
inline void Symbol<E>::add_aux(Context<E> &ctx) {
if (aux_idx == -1) {
i64 sz = ctx.symbol_aux.size();
aux_idx = sz;
ctx.symbol_aux.resize(sz + 1);
}
}
inline bool is_c_identifier(std::string_view s) {
@@ -2808,16 +2954,6 @@ inline bool is_c_identifier(std::string_view s) {
return true;
}
template <typename E>
inline bool relax_tlsgd(Context<E> &ctx, Symbol<E> &sym) {
return ctx.arg.relax && !ctx.arg.shared && !sym.is_imported;
}
template <typename E>
inline bool relax_tlsld(Context<E> &ctx) {
return ctx.arg.relax && !ctx.arg.shared;
}
template <typename E>
inline bool relax_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
// TLSDESC relocs must be always relaxed for statically-linked
+355 -324
View File
File diff suppressed because it is too large. Load diff
+289 -156
View File
@@ -4,7 +4,6 @@
#include <functional>
#include <map>
#include <optional>
#include <random>
#include <regex>
#include <shared_mutex>
#include <tbb/parallel_for_each.h>
@@ -58,13 +57,14 @@ void create_synthetic_sections(Context<E> &ctx) {
else
ctx.phdr = push(new OutputPhdr<E>(0));
ctx.shdr = push(new OutputShdr<E>);
if (ctx.arg.z_sectionheader)
ctx.shdr = push(new OutputShdr<E>);
}
ctx.got = push(new GotSection<E>);
if constexpr (!is_sparc<E>)
ctx.gotplt = push(new GotPltSection<E>);
ctx.gotplt = push(new GotPltSection<E>(ctx));
ctx.reldyn = push(new RelDynSection<E>);
ctx.relplt = push(new RelPltSection<E>);
@@ -82,7 +82,7 @@ void create_synthetic_sections(Context<E> &ctx) {
ctx.copyrel = push(new CopyrelSection<E>(false));
ctx.copyrel_relro = push(new CopyrelSection<E>(true));
if (!ctx.arg.oformat_binary)
if (ctx.shdr)
ctx.shstrtab = push(new ShstrtabSection<E>);
if (!ctx.arg.dynamic_linker.empty())
@@ -113,29 +113,35 @@ 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_riscv<E>)
ctx.extra.riscv_attributes = push(new RiscvAttributesSection<E>);
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.quickstart = push(new MipsQuickstartSection<E>);
ctx.extra.abi_flags = push(new MipsABIFlagsSection<E>);
for (ObjectFile<E> *file : ctx.objs)
file->extra.got = push(new MipsGotSection<E>(ctx, *file));
}
// If .dynamic exists, .dynsym and .dynstr must exist as well
// since .dynamic refers them.
if (ctx.dynamic) {
ctx.dynstr->keep();
ctx.dynsym->keep();
}
ctx.tls_get_addr = get_symbol(ctx, "__tls_get_addr");
ctx.tls_get_offset = get_symbol(ctx, "__tls_get_offset");
}
template <typename E>
@@ -225,11 +231,16 @@ void do_resolve_symbols(Context<E> &ctx) {
Timer t(ctx, "eliminate_comdats");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
file->resolve_comdat_groups();
for (ComdatGroupRef<E> &ref : file->comdat_groups)
update_minimum(ref.group->owner, file->priority);
});
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
file->eliminate_duplicate_comdat_groups();
for (ComdatGroupRef<E> &ref : file->comdat_groups)
if (ref.group->owner != file->priority)
for (u32 i : ref.members)
if (file->sections[i])
file->sections[i]->kill();
});
}
@@ -273,12 +284,10 @@ void resolve_symbols(Context<E> &ctx) {
// Redo name resolution from scratch.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->clear_symbols();
file->is_alive = !file->is_in_lib;
});
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
file->clear_symbols();
file->is_alive = !file->is_needed;
});
// Remove IR object files.
@@ -292,6 +301,29 @@ void resolve_symbols(Context<E> &ctx) {
}
}
// .eh_frame sections are parsed and regenerated by the linker for the purpose
// of deduplication and garbage collection. As such, the input sections should
// not be copied over.
// However, in very rare cases (e.g. GCC CRT compiled with LTO) we might need
// to resolve cross-object .eh_frame section references (they only point to
// begin or end and don't depend on the actual section contents).
// Therefore, the sections are "killed" after symbol resolution as a separate
// pass.
template <typename E>
void kill_eh_frame_sections(Context<E> &ctx) {
Timer t(ctx, "kill_eh_frame_sections");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (i64 i = 0; i < file->sections.size(); i++) {
if (std::unique_ptr<InputSection<E>> &isec = file->sections[i]) {
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
isec->is_alive = false;
}
}
}
});
}
template <typename E>
void resolve_section_pieces(Context<E> &ctx) {
Timer t(ctx, "resolve_section_pieces");
@@ -331,24 +363,13 @@ void add_comment_string(Context<E> &ctx, std::string str) {
std::string_view buf = save_string(ctx, str);
std::string_view data(buf.data(), buf.size() + 1);
SectionFragment<E> *frag = sec->insert(data, hash_string(data), 0);
frag->is_alive = true;
sec->insert(ctx, data, hash_string(data), 0);
}
template <typename E>
void compute_merged_section_sizes(Context<E> &ctx) {
Timer t(ctx, "compute_merged_section_sizes");
// Mark section fragments referenced by live objects.
if (!ctx.arg.gc_sections) {
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
if (m)
for (SectionFragment<E> *frag : m->fragments)
frag->is_alive.store(true, std::memory_order_relaxed);
});
}
// Add an identification string to .comment.
if (!ctx.arg.oformat_binary)
add_comment_string(ctx, mold_version);
@@ -357,7 +378,6 @@ void compute_merged_section_sizes(Context<E> &ctx) {
if (char *env = getenv("MOLD_DEBUG"); env && env[0])
add_comment_string(ctx, "mold command line: " + get_cmdline_args(ctx));
Timer t2(ctx, "MergedSection assign_offsets");
tbb::parallel_for_each(ctx.merged_sections,
[&](std::unique_ptr<MergedSection<E>> &sec) {
sec->assign_offsets(ctx);
@@ -443,7 +463,7 @@ get_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
static std::string_view prefixes[] = {
".text.", ".data.rel.ro.", ".data.", ".rodata.", ".bss.rel.ro.", ".bss.",
".init_array.", ".fini_array.", ".tbss.", ".tdata.", ".gcc_except_table.",
".ctors.", ".dtors.", ".gnu.warning.",
".ctors.", ".dtors.", ".gnu.warning.", ".openbsd.randomdata.",
};
for (std::string_view prefix : prefixes) {
@@ -479,7 +499,7 @@ template <typename E>
void create_output_sections(Context<E> &ctx) {
Timer t(ctx, "create_output_sections");
struct Cmp {
struct Hash {
size_t operator()(const OutputSectionKey &k) const {
u64 h = hash_string(k.name);
h = combine_hash(h, std::hash<u64>{}(k.type));
@@ -488,34 +508,53 @@ void create_output_sections(Context<E> &ctx) {
}
};
std::unordered_map<OutputSectionKey, OutputSection<E> *, Cmp> map;
std::unordered_map<OutputSectionKey, OutputSection<E> *, Hash> map;
std::shared_mutex mu;
// Instantiate output sections
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
// Make a per-thread cache of the main map to avoid lock contention.
// It makes a noticeable difference if we have millions of input sections.
decltype(map) cache;
{
std::shared_lock lock(mu);
cache = map;
}
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (!isec || !isec->is_alive)
continue;
OutputSectionKey key = get_output_section_key(ctx, *isec);
{
std::shared_lock lock(mu);
auto it = map.find(key);
if (it != map.end()) {
isec->output_section = it->second;
continue;
}
if (auto it = cache.find(key); it != cache.end()) {
isec->output_section = it->second;
continue;
}
std::unique_ptr<OutputSection<E>> osec =
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
std::unique_lock lock(mu);
auto get_or_insert = [&] {
{
std::shared_lock lock(mu);
if (auto it = map.find(key); it != map.end())
return it->second;
}
auto [it, inserted] = map.insert({key, osec.get()});
isec->output_section = it->second;
if (inserted)
ctx.osec_pool.emplace_back(std::move(osec));
std::unique_ptr<OutputSection<E>> osec =
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
std::unique_lock lock(mu);
auto [it, inserted] = map.insert({key, osec.get()});
OutputSection<E> *ret = it->second;
lock.unlock();
if (inserted)
ctx.osec_pool.emplace_back(std::move(osec));
return ret;
};
OutputSection<E> *osec = get_or_insert();
isec->output_section = osec;
cache.insert({key, osec});
}
});
@@ -615,7 +654,7 @@ void create_internal_file(Context<E> &ctx) {
add(get_symbol(ctx, ord.name));
obj->elf_syms = ctx.internal_esyms;
obj->symvers.resize(ctx.internal_esyms.size() - 1);
obj->has_symver.resize(ctx.internal_esyms.size() - 1);
}
template <typename E>
@@ -628,7 +667,7 @@ get_start_stop_name(Context<E> &ctx, Chunk<E> &chunk) {
if (ctx.arg.start_stop) {
auto isalnum = [](char c) {
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
('0' <= c && c <= '9');
('0' <= c && c <= '9');
};
std::string s{chunk.name};
@@ -715,6 +754,9 @@ void add_synthetic_symbols(Context<E> &ctx) {
if constexpr (is_ppc32<E>)
ctx.extra._SDA_BASE_ = add("_SDA_BASE_");
if constexpr (is_mips<E>)
ctx._gp = add("_gp");
for (Chunk<E> *chunk : ctx.chunks) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
add(save_string(ctx, "__start_" + *name));
@@ -728,7 +770,7 @@ void add_synthetic_symbols(Context<E> &ctx) {
}
obj.elf_syms = ctx.internal_esyms;
obj.symvers.resize(ctx.internal_esyms.size() - 1);
obj.has_symver.resize(ctx.internal_esyms.size() - 1);
obj.resolve_symbols(ctx);
@@ -765,19 +807,20 @@ void add_synthetic_symbols(Context<E> &ctx) {
template <typename E>
void check_cet_errors(Context<E> &ctx) {
bool warning = (ctx.arg.z_cet_report == CET_REPORT_WARNING);
assert(warning || (ctx.arg.z_cet_report == CET_REPORT_ERROR));
assert(warning || ctx.arg.z_cet_report == CET_REPORT_ERROR);
auto has_feature = [](ObjectFile<E> *file, u32 feature) {
return std::any_of(file->gnu_properties.begin(), file->gnu_properties.end(),
[&](auto kv) {
return kv.first == GNU_PROPERTY_X86_FEATURE_1_AND
&& (kv.second & feature);
});
[&](std::pair<u32, u32> kv) {
return kv.first == GNU_PROPERTY_X86_FEATURE_1_AND &&
(kv.second & feature);
});
};
for (ObjectFile<E> *file : ctx.objs) {
if (file == ctx.internal_obj)
continue;
if (!has_feature(file, GNU_PROPERTY_X86_FEATURE_1_IBT)) {
if (warning)
Warn(ctx) << *file << ": -cet-report=warning: "
@@ -803,37 +846,14 @@ void print_dependencies(Context<E> &ctx) {
SyncOut(ctx) <<
R"(# This is an output of the mold linker's --print-dependencies option.
#
# Each line consists of three fields, <file1>, <file2> and <symbol>
# separated by tab characters. It indicates that <file1> depends on
# <file2> to use <symbol>.)";
auto print = [&](InputFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
if (esym.is_undef() && sym.file && sym.file != file)
SyncOut(ctx) << *file << "\t" << *sym.file << "\t" << sym;
}
};
for (InputFile<E> *file : ctx.objs)
print(file);
for (InputFile<E> *file : ctx.dsos)
print(file);
}
template <typename E>
void print_dependencies_full(Context<E> &ctx) {
SyncOut(ctx) <<
R"(# This is an output of the mold linker's --print-dependencies=full option.
#
# Each line consists of 4 fields, <section1>, <section2>, <symbol-type> and
# <symbol>, separated by tab characters. It indicates that <section1> depends
# on <section2> to use <symbol>. <symbol-type> is either "u" or "w" for
# regular undefined or weak undefined, respectively.
#
# If you want to obtain dependency information per function granularity,
# compile source files with the -ffunction-sections compiler flag.)";
# compile source files with the -ffunction-sections compiler flag.
)";
auto println = [&](auto &src, Symbol<E> &sym, ElfSym<E> &esym) {
if (InputSection<E> *isec = sym.get_input_section())
@@ -1011,8 +1031,12 @@ void sort_init_fini(Context<E> &ctx) {
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_REVERSE)
std::reverse(osec->members.begin(), osec->members.end());
std::unordered_map<InputSection<E> *, i64> map;
for (InputSection<E> *isec : osec->members)
map.insert({isec, get_priority(isec)});
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
return get_priority(a) < get_priority(b);
return map[a] < map[b];
});
}
}
@@ -1050,8 +1074,12 @@ void sort_ctor_dtor(Context<E> &ctx) {
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_REVERSE)
std::reverse(osec->members.begin(), osec->members.end());
std::unordered_map<InputSection<E> *, i64> map;
for (InputSection<E> *isec : osec->members)
map.insert({isec, get_priority(isec)});
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
return get_priority(a) < get_priority(b);
return map[a] < map[b];
});
}
}
@@ -1088,37 +1116,35 @@ template <typename E>
void shuffle_sections(Context<E> &ctx) {
Timer t(ctx, "shuffle_sections");
auto is_eligible = [](OutputSection<E> &osec) {
return osec.name != ".init" && osec.name != ".fini" &&
osec.name != ".ctors" && osec.name != ".dtors" &&
osec.name != ".init_array" && osec.name != ".preinit_array" &&
osec.name != ".fini_array";
auto is_eligible = [](OutputSection<E> *osec) {
if (osec) {
std::string_view name = osec->name;
return name != ".init" && name != ".fini" &&
name != ".ctors" && name != ".dtors" &&
name != ".init_array" && name != ".preinit_array" &&
name != ".fini_array";
}
return false;
};
switch (ctx.arg.shuffle_sections) {
case SHUFFLE_SECTIONS_NONE:
unreachable();
case SHUFFLE_SECTIONS_SHUFFLE: {
u64 seed;
if (ctx.arg.shuffle_sections_seed)
seed = *ctx.arg.shuffle_sections_seed;
else
seed = ((u64)std::random_device()() << 32) | std::random_device()();
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
if (OutputSection<E> *osec = chunk->to_osec())
if (is_eligible(*osec))
shuffle(osec->members, seed + hash_string(osec->name));
if (OutputSection<E> *osec = chunk->to_osec(); is_eligible(osec)) {
u64 seed = ctx.arg.shuffle_sections_seed + hash_string(osec->name);
shuffle(osec->members, seed);
}
});
break;
}
case SHUFFLE_SECTIONS_REVERSE:
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
if (OutputSection<E> *osec = chunk->to_osec())
if (is_eligible(*osec))
std::reverse(osec->members.begin(), osec->members.end());
if (OutputSection<E> *osec = chunk->to_osec(); is_eligible(osec))
std::reverse(osec->members.begin(), osec->members.end());
});
break;
default:
unreachable();
}
}
@@ -1210,11 +1236,95 @@ void compute_section_sizes(Context<E> &ctx) {
osec->shdr.sh_addralign = std::max<u32>(osec->shdr.sh_addralign, align);
}
// Find all unresolved symbols and attach them to the most appropriate files.
// Note that even a symbol that will be reported as an undefined symbol will
// get an owner file in this function. Such symbol will be reported by
// ObjectFile<E>::scan_relocations().
template <typename E>
void claim_unresolved_symbols(Context<E> &ctx) {
Timer t(ctx, "claim_unresolved_symbols");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->claim_unresolved_symbols(ctx);
if (!file->is_alive)
return;
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
if (!esym.is_undef())
continue;
std::scoped_lock lock(sym.mu);
if (sym.file)
if (!sym.esym().is_undef() || sym.file->priority <= file->priority)
continue;
// If a symbol name is in the form of "foo@version", search for
// symbol "foo" and check if the symbol has version "version".
if (file->has_symver.get(i - file->first_global)) {
std::string_view str = file->symbol_strtab.data() + esym.st_name;
i64 pos = str.find('@');
assert(pos != str.npos);
std::string_view name = str.substr(0, pos);
std::string_view ver = str.substr(pos + 1);
Symbol<E> *sym2 = get_symbol(ctx, name);
if (sym2->file && sym2->file->is_dso && sym2->get_version() == ver) {
file->symbols[i] = sym2;
sym2->is_imported = true;
continue;
}
}
auto claim = [&](bool is_imported) {
if (sym.is_traced)
SyncOut(ctx) << "trace-symbol: " << *file << ": unresolved"
<< (esym.is_weak() ? " weak" : "")
<< " symbol " << sym;
sym.file = file;
sym.origin = 0;
sym.value = 0;
sym.sym_idx = i;
sym.is_weak = false;
sym.is_imported = is_imported;
sym.is_exported = false;
sym.ver_idx = is_imported ? 0 : ctx.default_version;
};
if (esym.is_undef_weak()) {
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
ctx.arg.z_dynamic_undefined_weak) {
// Global weak undefined symbols are promoted to dynamic symbols
// when linking a DSO unless `-z nodynamic_undefined_weak` was given.
claim(true);
} else {
// Otherwise, weak undefs are converted to absolute symbols with value 0.
claim(false);
}
continue;
}
// Traditionally, remaining undefined symbols cause a link failure
// only when we are creating an executable. Undefined symbols in
// shared objects are promoted to dynamic symbols, so that they'll
// get another chance to be resolved at run-time. You can change the
// behavior by passing `-z defs` to the linker.
//
// Even if `-z defs` is given, weak undefined symbols are still
// promoted to dynamic symbols for compatibility with other linkers.
// Some major programs, notably Firefox, depend on the behavior
// (they use this loophole to export symbols from libxul.so).
if (ctx.arg.shared && sym.visibility != STV_HIDDEN && !ctx.arg.z_defs) {
claim(true);
continue;
}
// Convert remaining undefined symbols to absolute symbols with value 0.
claim(false);
}
});
}
@@ -1247,17 +1357,12 @@ 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);
}
};
if (ctx.needs_tlsld)
ctx.got->add_tlsld(ctx);
// Assign offsets in additional tables for each dynamic symbol.
for (Symbol<E> *sym : syms) {
add_aux(sym);
sym->add_aux(ctx);
if (sym->is_imported || sym->is_exported)
ctx.dynsym->add_symbol(ctx, sym);
@@ -1272,7 +1377,7 @@ void scan_relocations(Context<E> &ctx) {
sym->is_exported = true;
// We can't use .plt.got for a canonical PLT because otherwise
// .plt.got and .got would refer each other, resulting in an
// .plt.got and .got would refer to each other, resulting in an
// infinite loop at runtime.
ctx.plt->add_symbol(ctx, sym);
} else if (sym->flags & NEEDS_PLT) {
@@ -1292,31 +1397,10 @@ void scan_relocations(Context<E> &ctx) {
ctx.got->add_tlsdesc_symbol(ctx, sym);
if (sym->flags & NEEDS_COPYREL) {
assert(sym->file->is_dso);
SharedFile<E> *file = (SharedFile<E> *)sym->file;
sym->copyrel_readonly = file->is_readonly(ctx, sym);
if (sym->copyrel_readonly)
if (((SharedFile<E> *)sym->file)->is_readonly(sym))
ctx.copyrel_relro->add_symbol(ctx, sym);
else
ctx.copyrel->add_symbol(ctx, sym);
// If a symbol needs copyrel, it is considered both imported
// and exported.
assert(sym->is_imported);
sym->is_exported = true;
// Aliases of this symbol are also copied so that they will be
// resolved to the same address at runtime.
for (Symbol<E> *alias : file->find_aliases(sym)) {
add_aux(alias);
alias->is_imported = true;
alias->is_exported = true;
alias->has_copyrel = true;
alias->value = sym->value;
alias->copyrel_readonly = sym->copyrel_readonly;
ctx.dynsym->add_symbol(ctx, alias);
}
}
if constexpr (is_ppc64v1<E>)
@@ -1326,16 +1410,46 @@ void scan_relocations(Context<E> &ctx) {
sym->flags = 0;
}
if (ctx.needs_tlsld)
ctx.got->add_tlsld(ctx);
if constexpr (is_alpha<E>)
ctx.extra.got->finalize();
if constexpr (is_mips<E>)
mips_merge_got_sections(ctx);
if (ctx.has_textrel && ctx.arg.warn_textrel)
Warn(ctx) << "creating a DT_TEXTREL in an output file";
}
// Report all undefined symbols, grouped by symbol.
template <typename E>
void report_undef_errors(Context<E> &ctx) {
constexpr i64 max_errors = 3;
for (auto &pair : ctx.undef_errors) {
std::string_view sym_name = pair.first;
std::span<std::string> errors = pair.second;
if (ctx.arg.demangle)
sym_name = demangle(sym_name);
std::stringstream ss;
ss << "undefined symbol: " << sym_name << "\n";
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
ss << errors[i];
if (errors.size() > max_errors)
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
Error(ctx) << ss.str();
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
Warn(ctx) << ss.str();
}
ctx.checkpoint();
}
template <typename E>
void create_reloc_sections(Context<E> &ctx) {
Timer t(ctx, "create_reloc_sections");
@@ -1376,6 +1490,10 @@ void copy_chunks(Context<E> &ctx) {
copy(*chunk);
});
// Undefined symbols in SHF_ALLOC sections are found by scan_relocations(),
// but those in non-SHF_ALLOC sections cannot be found until we copy section
// contents. So we need to call this function again to report possible
// undefined errors.
report_undef_errors(ctx);
if constexpr (is_arm32<E>)
@@ -1492,17 +1610,23 @@ void parse_symbol_version(Context<E> &ctx) {
verdefs[ctx.arg.version_definitions[i]] = i + VER_NDX_LAST_RESERVED + 1;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (i64 i = 0; i < file->elf_syms.size() - file->first_global; i++) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
// Match VERSION part of symbol foo@VERSION with version definitions.
// The symbols' VERSION parts are in file->symvers.
if (!file->symvers[i])
if (!file->has_symver.get(i - file->first_global))
continue;
Symbol<E> *sym = file->symbols[i + file->first_global];
Symbol<E> *sym = file->symbols[i];
if (sym->file != file)
continue;
std::string_view ver = file->symvers[i];
std::string_view ver;
if (file->is_lto_obj) {
ver = file->lto_symbol_versions[i - file->first_global];
} else {
const char *name = file->symbol_strtab.data() + file->elf_syms[i].st_name;
ver = strchr(name, '@') + 1;
}
bool is_default = false;
if (ver.starts_with('@')) {
@@ -1526,7 +1650,8 @@ void parse_symbol_version(Context<E> &ctx) {
// versioned symbol. Likewise, if `foo@VERSION` and `foo@@VERSION` are
// defined, the default one takes precedence.
Symbol<E> *sym2 = get_symbol(ctx, sym->name());
if (sym2->file == file && !file->symvers[sym2->sym_idx - file->first_global])
if (sym2->file == file &&
!file->has_symver.get(sym2->sym_idx - file->first_global))
if (sym2->ver_idx == ctx.default_version ||
(sym2->ver_idx & ~VERSYM_HIDDEN) == (sym->ver_idx & ~VERSYM_HIDDEN))
sym2->ver_idx = VER_NDX_LOCAL;
@@ -1544,7 +1669,8 @@ void compute_import_export(Context<E> &ctx) {
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN) {
if (sym->ver_idx != VER_NDX_LOCAL || !ctx.default_version_from_version_script) {
if (sym->ver_idx != VER_NDX_LOCAL ||
!ctx.default_version_from_version_script) {
std::scoped_lock lock(sym->mu);
sym->is_exported = true;
}
@@ -1701,7 +1827,7 @@ void sort_output_sections_regular(Context<E> &ctx) {
bool writable = (flags & SHF_WRITE);
bool exec = (flags & SHF_EXECINSTR);
bool tls = (flags & SHF_TLS);
bool relro = is_relro(ctx, chunk);
bool relro = chunk->is_relro;
bool is_bss = (type == SHT_NOBITS);
return (1 << 10) | (!alloc << 9) | (writable << 8) | (exec << 7) |
@@ -1840,7 +1966,7 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
auto get_flags = [&](Chunk<E> *chunk) {
i64 flags = to_phdr_flags(ctx, chunk);
if (is_relro(ctx, chunk))
if (chunk->is_relro)
return flags | RELRO;
return flags;
};
@@ -2147,17 +2273,18 @@ i64 set_osec_offsets(Context<E> &ctx) {
else
set_virtual_addresses_by_order(ctx);
i64 fileoff = set_file_offsets(ctx);
// Assigning new offsets may change the contents and the length
// of the program header, so repeat it until converge.
if (!ctx.phdr)
return fileoff;
i64 fileoff = set_file_offsets(ctx);
i64 sz = ctx.phdr->shdr.sh_size;
ctx.phdr->update_shdr(ctx);
if (sz == ctx.phdr->shdr.sh_size)
return fileoff;
if (ctx.phdr) {
i64 sz = ctx.phdr->shdr.sh_size;
ctx.phdr->update_shdr(ctx);
if (sz != ctx.phdr->shdr.sh_size)
continue;
}
return fileoff;
}
}
@@ -2332,6 +2459,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
}
// MIPS' _gp symbol.
if constexpr (is_mips<E>)
start(ctx._gp, ctx.extra.quickstart, 0x7ff0);
// __start_ and __stop_ symbols
for (Chunk<E> *chunk : sections) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
@@ -2397,7 +2528,8 @@ i64 compress_debug_sections(Context<E> &ctx) {
ctx.chunks[i] = comp;
});
ctx.shstrtab->update_shdr(ctx);
if (ctx.shstrtab)
ctx.shstrtab->update_shdr(ctx);
if (ctx.ehdr)
ctx.ehdr->update_shdr(ctx);
@@ -2509,6 +2641,7 @@ template void create_internal_file(Context<E> &);
template void apply_exclude_libs(Context<E> &);
template void create_synthetic_sections(Context<E> &);
template void resolve_symbols(Context<E> &);
template void kill_eh_frame_sections(Context<E> &);
template void resolve_section_pieces(Context<E> &);
template void convert_common_symbols(Context<E> &);
template void compute_merged_section_sizes(Context<E> &);
@@ -2516,7 +2649,6 @@ template void create_output_sections(Context<E> &);
template void add_synthetic_symbols(Context<E> &);
template void check_cet_errors(Context<E> &);
template void print_dependencies(Context<E> &);
template void print_dependencies_full(Context<E> &);
template void write_repro_file(Context<E> &);
template void check_duplicate_symbols(Context<E> &);
template void check_symbol_types(Context<E> &);
@@ -2527,6 +2659,7 @@ template void compute_section_sizes(Context<E> &);
template void sort_output_sections(Context<E> &);
template void claim_unresolved_symbols(Context<E> &);
template void scan_relocations(Context<E> &);
template void report_undef_errors(Context<E> &);
template void create_reloc_sections(Context<E> &);
template void copy_chunks(Context<E> &);
template void construct_relr(Context<E> &);
+3
View File
@@ -54,6 +54,9 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
ctx.symtab = push(new SymtabSection<E>);
ctx.shstrtab = push(new ShstrtabSection<E>);
ctx.note_property = push(new NotePropertySection<E>);
if constexpr (is_riscv<E>)
ctx.extra.riscv_attributes = push(new RiscvAttributesSection<E>);
}
// Create SHT_GROUP (i.e. comdat group) sections. We uniquify comdat
+104 -54
View File
@@ -20,6 +20,9 @@
// 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 || \
MOLD_LOONGARCH64 || MOLD_LOONGARCH32
#include "mold.h"
#include <tbb/parallel_for.h>
@@ -27,33 +30,38 @@
namespace mold::elf {
// Branch reach in bytes.
//
// ARM64's branch has 26 bits immediate, and it's scaled by 4 because all
// instructions are 4 bytes aligned, so it's effectively 28 bits long.
// That means the range is [-2^27, 2^27).
//
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
// aligned to 2, so it's effectively 25 bits. ARM32's non-Thumb branches
// have twice longer range than its Thumb counterparts, but we
// conservatively use the Thumb's limitation.
//
// PPC's branch has 24 bits immediate, and the instructions are aligned
// to 4, therefore 26.
//
// Here is the summary of branch instructions reaches:
//
// ARM64: PC ± 128 MiB
// ARM32: PC ± 16 MiB
// PPC: PC ± 32 MiB
// Returns a branch reach in bytes for a given target.
template <typename E>
static constexpr i64 max_distance =
1LL << (is_arm64<E> ? 27 : is_arm32<E> ? 24 : 25);
static consteval i64 max_distance() {
// ARM64's branch has 26 bits immediate. The immediate is padded with
// implicit two-bit zeros because all instructions are 4 bytes aligned
// and therefore the least two bits are always zero. So the branch
// operand is effectively 28 bits long. That means the branch range is
// [-2^27, 2^27) or PC ± 128 MiB.
//
// LoongArch's BR instruction also takes a 26 bit immediate.
if (is_arm64<E> || is_loongarch<E>)
return 1 << 27;
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
// aligned to 2, so it's effectively 25 bits. It's [-2^24, 2^24) or PC ±
// 16 MiB.
//
// ARM32's non-Thumb branches have twice longer range than its Thumb
// counterparts, but we conservatively use the Thumb's limitation.
if (is_arm32<E>)
return 1 << 24;
// PPC's branch has 24 bits immediate, and the instructions are aligned
// to 4, therefore the reach is [-2^25, 2^25) or PC ± 32 MiB.
assert(is_ppc<E>);
return 1 << 25;
}
// We create thunks for each 12.8/1.6/3.2 MiB code block for
// ARM64/ARM32/PPC, respectively.
template <typename E>
static constexpr i64 batch_size = max_distance<E> / 10;
static constexpr i64 batch_size = max_distance<E>() / 10;
// We assume that a single thunk group is smaller than 100 KiB.
static constexpr i64 max_thunk_size = 102400;
@@ -67,12 +75,15 @@ static bool needs_thunk_rel(const ElfRel<E> &r) {
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
} else if constexpr (is_arm32<E>) {
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL;
ty == R_ARM_CALL || ty == R_ARM_THM_CALL ||
ty == R_ARM_PLT32;
} else if constexpr (is_ppc32<E>) {
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
} else if constexpr (is_ppc64<E>) {
return ty == R_PPC64_REL24 || ty == R_PPC64_REL24_NOTOC;
} else {
static_assert(is_ppc64<E>);
return ty == R_PPC64_REL24;
static_assert(is_loongarch<E>);
return ty == R_LARCH_B26;
}
}
@@ -102,7 +113,19 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
if constexpr (is_arm32<E>) {
bool is_thumb = sym.get_addr(ctx) & 1;
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
(rel.r_type == R_ARM_PLT32 && is_thumb))
return false;
}
// PowerPC before Power9 lacks PC-relative load/store instructions.
// Functions compiled for Power9 or earlier assume that r2 points to
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
// We need to a thunk to recompute r2 for interworking.
if constexpr (is_ppc64v2<E>) {
if (rel.r_type == R_PPC64_REL24 && !sym.esym().preserves_r2())
return false;
if (rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().uses_toc())
return false;
}
@@ -112,7 +135,7 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -max_distance<E> <= val && val < max_distance<E>;
return -max_distance<E>() <= val && val < max_distance<E>();
}
template <typename E>
@@ -127,7 +150,7 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
// Scan relocations to collect symbols that need thunks.
template <typename E>
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
RangeExtensionThunk<E> &thunk) {
RangeExtensionThunk<E> &thunk, i64 thunk_idx) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
range_extn.resize(rels.size());
@@ -156,7 +179,7 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
// Otherwise, add the symbol to the current thunk if it's not
// added already.
range_extn[i].thunk_idx = thunk.thunk_idx;
range_extn[i].thunk_idx = thunk_idx;
range_extn[i].sym_idx = -1;
if (sym.flags.exchange(-1) == 0) {
@@ -184,17 +207,36 @@ 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;
// The smallest thunk index that is reachable from the current batch.
i64 t = 0;
while (b < m.size()) {
// Move D foward as far as we can jump from B to anywhere in a thunk after D.
// Move D foward as far as we can jump from B to anywhere in a thunk at D.
while (d < m.size() &&
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
m[b]->offset + max_distance<E>) {
m[b]->offset + max_distance<E>()) {
offset = align_to(offset, 1 << m[d]->p2align);
m[d]->offset = offset;
offset += m[d]->sh_size;
@@ -211,48 +253,53 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
// Move A forward so that A is reachable from C.
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
while (a < osec.thunks.size() &&
osec.thunks[a]->offset + max_distance<E> < c_offset)
reset_thunk(*osec.thunks[a++]);
while (a < m.size() && m[a]->offset + max_distance<E>() < c_offset)
a++;
// Erase references to out-of-range thunks.
while (t < osec.thunks.size() &&
osec.thunks[t]->offset < m[a]->offset)
reset_thunk(*osec.thunks[t++]);
// Create a thunk for input sections between B and C and place it at D.
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
thunk.thunk_idx = osec.thunks.size() - 1;
offset = align_to(offset, thunk.alignment);
thunk.offset = offset;
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
i64 thunk_idx = osec.thunks.size();
RangeExtensionThunk<E> *thunk = new RangeExtensionThunk<E>(osec, offset);
osec.thunks.emplace_back(thunk);
// Scan relocations between B and C to collect symbols that need thunks.
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
scan_rels(ctx, *isec, thunk);
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
[&](InputSection<E> *isec) {
scan_rels(ctx, *isec, *thunk, thunk_idx);
});
// Now that we know the number of symbols in the thunk, we can compute
// its size.
assert(thunk.size() < max_thunk_size);
offset += thunk.size();
assert(thunk->size() < max_thunk_size);
offset += thunk->size();
// Sort symbols added to the thunk to make the output deterministic.
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
sort(thunk->symbols, [](Symbol<E> *a, Symbol<E> *b) {
return std::tuple{a->file->priority, a->sym_idx} <
std::tuple{b->file->priority, b->sym_idx};
});
// Assign offsets within the thunk to the symbols.
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
sym->extra.thunk_idx = thunk.thunk_idx;
sym->extra.thunk_sym_idx = i++;
for (i64 i = 0; i < thunk->symbols.size(); i++) {
Symbol<E> &sym = *thunk->symbols[i];
sym.extra.thunk_idx = thunk_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_idx)
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
});
@@ -260,15 +307,18 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
b = c;
}
while (a < osec.thunks.size())
reset_thunk(*osec.thunks[a++]);
while (t < osec.thunks.size())
reset_thunk(*osec.thunks[t++]);
osec.shdr.sh_size = offset;
}
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
using E = MOLD_TARGET;
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
#endif
} // namespace mold::elf
#endif
+212
View File
@@ -0,0 +1,212 @@
// This file contains helper functions for thread-local storage (TLS).
// TLS is probably the most obscure feature the linker has to support,
// so I'll explain it in detail in this comment.
//
// TLS is a per-thread storage. Thread-local variables (TLVs) are in a TLS
// so that each thread has its own set of thread-local variables. Taking
// an address of a TLV returns a unique value for each thread. For example,
// `&foo` for the following code returns different pointer values for
// different threads.
//
// thread_local int foo;
//
// TLV is a relatively new feature. C for example didn't provide the
// official support for it through the keyword `thread_local` until C11.
// TLV needs a coordination between the compiler, the linker and the
// runtime to work correctly.
//
// An ELF exectuable or a shared library using TLV contains a "TLS template
// image" in the PT_TLS segment. For each newly created thread including the
// initial one, the runtime allocates a contiguous memory for an executable
// and its depending shared libraries and copies template images there. That
// per-thread memory is called the "initial TLS block". After allocating and
// initializing the initial TLS block, the runtime sets a register to refer
// to the initial 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 initial 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 known only at process startup
// time, we create a GOT entry to store its TP-relative address. We
// then 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 may not be allocated next to the
// initial TLS block, so we can 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> || is_loongarch<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
+11 -2
View File
@@ -1,6 +1,6 @@
#!/bin/bash
#!/bin/sh
set -e
source /etc/os-release
. /etc/os-release
set -x
@@ -40,6 +40,15 @@ 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
;;
alpine-*)
apk update
apk add bash make openssl-dev linux-headers cmake zlib-dev gcc g++
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
exit 1
+26 -3
View File
@@ -1,10 +1,16 @@
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
function(add_target TRIPLE)
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
if(${HOST} MATCHES "arm.*")
if(${HOST} MATCHES "amd64")
set(HOST x86_64)
elseif(${HOST} MATCHES "arm.*")
set(HOST arm)
elseif(${HOST} STREQUAL "ppc64")
set(HOST powerpc64)
elseif(${HOST} STREQUAL "ppc64le")
set(HOST powerpc64le)
endif()
if(${TRIPLE} MATCHES "${HOST}-.*")
@@ -54,10 +60,27 @@ add_target(powerpc64-linux-gnu)
add_target(powerpc64le-linux-gnu)
add_target(sparc64-linux-gnu)
add_target(s390x-linux-gnu)
add_target(m68k-linux-gnu)
add_target(sh4-linux-gnu)
add_target(alpha-linux-gnu)
add_target(mips64-linux-gnuabi64)
add_target(mips64el-linux-gnuabi64)
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
if(MOLD_ENABLE_QEMU_TESTS_RV32)
add_target(riscv32-linux-gnu)
endif()
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
add_target(powerpc64le_power10-linux-gnu)
endif()
option(MOLD_ENABLE_QEMU_TESTS_M68K "Enable tests for m68k" OFF)
if(MOLD_ENABLE_QEMU_TESTS_M68K)
add_target(m68k-linux-gnu)
endif()
option(MOLD_ENABLE_QEMU_TESTS_LOONGARCH "Enable tests for LoongArch" OFF)
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH)
add_target(loongarch64-linux-gnu)
endif()
+3
View File
@@ -6,6 +6,9 @@
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = s390x ] && skip
[ $MACHINE = alpha ] && skip
[ $MACHINE = mips64el ] && skip
[ $MACHINE = mips64 ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
.globl foo
@@ -22,6 +22,5 @@ $CC -B. -o $t/exe $t/a.o \
$OBJDUMP -dr $t/exe | grep -F -A7 '<fn1$thunk>:' > $t/log
grep -Eq 'mov\s+ip, pc' $t/log
grep -Eq 'bx\s+ip' $t/log
grep -Eq 'bx\s+pc' $t/log
grep -Eq 'add\s+ip, ip, pc' $t/log
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
int fn1() { return 42; }
EOF
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc -
int fn1();
int fn2() { return fn1(); }
EOF
cat <<EOF | $CC -o $t/a.o -c -xc -
int fn2();
int main() { fn2(); }
EOF
$CC -B. -o $t/exe1 $t/a.o -L$t -Wl,--as-needed -lbar -Wl,--allow-shlib-undefined
readelf -W --dynamic $t/exe1 > $t/log1
! grep -q libfoo $t/log1 || false
$CC -B. -o $t/exe2 $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
readelf -W --dynamic $t/exe2 > $t/log2
grep -q libfoo $t/log2
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
int foo() {
return 0;
}
EOF
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
__attribute__((weak)) int foo();
int bar() {
if (foo) return foo();
return 0;
}
EOF
cat <<EOF | $CC -xc -c -o $t/c.o -
int bar();
int main() {
return bar();
}
EOF
$CC -B. -shared -o $t/libfoo.so $t/a.o
$CC -B. -shared -o $t/libbar.so $t/b.o
$CC -B. -o $t/exe $t/c.o -L$t -Wl,--as-needed -lfoo -lbar
readelf --dynamic $t/exe > $t/log
! grep libfoo.so $t/log || false
grep -q libbar.so $t/log
+12 -11
View File
@@ -5,26 +5,27 @@ cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
__attribute__((weak)) int fn1();
int main() {
fn1();
if (fn1)
fn1();
}
EOF
cat <<EOF | $CC -o $t/b.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
int fn1() { return 42; }
EOF
cat <<EOF | $CC -o $t/c.so -shared -fPIC -Wl,-soname,libbar.so -xc -
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc -
int fn2() { return 42; }
EOF
$CC -B. -o $t/exe $t/a.o -Wl,-no-as-needed $t/b.so $t/c.so
$CC -o $t/exe1 $t/a.o -Wl,-no-as-needed -L$t -lbar -lfoo
readelf --dynamic $t/exe > $t/readelf
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
grep -Fq 'Shared library: [libbar.so]' $t/readelf
readelf --dynamic $t/exe1 > $t/log1
grep -Fq 'Shared library: [libfoo.so]' $t/log1
grep -Fq 'Shared library: [libbar.so]' $t/log1
$CC -B. -o $t/exe $t/a.o -Wl,-as-needed $t/b.so $t/c.so
$CC -o $t/exe2 $t/a.o -Wl,-as-needed -L$t -lbar -lfoo
readelf --dynamic $t/exe > $t/readelf
! grep -Fq 'Shared library: [libfoo.so]' $t/readelf || false
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
readelf --dynamic $t/exe2 > $t/log2
! grep -Fq 'Shared library: [libfoo.so]' $t/log2 || false
! grep -Fq 'Shared library: [libbar.so]' $t/log2 || false
+9 -10
View File
@@ -1,22 +1,22 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
cat <<EOF | $CC -shared -fPIC -o $t/libfoo.so -Wl,--soname,libfoo.so -xc -
int foo() { return 3; }
EOF
cat <<EOF | $CC -shared -fPIC -o $t/b.so -xc -
cat <<EOF | $CC -shared -fPIC -o $t/libbar.so -Wl,--soname,libbar.so -xc -
int bar() { return 3; }
EOF
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
int foo();
int baz() { return foo(); }
EOF
$CC -shared -o $t/c.so $t/c.o $t/a.so
$CC -B. -shared -o $t/libbaz.so -Wl,--soname,libbaz.so -L$t $t/a.o -lfoo
cat <<EOF | $CC -c -o $t/d.o -xc -
cat <<EOF | $CC -c -o $t/b.o -xc -
#include <stdio.h>
int baz();
int main() {
@@ -24,10 +24,9 @@ int main() {
}
EOF
$CC -B. -o $t/exe $t/d.o -Wl,--as-needed $t/c.so $t/b.so $t/a.so
$QEMU $t/exe | grep -q '^3$'
$CC -B. -o $t/exe $t/b.o -L$t -Wl,--as-needed -lbaz -lbar -lfoo
readelf --dynamic $t/exe > $t/log
! grep -q /a.so $t/log || false
grep -q /c.so $t/log || false
! grep -q /b.so $t/log || false
grep -q libbaz $t/log || false
! grep -q libbar $t/log || false
grep -q libfoo $t/log || false
+27 -17
View File
@@ -16,23 +16,10 @@ canonical_name() {
HOST=$(canonical_name $(uname -m))
if [ -n "$TRIPLE" ]; then
MACHINE=$(canonical_name $(echo $TRIPLE | sed 's/-.*//'))
else
MACHINE=$HOST
fi
# Set tool names
if [ -n "$TRIPLE" ]; then
CC="${TEST_CC:-$TRIPLE-gcc}"
CXX="${TEST_CXX:-$TRIPLE-g++}"
GCC="${TEST_GCC:-$TRIPLE-gcc}"
GXX="${TEST_GXX:-$TRIPLE-g++}"
OBJDUMP="$TRIPLE-objdump"
OBJCOPY="$TRIPLE-objcopy"
STRIP="$TRIPLE-strip"
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
else
if [ "$TRIPLE" = "" ]; then
MACHINE=$HOST
TESTDIR=out/test/elf/$HOST
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
@@ -41,6 +28,29 @@ else
OBJCOPY=objcopy
STRIP=strip
QEMU=
elif [ "$TRIPLE" = powerpc64le_power10-linux-gnu ]; then
TRIPLE=powerpc64le-linux-gnu
MACHINE=ppc64le
TESTDIR=out/test/elf/ppc64le-power10
CC="${TEST_CC:-$TRIPLE-gcc} -mcpu=power10"
CXX="${TEST_CXX:-$TRIPLE-g++} -mcpu=power10"
GCC="${TEST_GCC:-$TRIPLE-gcc} -mcpu=power10"
GXX="${TEST_GXX:-$TRIPLE-g++} -mcpu=power10"
OBJDUMP="$TRIPLE-objdump"
OBJCOPY="$TRIPLE-objcopy"
STRIP="$TRIPLE-strip"
QEMU="qemu-ppc64le -L /usr/$TRIPLE -cpu power10"
else
MACHINE=$(canonical_name $(echo $TRIPLE | sed 's/-.*//'))
TESTDIR=out/test/elf/$MACHINE
CC="${TEST_CC:-$TRIPLE-gcc}"
CXX="${TEST_CXX:-$TRIPLE-g++}"
GCC="${TEST_GCC:-$TRIPLE-gcc}"
GXX="${TEST_GXX:-$TRIPLE-g++}"
OBJDUMP="$TRIPLE-objdump"
OBJCOPY="$TRIPLE-objcopy"
STRIP="$TRIPLE-strip"
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
fi
# Common functions
@@ -77,5 +87,5 @@ trap on_exit EXIT
# Print out the startup message
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
t=$TESTDIR/$testname
mkdir -p $t
+1
View File
@@ -4,6 +4,7 @@
# arm-linux-gnueabihf-objcopy crashes on x86-64
[ $MACHINE = arm ] && skip
[ $MACHINE = riscv32 ] && skip
[[ $MACHINE = mips* ]] && skip
command -v zstdcat >& /dev/null || skip
+2
View File
@@ -4,6 +4,8 @@
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -fPIC -shared -o $t/a.so -xc -
__attribute__((aligned(32))) int foo = 5;
+2
View File
@@ -4,6 +4,8 @@
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
extern int foo;
+5
View File
@@ -2,6 +2,11 @@
. $(dirname $0)/common.inc
[ $MACHINE = riscv64 -o $MACHINE = riscv32 ] && skip
[[ $MACHINE = loongarch* ]] && 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
@@ -0,0 +1,26 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = sh4 ] && skip
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -g -ffunction-sections -
#include <iostream>
struct Foo {
Foo() { std::cout << "Hello world\n"; }
};
Foo x;
EOF
cat <<EOF | $CXX -o $t/b.o -c -fPIC -xc++ -g -ffunction-sections -
#include <iostream>
struct Foo {
Foo() { std::cout << "Hello world\n"; }
};
Foo y;
int main() {}
EOF
$CXX -B. -o $t/exe $t/a.o $t/b.o -Wl,-emit-relocs
$QEMU $t/exe | grep -q 'Hello world'
+16 -23
View File
@@ -3,8 +3,11 @@
[ $MACHINE = sh4 ] && skip
static=1
test_cflags -static || static=0
static=
test_cflags -static && static=-static
# I don't know why, but we need -pthread on m68k
static="$static -pthread"
cat <<EOF > $t/a.cc
int main() {
@@ -20,15 +23,11 @@ EOF
$CXX -c -o $t/b.o $t/a.cc -fPIC
$CXX -c -o $t/c.o $t/a.cc -fno-PIC
if [ $static = 1 ]; then
$CXX -B. -o $t/exe1 $t/b.o -static
$QEMU $t/exe1
fi
$CXX -B. -o $t/exe1 $t/b.o $static
$QEMU $t/exe1
if [ $static = 1 ]; then
$CXX -B. -o $t/exe2 $t/c.o -static
$QEMU $t/exe2
fi
$CXX -B. -o $t/exe2 $t/c.o -no-pie $static
$QEMU $t/exe2
$CXX -B. -o $t/exe3 $t/b.o -pie
$QEMU $t/exe3
@@ -39,18 +38,14 @@ $QEMU $t/exe4
$CXX -B. -o $t/exe5 $t/b.o -pie -Wl,--gc-sections
$QEMU $t/exe5
if [ $static = 1 ]; then
$CXX -B. -o $t/exe6 $t/c.o -static -Wl,--gc-sections
$QEMU $t/exe6
fi
$CXX -B. -o $t/exe6 $t/c.o -no-pie $static -Wl,--gc-sections
$QEMU $t/exe6
if [ $MACHINE = x86_64 ]; then
$CXX -c -o $t/d.o $t/a.cc -mcmodel=large -fPIC
if [ $static = 1 ]; then
$CXX -B. -o $t/exe7 $t/d.o -static
$QEMU $t/exe7
fi
$CXX -B. -o $t/exe7 $t/d.o $static
$QEMU $t/exe7
$CXX -B. -o $t/exe8 $t/d.o -pie
$QEMU $t/exe8
@@ -59,17 +54,15 @@ fi
if [ $MACHINE = x86_64 -o $MACHINE = aarch64 ]; then
$CXX -c -o $t/e.o $t/a.cc -mcmodel=large -fno-PIC
if [ $static = 1 ]; then
$CXX -B. -o $t/exe9 $t/e.o -static
$QEMU $t/exe9
fi
$CXX -B. -o $t/exe9 $t/e.o -no-pie $static
$QEMU $t/exe9
$CXX -B. -o $t/exe10 $t/e.o -no-pie
$QEMU $t/exe10
fi
# riscv64-linux-gnu-strip crashes for some reason
if [ $MACHINE != riscv32 ]; then
if [ $MACHINE != riscv32 ] && [[ $MACHINE != mips* ]]; then
$CXX -B. -o $t/exe11 $t/b.o -pie
$STRIP $t/exe11
$QEMU $t/exe11
+3
View File
@@ -4,6 +4,9 @@
# .text is writable on sparc, which is not compatible with --execute-only
[ $MACHINE = sparc64 ] && skip
# GCC emits data to .text for PPC64, so PPC64 is not compatible with -execute-only
[ $MACHINE = ppc64 ] && skip
cat <<EOF | $CC -o $t/a.o -c -xc -
#include <stdio.h>
int main() {
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = $HOST ] || skip
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
command -v gdb >& /dev/null || skip
test_cflags -gdwarf-5 -g || skip
cat <<EOF > $t/a.c
void fn3();
static void fn2() {
fn3();
}
void fn1() {
fn2();
}
EOF
cat <<EOF > $t/b.c
void fn5();
static void fn4() {
fn5();
}
void fn3() {
fn4();
}
EOF
cat <<EOF > $t/c.c
void fn7();
static void fn6() {
fn7();
}
void fn5() {
fn6();
}
EOF
cat <<EOF > $t/d.c
#include <stdio.h>
void trap() {}
static void fn8() {
printf("Hello world\n");
trap();
}
void fn7() {
fn8();
}
EOF
$CC -c -o $t/a.o $t/a.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gsplit-dwarf
$CC -c -o $t/b.o $t/b.c -fPIC -g -ggnu-pubnames -gdwarf-4 -gsplit-dwarf
$CC -c -o $t/c.o $t/c.c -fPIC -g -ggnu-pubnames -gdwarf-5
$CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gsplit-dwarf
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
readelf -WS $t/e.so 2> /dev/null | grep -Fq .gdb_index
cat <<EOF | $CC -c -o $t/f.o -fPIC -g -ggnu-pubnames -gdwarf-5 -gsplit-dwarf -xc - -gz
void fn1();
int main() {
fn1();
}
EOF
$CC -B. -o $t/exe $t/e.so $t/f.o -Wl,--gdb-index
readelf -WS $t/exe 2> /dev/null | grep -Fq .gdb_index
$QEMU $t/exe | grep -q 'Hello world'
+1
View File
@@ -2,6 +2,7 @@
. $(dirname $0)/common.inc
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
# glibc 2.22 or prior have a bug that ld-linux.so.2 crashes on dlopen()
# if .rela.dyn and .rela.plt are not contiguous in a given DSO.
+9
View File
@@ -1,6 +1,9 @@
#!/bin/bash
. $(dirname $0)/common.inc
# MIPS ABIs are not compatible with .gnu.hash
[[ $MACHINE = mips* ]] && skip
cat <<EOF | $CC -o $t/a.o -c -xc -
void foo() {}
EOF
@@ -9,3 +12,9 @@ EOF
readelf -WS $t/b.so | grep -Fq ' .hash'
readelf -WS $t/b.so | grep -Fq ' .gnu.hash'
./mold -shared -o $t/c.so $t/a.o --hash-style=both --hash-style=none
readelf -WS $t/c.so > $t/log
! grep -Fq ' .hash' $t/log || false
! grep -Fq ' .gnu.hash' $t/log || false
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_ifunc || skip
cat <<EOF | $CC -fno-PIE -o $t/a.o -c -xc -
#include <dlfcn.h>
#include <stdio.h>
typedef void Func();
void foo(void);
int main() {
void *handle = dlopen(NULL, RTLD_NOW);
Func *p = dlsym(handle, "foo");
foo();
p();
printf("%p %p\n", foo, p);
}
EOF
cat <<EOF | $CC -fPIC -o $t/b.o -c -xc -
#include <stdio.h>
__attribute__((ifunc("resolve_foo")))
void foo(void);
static void real_foo(void) {
printf("foo ");
}
typedef void Func();
static Func *resolve_foo(void) {
return real_foo;
}
EOF
$CC -B. -o $t/c.so $t/b.o -shared
$CC -B. -o $t/exe $t/a.o $t/c.so -no-pie -ldl
$QEMU $t/exe | grep -q 'foo foo'
+5 -2
View File
@@ -11,7 +11,7 @@ int main() {
}
EOF
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
cat <<EOF | $CC -fPIC -o $t/b.o -c -xc -
#include <stdio.h>
__attribute__((ifunc("resolve_foobar")))
@@ -28,5 +28,8 @@ static Func *resolve_foobar(void) {
}
EOF
$CC -B. -o $t/exe $t/a.o $t/b.so
$CC -B. -o $t/c.so $t/b.o -shared
readelf -W --dyn-syms $t/c.so | grep -Eq '(IFUNC|<OS specific>: 10).*foobar'
$CC -B. -o $t/exe $t/a.o $t/c.so
$QEMU $t/exe | grep -q 'Hello world'
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_ifunc || skip
cat <<EOF | $CC -c -fPIC -o $t/a.o -c -xc -
typedef int Fn();
int foo() __attribute__((ifunc("resolve_foo")));
int real_foo() { return 3; }
Fn *resolve_foo(void) {
return real_foo;
}
EOF
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
typedef int Fn();
int foo();
Fn *get_foo() { return foo; }
EOF
cat <<EOF | $CC -c -fPIC -o $t/c.o -xc -
#include <stdio.h>
typedef int Fn();
Fn *get_foo();
int main() {
Fn *f = get_foo();
printf("%d\n", f());
}
EOF
$CC -B. -o $t/exe $t/a.o $t/b.o $t/c.o
$QEMU $t/exe | grep -q '^3$'
+1
View File
@@ -3,6 +3,7 @@
# ARM32's strip command crashes on the output of this test for some reason.
[ $MACHINE = arm ] && skip
[[ $MACHINE = mips* ]] && skip
strip=$STRIP
command -v $strip >& /dev/null || skip
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
#include <stdio.h>
int main() {
printf("Hello world\n");
}
EOF
$CC -B. -shared -o $t/b.so $t/a.o
$CC -o $t/c.o -c -xc /dev/null -fPIC
$CC -B. -o $t/exe1 $t/c.o $t/b.so -pie
$QEMU $t/exe1 | grep -q 'Hello world'
$CC -o $t/c.o -c -xc /dev/null -fno-PIC
$CC -B. -o $t/exe2 $t/c.o $t/b.so -no-pie
$QEMU $t/exe2 | grep -q 'Hello world'
+2 -2
View File
@@ -1,8 +1,8 @@
#!/bin/bash
. $(dirname $0)/common.inc
seq 1 100000 | sed 's/.*/int x\0 __attribute__((section(".data.\0")));/g' | \
$CC -c -xc -o $t/a.o -
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' | \
$CC -c -xassembler -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o -
#include <stdio.h>
+11 -4
View File
@@ -4,9 +4,16 @@
# OneTBB isn't tsan-clean
nm mold | grep -q '__tsan_init' && skip
seq 1 100000 | sed 's/.*/int x\0 __attribute__((section(".data.\0")));/g' | \
$CC -c -xc -o $t/a.o -
echo 'foo = 0x1000' > $t/a.s
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.globl x\0\nx\0: .word 0\n/g' >> $t/a.s
$CC -c -xassembler -o $t/a.o $t/a.s
./mold --relocatable -o $t/b.o $t/a.o
readelf -WS $t/b.o | grep -Fq .data.100000
readelf -Ws $t/b.o | grep -Fq 'OBJECT GLOBAL DEFAULT 100000'
readelf -WS $t/b.o > $t/log1
grep -Fq .data.100000 $t/log1
readelf -Ws $t/b.o > $t/log2
grep -Fq 'GLOBAL DEFAULT 100000' $t/log2
grep -Fq 'ABS foo' $t/log2
! grep -Fq 'ABS x68966' $t/log2 || false
+2
View File
@@ -8,6 +8,8 @@
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = sh4 ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -shared -o $t/a.so -xc -
int foo = 3;
+4
View File
@@ -2,6 +2,10 @@
. $(dirname $0)/common.inc
[ $MACHINE = ppc64 ] && skip
[[ $MACHINE = mips* ]] && skip
# BusyBox's grep can't handle capture groups (e.g. \1, \2 ...)
grep --version 2>&1 | grep BusyBox && skip
cat <<EOF | $CC -o $t/a.o -c -xc -
#include <stdio.h>
+1 -3
View File
@@ -10,7 +10,5 @@ void foo();
int main() { foo(); }
EOF
! $CC -B. -o $t/exe $t/a.o $t/b.o \
-Wl,--print-dependencies=full > $t/log 2> /dev/null
! $CC -B. -o $t/exe $t/a.o $t/b.o -Wl,--print-dependencies > $t/log 2> /dev/null
grep -q 'b\.o.*a\.o.*foo$' $t/log
-1
View File
@@ -3,7 +3,6 @@
# Skip if 32 bits as we use very large addresses in this test.
[ $MACHINE = i386 ] && skip
[ $MACHINE = arm32 ] && skip
[ $MACHINE = riscv32 ] && skip
# It looks like SPARC's runtime can't handle PLT if it's too far from GOT.
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
#include <stdio.h>
extern char *msg;
void hello() { printf("%s\n", msg); }
EOF
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
char *msg = "Hello world";
void hello();
int main() { hello(); }
EOF
$CC -B. -o $t/exe $t/a.o $t/b.o
$QEMU $t/exe | grep -q 'Hello world'
Loaded 100 of 365 files, more files were not shown because too many files have changed in this diff. Show more