Compare commits

...
459 Commits
Author SHA1 Message Date
Rui Ueyama 79b3fc4b6a Bump mold version to 2.2.0 2023-09-24 12:28:19 +09:00
Rui Ueyama 9e09e74db3 Refactor 2023-09-20 16:58:37 +09:00
Rui Ueyama 9ee10ba4bd Fix test
Fixes https://github.com/rui314/mold/issues/1107
2023-09-18 15:03:41 +09:00
Rui Ueyama fe7c8c26b7 Fix for big-endian hosts 2023-09-17 15:28:35 +09:00
Rui Ueyama 23576b6e48 Refactor 2023-09-17 11:16:39 +09:00
Rui Ueyama cfa9e404d1 Keep the entry symbol when doing LTO
Fixes https://github.com/rui314/mold/issues/1105
2023-09-16 14:43:04 +09:00
Rui Ueyama 641360b0aa Update comments 2023-09-16 14:28:39 +09:00
Rui Ueyama 4ccc9eb89a Fix RISC-V's shrink_section()
r_delta is not available when we are shrinking sections.

Address the post review comment
https://github.com/rui314/mold/commit/742cdeec666ea015c26af4f30d01f2fb08fcb7b7#r127431639
2023-09-16 14:01:57 +09:00
Rui Ueyama a801dde186 Use ld for RV64 and lw for RV32 2023-09-14 14:00:55 +09:00
Rui Ueyama e2d53f26e2 Refactor 2023-09-14 14:00:54 +09:00
Rui Ueyama 742cdeec66 Refactor 2023-09-14 14:00:53 +09:00
Rui Ueyama c2116aa7ca Merge pull request #1041 from ishitatsuyuki/rv-tlsdesc
RISC-V TLSDESC support
2023-09-14 12:54:11 +09:00
Tatsuyuki Ishi fed950837a [ELF][RISCV] Add TLSDESC relaxation.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-09-14 02:18:15 +09:00
Tatsuyuki Ishi 508890d33d [ELF][RISCV] Fix finding paired relocations with zero distance.
For example, when the leader's instruction is removed and a follower is right
next to it, the follower's relocation can point to the follower's address
itself. In this case the leader's relocation may lie before or after the
follower, so just search for both.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-09-13 13:38:46 +09:00
Tatsuyuki Ishi 141556db44 [ELF] Support RISC-V TLSDESC relocations.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-09-13 13:38:42 +09:00
Tatsuyuki Ishi 8589403fe0 [ELF][RISCV] Move the list of "leader" paired reloc into a helper.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-09-13 13:37:15 +09:00
Tatsuyuki Ishi cd2224742f [ELF] Support RISC-V TLSDESC GOT generation.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-09-13 13:36:55 +09:00
Tatsuyuki Ishi 0b1d11ed01 [ELF] Add RISC-V TLSDESC relocation constants.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-09-13 13:36:16 +09:00
Rui Ueyama 22443b1490 Reduce the number of atomic memory operations 2023-09-08 18:48:25 +09:00
Rui Ueyama a3a53e9a0c Refactor 2023-09-08 17:44:24 +09:00
Rui Ueyama dd3d045396 Discard local symbols in discarded sections 2023-09-06 17:16:22 +09:00
Rui Ueyama 673d790372 Attempt to fix CI 2023-09-06 14:48:10 +09:00
Rui Ueyama 6cac0aba6a Fix CI 2023-09-06 13:59:48 +09:00
Rui Ueyama 1a13c5076e Avoid cache-line false sharing
This change improves the performance of our concurrent hash map on
a multicore machine.
2023-09-06 13:26:49 +09:00
Rui Ueyama c504911bf6 Add a missing #include 2023-09-05 15:39:44 +09:00
Rui Ueyama db5fa8a8cd Remove incomplete MIPS support
I made a fair amount of effort to try to support MIPS, but it turned out
that it is much harder than expected. I punted it instead of making
further efforts.

The problem is the MIPS ABI is hostile to the linker in the modern
environment. MIPS object files are still compiled for the small code
model in which GOT entries and data in the small data area are expected
to be accessible with a single machine instruction with a 16-bit
displacement. In other words, .got/.sdata/.sbss are expected to be
smaller than 64 KiB. This might have been a reasonable assumption in the
90s, but it's not suitable for modern applications that can be 1000x
larger than the binaries in the 90s.

MIPS requires the linker to implement tons of workarounds for its legacy
ABI assumptions. Our incomplete MIPS support can build binaries that
pass all our unit tests. However, it needed more effort to support
real-world programs that are larger than our test cases.

At this point, I don't think it is productive to implement workarounds
for the old ABI that is stuck in the 90s. It is honestly annoying to
think about workarounds for the code that is intentionally compiled to
be hostile to the linker. The situation is unfortunate, but if the
Open Source community is still serious about MIPS, they should improve
the ABI and the compiler instead of asking us to implement the legacy
ABI.

Closes https://github.com/rui314/mold/issues/1040
2023-09-04 13:12:03 +09:00
Rui Ueyama c03400303f Fix --relocatable
Fixes https://github.com/rui314/mold/issues/1096
2023-09-03 20:15:32 +09:00
Rui Ueyama 7f91a134ef Refactor 2023-09-03 12:31:22 +09:00
Rui Ueyama d4891d5ff1 Improve PC-relative relocation relaxation
Absolute symbols are rare, but their PC-relative addresses are known
at link-time if we are creating a position-dependent executable.
2023-09-03 10:50:52 +09:00
Rui Ueyama bdc2336265 Refactor 2023-09-03 10:50:52 +09:00
Rui Ueyama 2a0f65084e Refactor 2023-09-01 17:24:32 +09:00
Rui Ueyama db146cfc37 Fix i386 TLSDESC 2023-09-01 15:30:38 +09:00
Rui Ueyama 49f38828d1 Add a missing break
This error resulted in creating an unnecessary GOT entry.
2023-09-01 13:58:09 +09:00
Rui Ueyama 0c972cf59d Fix a minor bug in R_X86_64_GOT32 handling 2023-09-01 13:54:39 +09:00
Rui Ueyama f057fdadba Fix TLSGD/TLSDDESC to Initial Exec relaxation 2023-09-01 12:39:10 +09:00
Rui Ueyama 3fb2571255 Refactor 2023-09-01 11:13:01 +09:00
Rui Ueyama 619f741ca9 Fix linker script "local" and "global" label handling
Fixes https://github.com/rui314/mold/issues/1098
2023-09-01 10:10:51 +09:00
Rui Ueyama 713d81947a Refactor 2023-09-01 09:57:20 +09:00
Rui Ueyama f76d3e0d0c Fix a unit test
Recent versions of GCC report a compile error if an extension
name is not recognizable.

Fixes https://github.com/rui314/mold/issues/1097
2023-08-31 18:16:32 +09:00
Rui Ueyama 488cd5d933 Add comments 2023-08-29 17:23:21 +09:00
Rui Ueyama 55bcb72be7 Fix a minor relaxation error 2023-08-29 16:12:57 +09:00
Rui Ueyama 7f8872b2f1 Do not update auto-generated files' timestamp on cmake
Fixes https://github.com/rui314/mold/issues/1094
2023-08-28 12:36:44 +09:00
Rui Ueyama 139807080b Refactor 2023-08-27 13:35:15 +09:00
Rui Ueyama 86b37b112f Add comments 2023-08-27 11:51:43 +09:00
Rui Ueyama d56f528c27 Relax TLSDESC to Initial Exec 2023-08-26 20:47:02 +09:00
Rui Ueyama a1b81b249f Add a rule for Asahi Linux 2023-08-26 14:37:48 +09:00
Rui Ueyama fa0de2f03b Make the PLT header a little bit compact 2023-08-25 11:26:43 +09:00
Rui Ueyama a357445654 Refactor 2023-08-24 19:08:10 +09:00
Rui Ueyama 8f5c49c655 Play the code-golf with our s390x PLT header 2023-08-23 22:41:41 +09:00
Rui Ueyama 03b5ce7c6f Reduce the size of PLT entry on s390 2023-08-23 21:45:58 +09:00
Rui Ueyama 63df2a3c8b Add a script to install cross toolchains 2023-08-23 19:45:28 +09:00
Rui Ueyama 9a09ead865 Reduce the size of range extension thunk for ARM 2023-08-23 16:40:48 +09:00
Rui Ueyama c2380ff7bc Do not run the compiler in sudo 2023-08-23 14:38:50 +09:00
Rui Ueyama c49a8b5b07 Merge pull request #1092 from david-perez/main
Update `README.md` installation instructions for CMake 3.14
2023-08-23 14:37:07 +09:00
Rui Ueyama 51fec5f1ff Avoid reading data from .plt 2023-08-23 14:02:03 +09:00
Rui Ueyama ac20d8729b Avoid reading data from .plt
With `--execute-only`, the text segment may not be readable.

Fixes https://github.com/rui314/mold/issues/1087
2023-08-23 12:53:27 +09:00
Rui Ueyama 7e9fedd203 Align range extension thunk entry points to 16 byte boundaries
Quote from Arm Cortex-A75 Software Optimization Guide
(PJDOC-466751330-205) p.59:

  Consider aligning subroutine entry points and branch targets to
  16-byte boundaries, within the bounds of the code-density requirements
  of the program. This ensures that the subsequent fetch can maximize
  bandwidth following the taken branch by bringing in all useful
  instructions.
2023-08-22 16:20:12 +09:00
Rui Ueyama 576cd86ecb Refactor 2023-08-22 15:47:34 +09:00
david-perez bb1db6cf17 Update README.md installation instructions for CMake 3.14
`CMakeLists.txt` declares a minimum CMake dependency on 3.14.

```
cmake_minimum_required(VERSION 3.13)
```

But the `--install` switch is only available since CMake 3.15, see
<https://stackoverflow.com/a/56457739>.

This commit updates the installation instructions in `README.md` so that
they work with CMake 3.14.
2023-08-21 18:52:35 +02:00
Rui Ueyama 3529a2fcc6 Refactor 2023-08-21 12:30:18 +09:00
Rui Ueyama e842bee049 Fix R_RISCV_HI20 relaxation 2023-08-20 17:30:44 +09:00
Rui Ueyama 3c8684c528 Fix R_RISCV_HI20 2023-08-20 17:03:38 +09:00
Rui Ueyama 108815dbe1 Implement LUI -> C.LUI relaxation 2023-08-20 14:29:02 +09:00
Rui Ueyama d75386b545 Bump the required CMake version to 3.14
Because CREATE_LINK requires CMake 3.14.

Fixes https://github.com/rui314/mold/issues/1091
2023-08-20 08:33:41 +09:00
Rui Ueyama bb1934bb9f Refactor 2023-08-19 17:34:38 +09:00
Rui Ueyama f01817f39e Statically link more libraies if -DMOLD_MOSTLY_STATIC=1 2023-08-19 14:08:26 +09:00
Rui Ueyama fad4c1b56c Disable tests in the bundled zlib 2023-08-19 13:55:03 +09:00
Rui Ueyama 3c8868280d Remove unnecessary build dependencies 2023-08-19 13:38:11 +09:00
Rui Ueyama a665ca766d Upgrade bundled zlib to v1.3 2023-08-19 13:30:22 +09:00
Rui Ueyama 0f85278c73 Upgrade bundled zstd to v1.5.5 2023-08-19 13:29:24 +09:00
Rui Ueyama 085727234a Refactor 2023-08-19 13:21:32 +09:00
Rui Ueyama 83fe073e97 Refactor 2023-08-18 17:14:04 +09:00
Rui Ueyama 8a25c1bfc1 Refactor 2023-08-17 18:50:15 +09:00
Rui Ueyama a94cd68ce1 Refactor 2023-08-17 17:45:48 +09:00
Rui Ueyama 68ac03d19c Refactor 2023-08-17 14:48:57 +09:00
Rui Ueyama b79bc35fd9 Fix CI 2023-08-17 12:40:02 +09:00
Rui Ueyama 90839d747f Refactor 2023-08-17 12:18:55 +09:00
Rui Ueyama 1b1ab744e5 Copy RELRO symbols to .copyrel.rel.ro instead of to .copyrel
So that readonly data wouldn't become writable by copy relocations.
2023-08-17 11:55:05 +09:00
Rui Ueyama f02db0fa0f Do not link libraries that are not directly used by the output file
If an as-needed DSO is referenced only by other DSOs and not by
the output file we are creating, we won't mark it as "needed".

Fixes https://github.com/rui314/mold/issues/1036
2023-08-17 11:03:12 +09:00
Rui Ueyama fbcee0b716 Refactor 2023-08-17 10:16:19 +09:00
Rui Ueyama c105cfd35a Fix CI 2023-08-16 23:13:07 +09:00
Rui Ueyama bb402480a5 Refactor 2023-08-16 22:57:16 +09:00
Rui Ueyama c983321fed Upgrade the bundled OneTBB to v2021.10.0 2023-08-16 21:06:41 +09:00
Rui Ueyama 43ebc96bfe Refactor 2023-08-16 20:46:25 +09:00
Rui Ueyama 8ae1ad57a4 Merge pull request #1090 from Romain-Geissler-1A/remove-openssl-mentions
Removing remaining openssl mentions following the move to Blake3.
2023-08-16 20:45:00 +09:00
Romain Geissler bd39bfdc5b Removing remaining openssl mentions following the move to Blake3. 2023-08-16 10:19:38 +00:00
Rui Ueyama a11566795d Update comments 2023-08-16 16:36:26 +09:00
Rui Ueyama 3676c596df Remove CI badge as well
We can see the CI status on the same GitHub homepage.
2023-08-16 14:27:21 +09:00
Rui Ueyama 0947836a44 Refactor 2023-08-16 14:27:09 +09:00
Rui Ueyama 2383e42a5c Merge pull request #1089 from marxin/drop-obs-badge
drop openSUSE OBS badge as the package is unmaintained
2023-08-16 14:26:58 +09:00
Martin Liska 049dbb73fe drop openSUSE OBS badge as the package is unmaintained 2023-08-16 07:19:27 +02:00
Rui Ueyama 95894ef2e2 Attempt to fix Windows CI 2023-08-16 13:36:30 +09:00
Rui Ueyama 7f7a744ebb Use BLAKE3 instead of SHA256 for cryptographic hash computation
Closes https://github.com/rui314/mold/issues/1088
2023-08-16 12:18:10 +09:00
Rui Ueyama 3833a4a457 Add BLAKE3 to third-party/
Since BLAKE3 is available under CC0, we don't need to add its license
terms (or the lack thereof) to our LICENSE.third-party file.
2023-08-16 12:13:53 +09:00
Rui Ueyama a0516a371f Refactor 2023-08-16 10:27:21 +09:00
Rui Ueyama 29491df6f2 Refactor 2023-08-15 18:19:39 +09:00
Rui Ueyama 599ed1dde0 Refactor 2023-08-15 16:56:59 +09:00
Rui Ueyama 143111ae5a Fix CI 2023-08-15 16:45:09 +09:00
Rui Ueyama d753ea5bdb Fix CI 2023-08-15 16:10:35 +09:00
Rui Ueyama b205eb7fa6 Refactor 2023-08-15 15:58:09 +09:00
Rui Ueyama d8baee9605 Refactor 2023-08-15 15:33:28 +09:00
Rui Ueyama 6dacc5192a Refactor 2023-08-15 15:08:34 +09:00
Rui Ueyama 4f5f88258c Fix TLSDESC relocation
Although we do not support TLSDESC on any target on which
ctx.tls_begin != ctx.dtp_addr, my understanding is that the origin
of the TLSDESC base relocation is tls_begin.
2023-08-14 19:55:39 +09:00
Rui Ueyama f5abedad01 Fix _TLS_MODULE_BASE_ address
Since the module base address obtained with `_TLS_MODULE_BASE_` will
be used with foo@dtpoff relocations, it should be the same as the DTP
address.
2023-08-14 19:39:50 +09:00
Rui Ueyama 02aee3b43e Do not put local symbols into .dynsym for TLSDESC
Placing local symbols into .dynsym is not technically incorrect, but
doing so is just a waste of space.
2023-08-14 19:06:35 +09:00
Rui Ueyama c1406860fa Fix -Wunused-variable
Fixes https://github.com/rui314/mold/issues/1086
2023-08-14 11:39:44 +09:00
Rui Ueyama 53759731cf Fix relocation overflow check 2023-08-14 08:39:13 +09:00
Rui Ueyama b018baec8a Refactor 2023-08-13 19:11:38 +09:00
Rui Ueyama 28ddea5371 Revert "Do not create copy relocations for PIE"
This reverts commit 82989a8b7c because
it broke our CI.
2023-08-13 17:55:44 +09:00
Rui Ueyama 82989a8b7c Do not create copy relocations for PIE
Object files have to be compiled with `-fPIE` or `-fPIC` for a
position-independent executable, and such object files shouldn't
contain relocations that directly refers to data objects without
GOT.
2023-08-13 17:02:45 +09:00
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
1151 changed files with 140371 additions and 26668 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}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
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}-linux-gnu arm-linux-gnueabihf; do
ln -sf /usr/bin/$i-gcc-10 /usr/bin/$i-gcc
ln -sf /usr/bin/$i-g++-10 /usr/bin/$i-g++
done
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
+70 -40
View File
@@ -43,8 +43,8 @@
# of the mold linker of the same version will have the exactly same set of
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.13)
project(mold VERSION 1.8.0)
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.2.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,17 @@ 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
-Wunused-variable
-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
@@ -88,7 +98,7 @@ if(MOLD_USE_TSAN)
target_link_options(mold PRIVATE -fsanitize=thread)
endif()
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
#
# This option is intended to be used by `./dist.sh` script to create a
# mold binary that works on various Linux distros. You probably don't
@@ -96,13 +106,12 @@ endif()
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
if(MOLD_MOSTLY_STATIC)
target_link_options(mold PRIVATE -static-libstdc++)
target_link_libraries(mold PRIVATE libcrypto.a)
endif()
# Find zlib. If libz.so is not found, we compile a bundled one and
# statically-link it to mold.
find_package(ZLIB QUIET)
if(ZLIB_FOUND)
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
else()
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
@@ -111,12 +120,23 @@ else()
target_link_libraries(mold PRIVATE zlibstatic)
endif()
# Find zstd compression library. Just like zlib, if libzstd.so is not
# found, we compile a bundled one and statically-link it to mold.
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
# is not found, we compile a bundled one and statically-link it to mold.
find_package(BLAKE3 QUIET)
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE BLAKE3::blake3)
else()
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
target_link_libraries(mold PRIVATE blake3)
target_include_directories(mold PUBLIC third-party/blake3/c)
endif()
# Find zstd compression library. If libzstd.so is not found, we compile a
# bundled one and statically-link it to mold.
include(CheckIncludeFile)
check_include_file(zstd.h HAVE_ZSTD_H)
if(HAVE_ZSTD_H)
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE zstd)
else()
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
@@ -142,7 +162,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 +218,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)
@@ -223,11 +247,6 @@ if(NOT APPLE AND NOT WIN32)
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
endif()
if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
endif()
# If atomics doesn't work by default, add -latomic.
# We need the flag on riscv, armv6 and m68k.
include(CheckCXXSourceCompiles)
@@ -245,7 +264,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 +274,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 +294,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 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 +316,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)
@@ -312,10 +335,12 @@ list(APPEND MOLD_MACHO_TEMPLATE_FILES
function(mold_instantiate_templates SOURCE TARGET)
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
if(NOT EXISTS ${PATH})
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
#define MOLD_TARGET ${TARGET}
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
")
endif()
target_sources(mold PRIVATE ${PATH})
endfunction()
@@ -335,8 +360,16 @@ 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
@@ -345,22 +378,13 @@ target_sources(mold PRIVATE
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 +445,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 +466,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.
+115 -171
View File
@@ -1,22 +1,19 @@
# mold: A Modern Linker
[![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 +23,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 +71,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.2.0
../install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
cmake --build . -j $(nproc)
sudo cmake --install .
sudo cmake --build . --target 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 +136,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 +160,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 +210,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
+1 -7
View File
@@ -11,13 +11,7 @@ RUN apt-get update && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
\
mkdir /openssl && cd /openssl && \
wget -O- -q https://www.openssl.org/source/openssl-3.0.7.tar.gz | tar --strip-components=1 -xzf - && \
./Configure --prefix=/usr/local --libdir=lib && \
make -j$(nproc) && \
make -j$(nproc) install && \
ldconfig && \
mkdir /cmake && cd /cmake && \
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
rm -rf /var/lib/apt/lists/* /cmake /openssl
rm -rf /var/lib/apt/lists/* /cmake
+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] == '@')
+212 -110
View File
@@ -8,6 +8,7 @@
#include <bitset>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
@@ -32,7 +33,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 +41,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 +78,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 +104,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 +157,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 +178,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 +200,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;
};
//
@@ -220,7 +299,7 @@ inline u64 bits(u64 val, u64 hi, u64 lo) {
inline i64 sign_extend(u64 val, i64 size) {
return (i64)(val << (63 - size)) >> (63 - size);
};
}
template <typename T, typename Compare = std::less<T>>
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
@@ -238,17 +317,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 +324,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 +395,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 +435,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';
@@ -392,53 +479,72 @@ public:
}
~ConcurrentMap() {
if (keys) {
free((void *)keys);
free((void *)key_sizes);
free((void *)values);
}
free(entries);
}
// In order to avoid unnecessary cache-line false sharing, we want
// to make this object to be aligned to a reasonably large
// power-of-two address.
struct alignas(32) Entry {
std::atomic<const char *> key;
T value;
u32 keylen;
};
void resize(i64 nbuckets) {
this->~ConcurrentMap();
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
i64 sz = sizeof(Entry) * this->nbuckets;
free(entries);
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]));
#if _WIN32
// Even though std::aligned_alloc is defined in C++17, MSVC doesn't
// seem to provide that function.
entries = (Entry *)_aligned_malloc(sz, alignof(Entry));
#else
entries = (Entry *)std::aligned_alloc(alignof(Entry), sz);
#endif
memset(entries, 0, sz);
}
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
if (!keys)
return {nullptr, false};
assert(has_single_bit(nbuckets));
i64 idx = hash & (nbuckets - 1);
i64 retry = 0;
while (retry < MAX_RETRY) {
const char *ptr = keys[idx].load(std::memory_order_acquire);
if (ptr == marker) {
pause();
Entry &ent = entries[idx];
const char *ptr = nullptr;
bool claimed = ent.key.compare_exchange_weak(ptr, (char *)-1,
std::memory_order_acquire);
// If we successfully claimed the ownership of an unused slot,
// copy values to it.
if (claimed) {
new (&ent.value) T(val);
ent.keylen = key.size();
ent.key.store(key.data(), std::memory_order_release);
return {&ent.value, true};
}
// Loop on a spurious failure.
if (ptr == nullptr)
continue;
// If someone is copying values to the slot, do busy wait.
while (ptr == (char *)-1) {
pause();
ptr = ent.key.load(std::memory_order_acquire);
}
if (ptr == nullptr) {
if (!keys[idx].compare_exchange_weak(ptr, marker,
std::memory_order_acq_rel))
continue;
new (values + idx) T(val);
key_sizes[idx] = key.size();
keys[idx].store(key.data(), std::memory_order_release);
return {values + idx, true};
}
if (key.size() == key_sizes[idx] &&
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
return {values + idx, false};
// If the same key is already present, this is the slot we are
// looking for.
if (key == std::string_view(ptr, ent.keylen))
return {&ent.value, false};
// Otherwise, move on to the next slot.
u64 mask = nbuckets / NUM_SHARDS - 1;
idx = (idx & ~mask) | ((idx + 1) & mask);
retry++;
@@ -448,18 +554,21 @@ public:
return {nullptr, false};
}
bool has_key(i64 idx) {
return keys[idx].load(std::memory_order_relaxed);
const char *get_key(i64 idx) {
return entries[idx].key.load(std::memory_order_relaxed);
}
i64 get_idx(T *value) const {
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
return (Entry *)addr - entries;
}
static constexpr i64 MIN_NBUCKETS = 2048;
static constexpr i64 NUM_SHARDS = 16;
static constexpr i64 MAX_RETRY = 128;
Entry *entries = nullptr;
i64 nbuckets = 0;
std::atomic<const char *> *keys = nullptr;
u32 *key_sizes = nullptr;
T *values = nullptr;
private:
static void pause() {
@@ -469,21 +578,19 @@ private:
asm volatile("yield");
#endif
}
static constexpr const char *marker = "marker";
};
//
// 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 +753,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 +796,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 +850,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 +889,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 +898,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 +910,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 +926,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 +949,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 +970,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());
+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())
+171 -157
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:
@@ -194,14 +195,14 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe08f'e00e, // 1: add lr, pc, lr
0xe5be'f008, // ldr pc, [lr, #8]!
0xe5be'f008, // ldr pc, [lr, #8]!
0x0000'0000, // 2: .word .got.plt - 1b - 8
0xe320'f000, // nop
0xe320'f000, // nop
0xe320'f000, // nop
0xe320'f000, // nop
0xe320'f000, // nop
0xe320'f000, // nop
};
memcpy(buf, insn, sizeof(insn));
@@ -210,8 +211,8 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 plt_entry[] = {
0xe59f'c004, // 1: ldr ip, 2f
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0x0000'0000, // 2: .word sym@GOT - 1b
};
@@ -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,54 +330,66 @@ 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: {
// 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);
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
case R_ARM_CALL: {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
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);
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 | bits(get_arm_thunk_addr() + A - P, 25, 2);
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
}
break;
}
case R_ARM_JUMP24:
case R_ARM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
// These relocs refers to a B (unconditional branch) instruction.
// Unlike BL or BLX, we can't rewrite B to BX in place when the
// processor mode switch is required because BX doesn't takes an
// immediate; it takes only a register. So if mode switch is
// required, we jump to a linker-synthesized thunk which does the
// job with a longer code sequence.
u64 val = S + A - P;
if (!is_jump_reachable(val) || T)
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
}
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;
@@ -456,12 +467,33 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
// ARM32 TLSDESC uses the following code sequence to materialize
// a TP-relative address in r0.
//
// ldr r0, .L2
// .L1: bl foo
// R_ARM_TLS_CALL
// .L2: .word foo + . - .L1
// R_ARM_TLS_GOTDESC
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// ldr r0, .L2
// .L1: ldr r0, [pc, r0]
// ...
// .L2: .word foo(gottpoff) + . - .L1
//
// or to the following for executable.
//
// ldr r0, .L2
// .L1: nop
// ...
// .L2: .word foo(tpoff)
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
if (A & 1)
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
else
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
} else {
*(ul32 *)loc = S - ctx.tp_addr;
}
@@ -469,20 +501,25 @@ 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 if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
// BL -> NOP
*(ul32 *)loc = 0xe320'f000;
*(ul32 *)loc = 0xe320'f000; // nop
}
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 if (sym.has_gottp(ctx)) {
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
// we use two instructions instead.
*(ul16 *)loc = 0x4478; // add r0, pc
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
} else {
// BL -> NOP.W
*(ul32 *)loc = 0x8000'f3af;
*(ul32 *)loc = 0x8000'f3af; // nop.w
}
break;
default:
@@ -497,17 +534,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 +578,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 +595,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 +612,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);
case R_ARM_TLS_CALL:
case R_ARM_THM_TLS_CALL:
scan_tlsdesc(ctx, sym);
break;
case R_ARM_TLS_LE32:
check_tlsle(ctx, sym, rel);
break;
case R_ARM_REL32:
case R_ARM_BASE_PREL:
@@ -607,10 +637,8 @@ 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:
case R_ARM_TLS_GOTDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -620,8 +648,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// TLS trampoline code. ARM32's TLSDESC is designed so that this
// common piece of code is factored out from object files to reduce
// output size. Since no one provide, the linker has to synthesize it.
@@ -629,36 +655,44 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
0xe08e'0000, // add r0, lr, r0
0xe590'1004, // ldr r1, [r0, #4]
0xe12f'ff11, // bx r1
0xe320'f000, // nop
};
// This is a range extension and mode switch thunk.
// 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
0x1c, 0xff, 0x2f, 0xe1, // bx ip
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
};
static_assert(E::thunk_hdr_size == sizeof(hdr));
static_assert(E::thunk_size == sizeof(entry));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
memcpy(buf, hdr, sizeof(hdr));
buf += sizeof(hdr);
for (i64 i = 0; i < symbols.size(); i++) {
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
memcpy(loc, entry, sizeof(entry));
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
u64 S = symbols[i]->get_addr(ctx);
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr) + i * sizeof(entry);
*(ul32 *)(loc + 16) = S - P - 16;
for (Symbol<E> *sym : symbols) {
memcpy(buf, entry, sizeof(entry));
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
buf += sizeof(entry);
P += sizeof(entry);
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return EF_ARM_EABI_VER5;
}
// ARM executables use an .ARM.exidx section to look up an exception
// handling record for the current instruction pointer. The table needs
// to be sorted by their addresses.
@@ -668,7 +702,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 +728,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 +759,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);
}
}
+202 -102
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,86 @@ 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 && sym.is_pcrel_linktime_const(ctx) &&
i + 1 < rels.size()) {
i64 val = S + A - P - 4;
const ElfRel<E> &rel2 = rels[i + 1];
if (sign_extend(val, 20) == val &&
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, val);
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 +286,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 +355,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;
@@ -322,38 +371,65 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
// ARM64 TLSDESC uses the following code sequence to materialize
// a TP-relative address in x0.
//
// adrp x0, 0
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
// ldr x1, [x0]
// R_AARCH64_TLSDESC_LD64_LO12 foo
// add x0, x0, #0
// R_AARCH64_TLSDESC_ADD_LO12 foo
// blr x1
// R_AARCH64_TLSDESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// nop
// nop
// adrp x0, :gottprel:foo
// ldr x0, [x0, :gottprel_lo12:foo]
//
// or to the following for executable.
//
// nop
// nop
// movz x0, :tls_offset_hi:foo, lsl #16
// movk x0, :tls_offset_lo:foo
if (sym.has_tlsdesc(ctx)) {
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
i64 val = (S + A - ctx.tp_addr);
check(val, -(1LL << 32), 1LL << 32);
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
*(ul32 *)loc = 0xd503'201f; // nop
}
break;
case R_AARCH64_TLSDESC_LD64_LO12:
if (sym.has_tlsdesc(ctx)) {
if (sym.has_tlsdesc(ctx))
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
} else {
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
}
else
*(ul32 *)loc = 0xd503'201f; // nop
break;
case R_AARCH64_TLSDESC_ADD_LO12:
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
} else {
// add x0, x0, #0 -> nop
*(ul32 *)loc = 0xd503'201f;
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
}
break;
case R_AARCH64_TLSDESC_CALL:
if (!sym.has_tlsdesc(ctx)) {
// blr x2 -> nop
*(ul32 *)loc = 0xd503'201f;
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
} else {
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
}
break;
default:
@@ -368,17 +444,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 +495,77 @@ 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_pcrel_linktime_const(ctx) &&
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:
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
case R_AARCH64_TLSDESC_CALL:
scan_tlsdesc(ctx, sym);
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 +582,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;
@@ -505,24 +603,26 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const ul32 data[] = {
static const ul32 insn[] = {
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
0xd61f'0200, // br x16
0xd503'201f, // nop
};
static_assert(E::thunk_size == sizeof(data));
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 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *loc = buf + i * E::thunk_size;
memcpy(loc , data, sizeof(data));
write_adrp(loc, page(S) - page(P));
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
memcpy(buf, insn, E::thunk_size);
write_adrp(buf, page(S) - page(P));
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
buf += E::thunk_size;
P += E::thunk_size;
}
}
+150 -160
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;
@@ -295,72 +338,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
break;
case R_386_TLS_GD:
if (sym.has_tlsgd(ctx)) {
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();
}
i++;
}
else
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
break;
case R_386_TLS_LDM:
if (ctx.got->has_tlsld(ctx)) {
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;
i++;
}
else
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
break;
case R_386_TLS_LDO_32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
@@ -369,13 +356,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = sym.esym().st_size + A;
break;
case R_386_TLS_GOTDESC:
// i386 TLSDESC uses the following code sequence to materialize
// a TP-relative address in %eax.
//
// lea 0(%ebx), %eax
// R_386_TLS_GOTDESC foo
// call *(%eax)
// R_386_TLS_DESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// mov foo@GOTTPOFF(%ebx), %eax
// nop
//
// or to the following for executable.
//
// mov $foo@TPOFF, %eax
// nop
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
} else if (sym.has_gottp(ctx)) {
loc[-2] = 0x8b;
loc[-1] = 0x83;
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
} else {
static const u8 insn[] = {
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
loc[-2] = 0x90;
loc[-1] = 0xb8;
*(ul32 *)loc = S + A - ctx.tp_addr;
}
break;
@@ -398,17 +404,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 +426,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 +482,24 @@ 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;
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
}
switch (rel.r_type) {
case R_386_8:
@@ -518,55 +516,47 @@ 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);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
case R_386_GOT32X:
// We always want to relax GOT32X even if --no-relax is given
// because static PIE doesn't work without it.
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
// Do nothing
} else {
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())
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
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.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static)
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())
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
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);
scan_tlsdesc(ctx, sym);
break;
case R_386_TLS_LE:
check_tlsle(ctx, sym, rel);
break;
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
+686
View File
@@ -0,0 +1,686 @@
// 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 bits(page(val + 0x800) - page(pc), 31, 12);
}
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 $rN, %pc_hi20(sym)
// addi.d $rM, $zero, %lo12(sym)
// lu32i.d $rM, %pc64_lo20(sym)
// lu52i.d $rM, $r12, %pc64_hi12(sym)
// add.d $rN, $rN, $rM
//
// 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.
bool x = val & 0x800;
bool y = (page(val + 0x800) - page(pc)) & 0x8000'0000;
if (x && !y)
return val - 0x1'0000'0000;
if (!x && y)
return val + 0x1'0000'0000;
return val;
}
static u64 higher20(u64 val, u64 pc) {
return bits(hi64(val, pc), 51, 32);
}
static u64 highest12(u64 val, u64 pc) {
return bits(hi64(val, pc), 63, 52);
}
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_k16(u8 *loc, u32 val) {
// opcode, [15:0], rj, rd
*(ul32 *)loc &= 0b111111'0000000000000000'11111'11111;
*(ul32 *)loc |= bits(val, 15, 0) << 10;
}
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_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);
}
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));
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));
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));
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_got_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_got_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_LO12:
write_k12(loc, S + A);
break;
case R_LARCH_ABS_HI20:
write_j20(loc, (S + A) >> 12);
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_LO12:
write_k12(loc, S + A);
break;
case R_LARCH_PCALA_HI20:
check(S + A - P, -(1LL << 31), 1LL << 31);
write_j20(loc, hi20(S + A, P));
break;
case R_LARCH_PCALA64_LO20:
write_j20(loc, higher20(S + A, P));
break;
case R_LARCH_PCALA64_HI12:
write_k12(loc, highest12(S + A, P));
break;
case R_LARCH_GOT_PC_LO12:
write_k12(loc, GOT + G + A);
break;
case R_LARCH_GOT_PC_HI20:
check(GOT + G + A - P, -(1LL << 31), 1LL << 31);
write_j20(loc, hi20(GOT + G + A, P));
break;
case R_LARCH_GOT64_PC_LO20:
write_j20(loc, higher20(GOT + G + A, P));
break;
case R_LARCH_GOT64_PC_HI12:
write_k12(loc, highest12(GOT + G + A, P));
break;
case R_LARCH_GOT_LO12:
write_k12(loc, GOT + G + A);
break;
case R_LARCH_GOT_HI20:
write_j20(loc, (GOT + G + A) >> 12);
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_LO12:
write_k12(loc, S + A - ctx.tp_addr);
break;
case R_LARCH_TLS_LE_HI20:
write_j20(loc, (S + A - ctx.tp_addr) >> 12);
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_LO12:
write_k12(loc, sym.get_gottp_addr(ctx) + A);
break;
case R_LARCH_TLS_IE_PC_HI20:
check(sym.get_gottp_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
break;
case R_LARCH_TLS_IE64_PC_LO20:
write_j20(loc, higher20(sym.get_gottp_addr(ctx) + A, P));
break;
case R_LARCH_TLS_IE64_PC_HI12:
write_k12(loc, highest12(sym.get_gottp_addr(ctx) + A, P));
break;
case R_LARCH_TLS_IE_LO12:
write_k12(loc, sym.get_gottp_addr(ctx) + A);
break;
case R_LARCH_TLS_IE_HI20:
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 12);
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:
check(sym.get_tlsgd_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
write_j20(loc, hi20(sym.get_tlsgd_addr(ctx) + A, P));
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) {
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));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
memcpy(buf, insn, sizeof(insn));
write_j20(buf, hi20(S, P));
write_k12(buf + 4, S);
buf += sizeof(insn);
P += sizeof(insn);
}
}
} // 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;
+45 -57
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:
@@ -423,8 +409,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const ub32 local_thunk[] = {
// Get this thunk's address
0x7c08'02a6, // mflr r0
@@ -443,22 +427,26 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_entry));
static_assert(E::thunk_size == sizeof(local_thunk));
for (i64 i = 0; i < symbols.size(); i++) {
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
Symbol<E> &sym = *symbols[i];
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
if (sym.has_plt(ctx)) {
memcpy(loc, plt_entry, sizeof(plt_entry));
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
i64 val = got - get_addr(i) - 8;
loc[4] |= higha(val);
loc[5] |= lo(val);
for (Symbol<E> *sym : symbols) {
if (sym->has_plt(ctx)) {
u64 got =
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
i64 val = got - P - 8;
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx) - get_addr(i) - 8;
loc[4] |= higha(val);
loc[5] |= lo(val);
i64 val = sym->get_addr(ctx) - P - 8;
memcpy(buf, local_thunk, sizeof(local_thunk));
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
+128 -94
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
@@ -73,29 +72,55 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
0x7d88'02a6, // mflr r12
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
0xe84b'0024, // ld r2,36(r11)
0x7d88'03a6, // mtlr r12
0x7d62'5a14, // add r11,r2,r11
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
0xe98b'0000, // ld r12,0(r11)
0xe84b'0008, // ld r2,8(r11)
0x7d89'03a6, // mtctr r12
0xe96b'0010, // ld r11,16(r11)
0x4e80'0420, // bctr
// .quad .got.plt - .plt - 8
0x0000'0000,
0x0000'0000,
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
}
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:
@@ -394,8 +410,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// If the destination is .plt.got, we save the current r2, read an
// address of a function descriptor from .got, restore %r2 and jump
// to the function.
@@ -450,26 +464,29 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(local_thunk));
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
if (sym.has_got(ctx)) {
memcpy(loc, pltgot_thunk, sizeof(pltgot_thunk));
i64 val = sym.get_got_addr(ctx) - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
} else if(sym.has_plt(ctx)) {
memcpy(loc, plt_thunk, sizeof(plt_thunk));
i64 val = sym.get_gotplt_addr(ctx) - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
for (Symbol<E> *sym : symbols) {
if (sym->has_got(ctx)) {
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else if(sym->has_plt(ctx)) {
i64 val = sym->get_gotplt_addr(ctx) - ctx.extra.TOC->value;
memcpy(buf, plt_thunk, sizeof(plt_thunk));
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
loc[0] |= higha(val);
loc[1] |= lo(val);
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
memcpy(buf, local_thunk, sizeof(local_thunk));
*(ub32 *)buf |= higha(val);
*(ub32 *)(buf + 4) |= lo(val);
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
@@ -521,10 +538,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 +549,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 +564,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 +629,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 +657,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);
}
}
}
+203 -102
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
@@ -85,25 +112,26 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
0x7c08'03a6, // mtlr r0
// Compute the PLT entry index
0xe80b'002c, // ld r0, 44(r11)
0x7d8b'6050, // subf r12, r11, r12
0x7d60'5a14, // add r11, r0, r11
0x380c'ffcc, // addi r0, r12, -52
0x398c'ffd4, // addi r12, r12, -44
0x7c0b'6050, // subf r0, r11, r12
0x7800'f082, // rldicl r0, r0, 62, 2
// Compute the address of .got.plt
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
0xe98b'0000, // ld r12, 0(r11)
0x7d89'03a6, // mtctr r12
0xe96b'0008, // ld r11, 8(r11)
0x4e80'0420, // bctr
// .quad .got.plt - .plt - 8
0x0000'0000,
0x0000'0000,
};
memcpy(buf, insn, sizeof(insn));
*(ul64 *)(buf + 52) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
*(ul32 *)(buf + 28) |= higha(val);
*(ul32 *)(buf + 32) |= lo(val);
}
template <>
@@ -120,8 +148,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 +172,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 +197,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 +204,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 +224,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 +276,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 +346,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 +399,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 +415,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 +456,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 +469,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;
@@ -409,53 +479,84 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// 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);
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
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);
for (Symbol<E> *sym : symbols) {
if (sym->has_plt(ctx)) {
u64 got =
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
if (ctx.extra.is_power10) {
memcpy(buf, plt_thunk_power10, E::thunk_size);
*(ul64 *)(buf + 1) |= prefix34(got - P - 4);
} else {
i64 val = got - ctx.extra.TOC->value;
memcpy(buf, plt_thunk, E::thunk_size);
*(ul32 *)(buf + 4) |= higha(val);
*(ul32 *)(buf + 8) |= lo(val);
}
} 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(buf, local_thunk_power10, E::thunk_size);
*(ul64 *)(buf + 1) |= prefix34(sym->get_addr(ctx) - P - 4);
} else {
i64 val = sym->get_addr(ctx) - ctx.extra.TOC->value;
memcpy(buf, local_thunk, E::thunk_size);
*(ul32 *)(buf + 4) |= higha(val);
*(ul32 *)(buf + 8) |= lo(val);
}
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return 2;
}
} // namespace mold::elf
+591 -255
View File
File diff suppressed because it is too large. Load diff
+124 -173
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"
@@ -48,16 +48,23 @@ static void write_mid20(u8 *loc, u64 val) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static u8 insn[] = {
// Compute PLT_INDEX
0xb9, 0x09, 0x00, 0x01, // sgr %r0, %r1
0xa7, 0x0b, 0xff, 0xc2, // aghi %r0, -62
0xeb, 0x10, 0x00, 0x01, 0x00, 0x0c, // srlg %r1, %r0, 1
0xb9, 0x08, 0x00, 0x01, // agr %r0, %r1
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
// Branch to _dl_runtime_resolve
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x07, 0x00, 0x07, 0x00, // nopr; nopr
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 8) = (ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 6) >> 1;
*(ub32 *)(buf + 26) =
(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 24) >> 1;
}
template <>
@@ -65,15 +72,12 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static u8 insn[] = {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0xc0, 0x01, 0, 0, 0, 0, // lgfi %r0, PLT_INDEX
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x0d, 0x01, // basr %r0, %r1
0x07, 0x00, // nopr
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 2) = (sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
*(ub32 *)(buf + 14) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
template <>
@@ -90,8 +94,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 +153,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 +251,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 +274,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 +322,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 +339,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 "
@@ -392,12 +360,10 @@ 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_390_32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_390_32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_390_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub64 *)loc = *val;
@@ -426,18 +392,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 +434,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 +446,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.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static) {
// Do nothing
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
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);
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared)) {
// Do nothing
} else {
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;
+92 -132
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:
@@ -321,36 +288,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// We always have to relax a GOT load to a load immediate if a
// symbol is local, because R_SPARC_GOTDATA_OP cannot represent
// an addend for a local symbol.
if (sym.is_imported || sym.is_ifunc()) {
if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
} else if (sym.is_pcrel_linktime_const(ctx)) {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
} else {
*(ub32 *)loc |= bits(G, 31, 10);
} else if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
} else {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
}
break;
case R_SPARC_GOTDATA_OP_LOX10: {
if (sym.is_imported || sym.is_ifunc()) {
case R_SPARC_GOTDATA_OP_LOX10:
if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
} else if (sym.is_pcrel_linktime_const(ctx)) {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
} else {
*(ub32 *)loc |= bits(G, 9, 0);
} else if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
} else {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
}
break;
}
case R_SPARC_GOTDATA_OP:
if (sym.is_imported || sym.is_ifunc())
break;
if (sym.is_absolute()) {
// ldx [ %g2 + %g1 ], %g1 → nop
*(ub32 *)loc = 0x0100'0000;
} else {
} else if (sym.is_pcrel_linktime_const(ctx)) {
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
@@ -402,12 +365,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 +421,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 +478,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 +528,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 +554,23 @@ 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)
if (Symbol<E> &sym = *ctx.extra.tls_get_addr_sym; sym.is_imported)
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 +586,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;
}
}
}
+286 -289
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) {
@@ -165,26 +165,143 @@ static u32 relax_gottpoff(u8 *loc) {
return 0;
}
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
// 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 tls_size) {
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
//
// The instructions are so short that we cannot rewrite them with
// "mov %fs:0, %rax" which is 9 bytes long. We use a shorter code
// sequence instead. Since "xor %eax, %eax" zero-clears %rax, the
// meaning is equivalent.
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) = tls_size;
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) = tls_size;
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[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 8) = tls_size;
break;
}
default:
unreachable();
}
return 0;
}
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
@@ -231,18 +348,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 +363,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);
@@ -272,7 +381,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc = S + A - P;
break;
case R_X86_64_GOT32:
write32s(G + A);
write32(G + A);
break;
case R_X86_64_GOT64:
*(ul64 *)loc = G + A;
@@ -294,146 +403,48 @@ 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_pcrel_linktime_const(ctx)) {
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_pcrel_linktime_const(ctx)) {
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)) {
if (sym.has_tlsgd(ctx))
write32s(sym.get_tlsgd_addr(ctx) + A - P);
} 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();
}
i++;
}
else if (sym.has_gottp(ctx))
relax_gd_to_ie(loc, rels[++i], sym.get_gottp_addr(ctx) - P);
else
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
break;
case R_X86_64_TLSLD:
if (ctx.got->has_tlsld(ctx)) {
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);
i++;
}
else
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
break;
case R_X86_64_DTPOFF32:
write32s(S + A - ctx.dtp_addr);
@@ -460,15 +471,46 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
break;
case R_X86_64_GOTPC32_TLSDESC:
// x86-64 TLSDESC uses the following code sequence to materialize
// a TP-relative address in %rax.
//
// lea 0(%rip), %rax
// R_X86_64_GOTPC32_TLSDESC foo
// call *(%rax)
// R_X86_64_TLSDESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// mov foo@GOTTPOFF(%rip), %rax
// nop
//
// or to the following for executable.
//
// mov $foo@TPOFF, %rax
// nop
if (sym.has_tlsdesc(ctx)) {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
} else if (sym.has_gottp(ctx)) {
// mov foo@gottpoff(%rip), %rax
loc[-3] = 0x48;
loc[-2] = 0x8b;
loc[-1] = 0x05;
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
// mov $foo@tpoff, %rax
loc[-3] = 0x48;
loc[-2] = 0xc7;
loc[-1] = 0xc0;
write32s(S - ctx.tp_addr);
assert(A == -4);
}
break;
case R_X86_64_TLSDESC_CALL:
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
break;
case R_X86_64_SIZE32:
@@ -477,13 +519,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_X86_64_SIZE64:
*(ul64 *)loc = sym.esym().st_size + A;
break;
case R_X86_64_TLSDESC_CALL:
if (!sym.has_tlsdesc(ctx)) {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
break;
default:
unreachable();
}
@@ -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,27 @@ 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;
if (rel.r_type == R_X86_64_TLSGD || rel.r_type == R_X86_64_TLSLD) {
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": " << rel
<< " must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": " << rel
<< " must be followed by PLT or GOTPCREL";
}
switch (rel.r_type) {
case R_X86_64_8:
@@ -645,103 +688,57 @@ 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: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (relax_tlsgd(ctx, sym))
case R_X86_64_TLSGD:
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
i++;
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
i++;
} else {
sym.flags |= NEEDS_TLSGD;
}
break;
case R_X86_64_TLSLD:
// 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
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
}
case R_X86_64_TLSLD: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (relax_tlsld(ctx))
i++;
else
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
case R_X86_64_GOTTPOFF:
if (ctx.arg.relax && relax_gottpoff(loc - 3) &&
sym.is_tprel_linktime_const(ctx)) {
// do nothing
} else {
sym.flags |= NEEDS_GOTTP;
}
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);
case R_X86_64_TLSDESC_CALL:
scan_tlsdesc(ctx, sym);
break;
}
case R_X86_64_GOTPC32_TLSDESC: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
<< " against an invalid code sequence";
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
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:
case R_X86_64_GOTPC32_TLSDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
+55 -30
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
@@ -398,13 +402,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
std::vector<std::string> remaining;
std::string_view arg;
ctx.page_size = E::page_size;
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
bool version_shown = false;
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 +427,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,7 +524,8 @@ 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 elf64loongarch\n elf32loongarch";
version_shown = true;
} else if (read_arg("m")) {
if (arg == "elf_x86_64") {
@@ -555,6 +560,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.emulation = SH4::target_name;
} else if (arg == "elf64alpha") {
ctx.arg.emulation = ALPHA::target_name;
} else if (arg == "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 +591,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 +632,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 +659,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 +677,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 +701,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 +721,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 +867,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 +1008,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 +1061,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 +1103,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 +1140,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 +1178,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 +1205,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)
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;
+946 -817
View File
File diff suppressed because it is too large. Load diff
+290 -133
View File
@@ -1,8 +1,8 @@
#pragma once
#include "../integers.h"
#include "../common/integers.h"
#include <cstdint>
#include <concepts>
#include <ostream>
#include <string>
#include <type_traits>
@@ -25,6 +25,8 @@ struct SPARC64;
struct M68K;
struct SH4;
struct ALPHA;
struct LOONGARCH64;
struct LOONGARCH32;
template <typename E> struct ElfSym;
template <typename E> struct ElfShdr;
@@ -54,9 +56,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 +89,7 @@ enum : u32 {
SHT_X86_64_UNWIND = 0x70000001,
SHT_ARM_EXIDX = 0x70000001,
SHT_ARM_ATTRIBUTES = 0x70000003,
SHT_RISCV_ATTRIBUTES = 0x70000003,
};
enum : u32 {
@@ -182,7 +185,9 @@ 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,
};
enum : u32 {
@@ -226,6 +231,7 @@ enum : u32 {
EM_X86_64 = 62,
EM_AARCH64 = 183,
EM_RISCV = 243,
EM_LOONGARCH = 258,
EM_ALPHA = 0x9026,
};
@@ -370,6 +376,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
//
@@ -720,6 +744,7 @@ enum : u32 {
R_RISCV_TLS_DTPREL64 = 9,
R_RISCV_TLS_TPREL32 = 10,
R_RISCV_TLS_TPREL64 = 11,
R_RISCV_TLSDESC = 12,
R_RISCV_BRANCH = 16,
R_RISCV_JAL = 17,
R_RISCV_CALL = 18,
@@ -757,6 +782,13 @@ 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,
R_RISCV_TLSDESC_HI20 = 62,
R_RISCV_TLSDESC_LOAD_LO12 = 63,
R_RISCV_TLSDESC_ADD_LO12 = 64,
R_RISCV_TLSDESC_CALL = 65,
};
enum : u32 {
@@ -933,9 +965,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 +1253,107 @@ enum : u32 {
R_ALPHA_TPREL16 = 41,
};
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 +1590,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 +1713,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__
@@ -1648,37 +1782,38 @@ struct ElfRel<SPARC64> {
// Machine descriptions
//
template <typename E>
static constexpr bool supports_tlsdesc = requires { E::R_TLSDESC; };
template <typename E> concept supports_ifunc = requires { E::R_IRELATIVE; };
template <typename E> concept supports_tlsdesc = requires { E::R_TLSDESC; };
template <typename E> concept needs_thunk = requires { E::thunk_size; };
template <typename E>
static constexpr bool needs_thunk = requires { E::thunk_size; };
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
template <typename E> concept is_i386 = std::same_as<E, I386>;
template <typename E> concept is_arm64 = std::same_as<E, ARM64>;
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
template <typename E> concept is_rv32be = std::same_as<E, RV32BE>;
template <typename E> concept is_ppc32 = std::same_as<E, PPC32>;
template <typename E> concept is_ppc64v1 = std::same_as<E, PPC64V1>;
template <typename E> concept is_ppc64v2 = std::same_as<E, PPC64V2>;
template <typename E> concept is_s390x = std::same_as<E, S390X>;
template <typename E> concept is_sparc64 = std::same_as<E, SPARC64>;
template <typename E> concept is_m68k = std::same_as<E, M68K>;
template <typename E> concept is_sh4 = std::same_as<E, SH4>;
template <typename E> concept is_alpha = std::same_as<E, ALPHA>;
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
template <typename E> static constexpr bool is_x86_64 = std::is_same_v<E, X86_64>;
template <typename E> static constexpr bool is_i386 = std::is_same_v<E, I386>;
template <typename E> static constexpr bool is_arm64 = std::is_same_v<E, ARM64>;
template <typename E> static constexpr bool is_arm32 = std::is_same_v<E, ARM32>;
template <typename E> static constexpr bool is_rv64le = std::is_same_v<E, RV64LE>;
template <typename E> static constexpr bool is_rv64be = std::is_same_v<E, RV64BE>;
template <typename E> static constexpr bool is_rv32le = std::is_same_v<E, RV32LE>;
template <typename E> static constexpr bool is_rv32be = std::is_same_v<E, RV32BE>;
template <typename E> static constexpr bool is_ppc32 = std::is_same_v<E, PPC32>;
template <typename E> static constexpr bool is_ppc64v1 = std::is_same_v<E, PPC64V1>;
template <typename E> static constexpr bool is_ppc64v2 = std::is_same_v<E, PPC64V2>;
template <typename E> static constexpr bool is_s390x = std::is_same_v<E, S390X>;
template <typename E> static constexpr bool is_sparc64 = std::is_same_v<E, SPARC64>;
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_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> static constexpr bool is_arm = is_arm64<E> || is_arm32<E>;
template <typename E> static constexpr bool is_rv64 = is_rv64le<E> || is_rv64be<E>;
template <typename E> static constexpr bool is_rv32 = is_rv32le<E> || is_rv32be<E>;
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> concept is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
template <typename E> concept is_rv32 = is_rv32le<E> || is_rv32be<E>;
template <typename E> concept is_riscv = is_rv64<E> || is_rv32<E>;
template <typename E> concept is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
template <typename E> concept is_ppc = is_ppc64<E> || is_ppc32<E>;
template <typename E> concept is_sparc = is_sparc64<E>;
template <typename E> concept is_loongarch = is_loongarch64<E> || is_loongarch32<E>;
struct X86_64 {
static constexpr std::string_view target_name = "x86_64";
@@ -1695,6 +1830,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 +1854,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;
@@ -1737,12 +1874,13 @@ struct ARM64 {
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 12;
static constexpr u32 thunk_size = 16;
static constexpr u32 R_COPY = R_AARCH64_COPY;
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_AARCH64_JUMP_SLOT;
static constexpr u32 R_ABS = R_AARCH64_ABS64;
static constexpr u32 R_DYNAMIC = R_AARCH64_ABS64;
static constexpr u32 R_RELATIVE = R_AARCH64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_AARCH64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_AARCH64_TLS_DTPREL64;
@@ -1761,13 +1899,14 @@ struct ARM32 {
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 = 12;
static constexpr u32 thunk_size = 20;
static constexpr u32 thunk_hdr_size = 16;
static constexpr u32 thunk_size = 16;
static constexpr u32 R_COPY = R_ARM_COPY;
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ARM_JUMP_SLOT;
static constexpr u32 R_ABS = R_ARM_ABS32;
static constexpr u32 R_DYNAMIC = R_ARM_ABS32;
static constexpr u32 R_RELATIVE = R_ARM_RELATIVE;
static constexpr u32 R_IRELATIVE = R_ARM_IRELATIVE;
static constexpr u32 R_DTPOFF = R_ARM_TLS_DTPOFF32;
@@ -1776,32 +1915,40 @@ struct ARM32 {
static constexpr u32 R_TLSDESC = R_ARM_TLS_DESC;
};
struct RV64LE {
struct RV64 {
static constexpr bool is_64 = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_64;
static constexpr u32 R_DYNAMIC = R_RISCV_64;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
};
struct RV64LE : RV64 {
static constexpr std::string_view target_name = "riscv64";
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_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_64;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
};
struct RV64BE {
struct RV64BE : RV64 {
static constexpr std::string_view target_name = "riscv64be";
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
};
struct RV32 {
static constexpr bool is_64 = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
@@ -1810,58 +1957,26 @@ struct RV64BE {
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
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_64;
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_DTPREL64;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
};
struct RV32LE {
struct RV32LE : RV32 {
static constexpr std::string_view target_name = "riscv32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_32;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
};
struct RV32BE {
struct RV32BE : RV32 {
static constexpr std::string_view target_name = "riscv32be";
static constexpr bool is_64 = false;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_32;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
};
struct PPC32 {
@@ -1881,6 +1996,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;
@@ -1888,23 +2004,17 @@ struct PPC32 {
static constexpr u32 R_DTPMOD = R_PPC_DTPMOD32;
};
struct PPC64V1 {
static constexpr std::string_view target_name = "ppc64v1";
struct PPC64 {
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
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;
static constexpr u32 R_COPY = R_PPC64_COPY;
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;
@@ -1912,28 +2022,23 @@ struct PPC64V1 {
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
};
struct PPC64V2 {
struct PPC64V1 : PPC64 {
static constexpr std::string_view target_name = "ppc64v1";
static constexpr bool is_le = false;
static constexpr u32 plt_hdr_size = 44;
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 28;
};
struct PPC64V2 : PPC64 {
static constexpr std::string_view target_name = "ppc64v2";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_PPC64;
static constexpr u32 plt_hdr_size = 60;
static constexpr u32 plt_hdr_size = 52;
static constexpr u32 plt_size = 4;
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 20;
static constexpr u32 R_COPY = R_PPC64_COPY;
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_RELATIVE = R_PPC64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
};
struct S390X {
@@ -1943,14 +2048,15 @@ struct S390X {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_S390X;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 32;
static constexpr u32 plt_hdr_size = 48;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_390_COPY;
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_390_JMP_SLOT;
static constexpr u32 R_ABS = R_390_64;
static constexpr u32 R_DYNAMIC = R_390_64;
static constexpr u32 R_RELATIVE = R_390_RELATIVE;
static constexpr u32 R_IRELATIVE = R_390_IRELATIVE;
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
@@ -1973,6 +2079,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 +2102,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 +2124,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 +2146,61 @@ 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 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);
+18 -24
View File
@@ -65,7 +65,7 @@
// conditions.
#include "mold.h"
#include "../sha.h"
#include "blake3.h"
#include <array>
#include <cstdio>
@@ -81,7 +81,7 @@ static constexpr int64_t HASH_SIZE = 16;
typedef std::array<uint8_t, HASH_SIZE> Digest;
namespace std {
template<> struct hash<Digest> {
template <> struct hash<Digest> {
size_t operator()(const Digest &k) const {
return *(int64_t *)&k[0];
}
@@ -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");
@@ -142,9 +132,11 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
}
static Digest digest_final(SHA256Hash &sha) {
u8 buf[SHA256_SIZE];
sha.finish(buf);
static Digest digest_final(blake3_hasher *hasher) {
assert(HASH_SIZE <= BLAKE3_OUT_LEN);
u8 buf[BLAKE3_OUT_LEN];
blake3_hasher_finalize(hasher, buf, BLAKE3_OUT_LEN);
Digest digest;
memcpy(digest.data(), buf, HASH_SIZE);
@@ -244,15 +236,16 @@ static void merge_leaf_nodes(Context<E> &ctx) {
template <typename E>
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
SHA256Hash sha;
blake3_hasher hasher;
blake3_hasher_init(&hasher);
auto hash = [&](auto val) {
sha.update((u8 *)&val, sizeof(val));
blake3_hasher_update(&hasher, (u8 *)&val, sizeof(val));
};
auto hash_string = [&](std::string_view str) {
hash(str.size());
sha.update((u8 *)str.data(), str.size());
blake3_hasher_update(&hasher, (u8 *)str.data(), str.size());
};
auto hash_symbol = [&](Symbol<E> &sym) {
@@ -308,7 +301,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
hash_symbol(*isec.file.symbols[rel.r_sym]);
}
return digest_final(sha);
return digest_final(&hasher);
}
template <typename E>
@@ -421,16 +414,17 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
if (converged.get(i))
return;
SHA256Hash sha;
sha.update(digests[2][i].data(), HASH_SIZE);
blake3_hasher hasher;
blake3_hasher_init(&hasher);
blake3_hasher_update(&hasher, digests[2][i].data(), HASH_SIZE);
i64 begin = edge_indices[i];
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
for (i64 j : edges.subspan(begin, end - begin))
sha.update(digests[slot][j].data(), HASH_SIZE);
blake3_hasher_update(&hasher, digests[slot][j].data(), HASH_SIZE);
digests[!slot][i] = digest_final(sha);
digests[!slot][i] = digest_final(&hasher);
if (digests[slot][i] == digests[!slot][i]) {
// This node has converged. Skip further iterations as it will
@@ -573,7 +567,7 @@ void icf_sections(Context<E> &ctx) {
}
}
// Group sections by SHA digest.
// Group sections by BLAKE3 digest.
{
Timer t(ctx, "group");
+186 -252
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.
@@ -99,7 +104,7 @@ static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
template <typename E>
void
ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
ObjectFile<E>::parse_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
std::string_view data = this->get_string(ctx, shdr);
while (!data.empty()) {
@@ -135,6 +140,57 @@ ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
}
}
// <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) {
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: {
i64 pos = p.find_first_of('\0');
file.extra.arch = p.substr(0, pos);
p = p.substr(pos + 1);
break;
}
case ELF_TAG_RISCV_UNALIGNED_ACCESS:
file.extra.unaligned_access = read_uleb(&p);
break;
default:
break;
}
}
}
}
template <typename E>
void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// Read sections
@@ -145,6 +201,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 +222,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 +248,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;
@@ -211,7 +283,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
}
if (name == ".note.gnu.property") {
read_note_gnu_property(ctx, shdr);
parse_note_gnu_property(ctx, shdr);
continue;
}
@@ -235,12 +307,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 +316,18 @@ 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 (name == ".eh_frame")
eh_frame_section = this->sections[i].get();
if constexpr (is_ppc32<E>)
if (name == ".got2")
ppc32_got2 = this->sections[i].get();
extra.got2 = this->sections[i].get();
// Save debug sections for --gdb-index.
if (ctx.arg.gdb_index) {
@@ -313,17 +388,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
}
}
template <typename E>
void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
for (i64 i = 0; i < sections.size(); i++) {
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;
}
}
}
// .eh_frame contains data records explaining how to handle exceptions.
// When an exception is thrown, the runtime searches a record from
// .eh_frame with the current program counter as a key. A record that
@@ -354,7 +418,11 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
//
// This function parses an input .eh_frame section.
template <typename E>
void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
if (!eh_frame_section)
return;
InputSection<E> &isec = *eh_frame_section;
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
i64 cies_begin = cies.size();
i64 fdes_begin = fdes.size();
@@ -490,7 +558,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 +573,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 +594,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 +642,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 +654,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 +726,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 +742,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 +779,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 +797,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 +874,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,12 +904,15 @@ 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);
initialize_symbols(ctx);
sort_relocations(ctx);
initialize_ehframe_sections(ctx);
parse_ehframe(ctx);
}
// Symbols with higher priorities overwrites symbols with lower priorities.
@@ -854,22 +928,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 +1017,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 +1046,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 +1056,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 +1068,11 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
for (ElfRel<E> &rel : cie.get_rels()) {
Symbol<E> &sym = *this->symbols[rel.r_sym];
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 +1148,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));
}
@@ -1238,9 +1184,6 @@ void ObjectFile<E>::compute_symtab_size(Context<E> &ctx) {
this->output_sym_indices.resize(this->elf_syms.size(), -1);
auto is_alive = [&](Symbol<E> &sym) -> bool {
if (!ctx.arg.gc_sections)
return true;
if (SectionFragment<E> *frag = sym.get_frag())
return frag->is_alive;
if (InputSection<E> *isec = sym.get_input_section())
@@ -1286,31 +1229,29 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
i64 strtab_off = this->strtab_offset;
auto write_sym = [&](Symbol<E> &sym, i64 &symtab_idx) {
auto write_sym = [&](Symbol<E> &sym, i64 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) + idx;
symtab_base[symtab_idx++] = to_output_esym(ctx, sym, strtab_off, xindex);
symtab_base[idx] = to_output_esym(ctx, sym, strtab_off, xindex);
strtab_off += write_string(strtab_base + strtab_off, sym.name());
};
i64 local_symtab_idx = this->local_symtab_idx;
i64 global_symtab_idx = this->global_symtab_idx;
for (i64 i = 1; i < this->first_global; i++) {
Symbol<E> &sym = *this->symbols[i];
if (sym.write_to_symtab)
write_sym(sym, local_symtab_idx);
}
for (i64 i = 1; i < this->first_global; i++)
if (Symbol<E> &sym = *this->symbols[i]; sym.write_to_symtab)
write_sym(sym, local_symtab_idx++);
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
Symbol<E> &sym = *this->symbols[i];
if (sym.file == this && sym.write_to_symtab) {
if (sym.is_local(ctx))
write_sym(sym, local_symtab_idx);
write_sym(sym, local_symtab_idx++);
else
write_sym(sym, global_symtab_idx);
write_sym(sym, global_symtab_idx++);
}
}
}
@@ -1341,7 +1282,6 @@ SharedFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
template <typename E>
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
: InputFile<E>(ctx, mf) {
this->is_needed = ctx.as_needed;
this->is_alive = !ctx.as_needed;
}
@@ -1481,8 +1421,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 +1436,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_dso &&
!sym.file->is_alive.test_and_set()) {
feeder(sym.file);
if (sym.is_traced)
@@ -1510,7 +1448,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 +1464,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 +1489,13 @@ 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_type == PT_GNU_RELRO) &&
!(phdr.p_flags & PF_W) &&
phdr.p_vaddr <= val && val < phdr.p_vaddr + phdr.p_memsz)
return true;
return false;
}
@@ -1601,8 +1535,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());
+168 -100
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) {
@@ -171,78 +150,125 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
case NONE:
break;
case ERROR:
// Print out the "recompile with -fPIC" error message.
error();
break;
case COPYREL:
// Create a copy relocation.
if (!ctx.arg.z_copyreloc)
error();
copyrel();
break;
case DYN_COPYREL:
// Same as COPYREL but try to avoid creating a copy relocation by
// creating a dynamic relocation instead if the relocation is in
// a writable section.
//
// GHC (Glasgow Haskell Compiler) places a small amount of data in
// .text before each function and access that data with a fixed
// offset. The function breaks if we copy-relocate the data. For such
// programs, we should avoid copy relocations if possible.
//
// Besides GHC, copy relocation is a hacky solution, so if we can
// represent a relocation either with copyrel or dynrel, we prefer
// dynamic relocation.
if (writable || !ctx.arg.z_copyreloc)
dynrel();
else
copyrel();
break;
case PLT:
// Create a PLT entry.
sym.flags |= NEEDS_PLT;
break;
case CPLT:
// Create a canonical PLT entry.
sym.flags |= NEEDS_CPLT;
break;
case DYN_CPLT:
// Same as CPLT but try to avoid creating a canonical PLT creating by
// creating a dynamic relocation instead if the relocation is in a
// writable section. The motivation behind it is hte same as DYN_COPYREL.
if (writable)
dynrel();
else
sym.flags |= NEEDS_CPLT;
break;
case DYNREL:
// Create a dynamic relocation.
dynrel();
break;
case BASEREL:
// Create a base relocation.
check_textrel();
if (!isec.is_relr_reloc(ctx, rel))
isec.file.num_dynrel++;
break;
case IFUNC:
// Create an IRELATIVE relocation for a GNU ifunc symbol.
//
// We usually create an IRELATIVE relocation in .got for each ifunc.
// However, if a statically-initialized pointer is initialized to an
// ifunc's address, we have no choice other than emitting an IRELATIVE
// relocation for each such pointer.
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>
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
// This is a decision table for absolute relocations that is smaller
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
// than the pointer size (e.g. R_X86_64_32). Since the dynamic linker
// generally does not support dynamic relocations smaller than the
// word size, we need to report an error if a relocation cannot be
// pointer 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>
@@ -250,17 +276,17 @@ static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC;
// This is a decision table for absolute relocations for the word
// This is a decision table for absolute relocations for the pointer
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
// 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 +298,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 +332,38 @@ 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>::scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
if (ctx.arg.is_static ||
(ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// Relax TLSDESC to Local Exec. In this case, we directly materialize
// a TP-relative offset, so no dynamic relocation is needed.
//
// TLSDESC relocs must always be relaxed for statically-linked
// executables even if -no-relax is given. It is because a
// statically-linked executable doesn't contain a trampoline
// function needed for TLSDESC.
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
// In this condition, TP-relative offset of a thread-local variable
// is known at process startup time, so we can relax TLSDESC to the
// code that reads the TP-relative offset from GOT and add TP to it.
sym.flags |= NEEDS_GOTTP;
} else {
// If no relaxation is doable, we simply create a TLSDESC dynamic
// relocation.
sym.flags |= NEEDS_TLSDESC;
}
}
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 +371,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 +394,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 +426,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 +435,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 +446,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 +462,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 +475,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
+5 -6
View File
@@ -279,9 +279,8 @@ static bool read_label(std::span<std::string_view> &tok,
template <typename E>
static void
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
std::string_view ver_str, u16 ver_idx, bool is_cpp) {
bool is_global = true;
std::string_view ver_str, u16 ver_idx,
bool is_global, bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (read_label(tok, "global")) {
is_global = true;
@@ -299,11 +298,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script_commands( ctx, tok, ver_str, ver_idx, false);
read_version_script_commands( ctx, tok, ver_str, ver_idx, is_global, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
read_version_script_commands(ctx, tok, ver_str, ver_idx, is_global, true);
}
tok = skip(ctx, tok, "}");
@@ -349,7 +348,7 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
}
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, false);
read_version_script_commands(ctx, tok, ver_str, ver_idx, true, false);
tok = skip(ctx, tok, "}");
if (!tok.empty() && tok[0] != ";")
tok = tok.subspan(1);
+33 -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;
}
}
}
});
@@ -692,6 +709,9 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
get_symbol(ctx, y)->referenced_by_regular_obj = true;
}
// Handle --entry
get_symbol(ctx, ctx.arg.entry)->referenced_by_regular_obj = true;
// all_symbols_read_hook() calls add_input_file() and add_input_library()
LOG << "all symbols read\n";
if (PluginStatus st = all_symbols_read_hook(); st != LDPS_OK)
+2 -4
View File
@@ -1,6 +1,6 @@
#pragma once
#include "../integers.h"
#include "../common/integers.h"
namespace mold {
@@ -55,9 +55,7 @@ enum PluginApiVersion {
struct PluginTagValue {
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
template <typename T>
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
PluginTagValue(PluginTag tag, auto *ptr) : tag(tag), ptr((void *)ptr) {}
PluginTag tag;
union {
+31 -23
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,26 @@ 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_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 +163,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 +174,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 +183,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;
}
@@ -339,7 +341,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
// Since elf_main is a template, we can't run it without a type parameter.
// We speculatively run elf_main with X86_64, and if the speculation was
// wrong, re-run it with an actual machine type.
template <typename E>
static int redo_main(int argc, char **argv, std::string_view target) {
if (target == I386::target_name)
return elf_main<I386>(argc, argv);
@@ -371,6 +372,10 @@ static int redo_main(int argc, char **argv, std::string_view target) {
return elf_main<SH4>(argc, argv);
if (target == ALPHA::target_name)
return elf_main<ALPHA>(argc, argv);
if (target == LOONGARCH32::target_name)
return elf_main<LOONGARCH32>(argc, argv);
if (target == LOONGARCH64::target_name)
return elf_main<LOONGARCH64>(argc, argv);
unreachable();
}
@@ -397,7 +402,7 @@ int elf_main(int argc, char **argv) {
// Redo if -m is not x86-64.
if constexpr (is_x86_64<E>)
if (ctx.arg.emulation != X86_64::target_name)
return redo_main<E>(argc, argv, ctx.arg.emulation);
return redo_main(argc, argv, ctx.arg.emulation);
Timer t_all(ctx, "all");
@@ -416,6 +421,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 +449,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 +470,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 +555,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 +718,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 +735,8 @@ int elf_main(int argc, char **argv) {
if (on_complete)
on_complete();
release_global_lock(ctx);
if (ctx.arg.quick_exit)
_exit(0);
@@ -738,10 +746,6 @@ int elf_main(int argc, char **argv) {
return 0;
}
using E = MOLD_TARGET;
template void read_file(Context<E> &, MappedFile<Context<E>> *);
#ifdef MOLD_X86_64
extern template int elf_main<I386>(int, char **);
@@ -759,6 +763,8 @@ extern template int elf_main<SPARC64>(int, char **);
extern template int elf_main<M68K>(int, char **);
extern template int elf_main<SH4>(int, char **);
extern template int elf_main<ALPHA>(int, char **);
extern template int elf_main<LOONGARCH32>(int, char **);
extern template int elf_main<LOONGARCH64>(int, char **);
int main(int argc, char **argv) {
return elf_main<X86_64>(argc, argv);
@@ -766,6 +772,8 @@ int main(int argc, char **argv) {
#else
using E = MOLD_TARGET;
template int elf_main<E>(int, char **);
#endif
+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) {
+257 -189
View File
File diff suppressed because it is too large. Load diff
+409 -404
View File
File diff suppressed because it is too large. Load diff
+330 -242
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>
@@ -34,7 +33,7 @@ void apply_exclude_libs(Context<E> &ctx) {
template <typename E>
void create_synthetic_sections(Context<E> &ctx) {
auto push = [&]<typename T>(T *x) {
auto push = [&](auto *x) {
ctx.chunks.push_back(x);
ctx.chunk_pool.emplace_back(x);
return x;
@@ -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())
@@ -105,37 +105,34 @@ void create_synthetic_sections(Context<E> &ctx) {
if (ctx.arg.emit_relocs)
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie)
if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie) {
ctx.dynamic = push(new DynamicSection<E>);
// If .dynamic exists, .dynsym and .dynstr must exist as well
// since .dynamic refers to them.
ctx.dynstr->shdr.sh_size = 1;
ctx.dynsym->symbols.resize(1);
}
ctx.versym = push(new VersymSection<E>);
ctx.verneed = push(new VerneedSection<E>);
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_alpha<E>)
ctx.extra.got = push(new AlphaGotSection);
// If .dynamic exists, .dynsym and .dynstr must exist as well
// since .dynamic refers them.
if (ctx.dynamic) {
ctx.dynstr->keep();
ctx.dynsym->keep();
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");
}
ctx.tls_get_addr = get_symbol(ctx, "__tls_get_addr");
ctx.tls_get_offset = get_symbol(ctx, "__tls_get_offset");
if constexpr (is_alpha<E>)
ctx.extra.got = push(new AlphaGotSection);
}
template <typename E>
@@ -225,11 +222,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 +275,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 +292,24 @@ 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");
for (ObjectFile<E> *file : ctx.objs)
if (file->eh_frame_section)
file->eh_frame_section->is_alive = false;
}
template <typename E>
void resolve_section_pieces(Context<E> &ctx) {
Timer t(ctx, "resolve_section_pieces");
@@ -331,24 +349,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 +364,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);
@@ -394,13 +400,10 @@ static u64 canonicalize_type(std::string_view name, u64 type) {
}
struct OutputSectionKey {
bool operator==(const OutputSectionKey &) const = default;
std::string_view name;
u64 type;
u64 flags;
bool operator==(const OutputSectionKey &other) const {
return name == other.name && type == other.type && flags == other.flags;
}
};
template <typename E>
@@ -443,7 +446,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) {
@@ -461,10 +464,7 @@ get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
const ElfShdr<E> &shdr = isec.shdr();
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
u64 type = canonicalize_type<E>(name, shdr.sh_type);
u64 flags = shdr.sh_flags & ~(u64)SHF_COMPRESSED;
if (!ctx.arg.relocatable)
flags &= ~(u64)SHF_GROUP & ~(u64)SHF_GNU_RETAIN;
u64 flags = shdr.sh_flags & ~(u64)(SHF_COMPRESSED | SHF_GROUP | SHF_GNU_RETAIN);
// .init_array is usually writable. We don't want to create multiple
// .init_array output sections, so make it always writable.
@@ -479,7 +479,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,61 +488,90 @@ 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;
i64 size = ctx.osec_pool.size();
// 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;
}
const ElfShdr<E> &shdr = isec->shdr();
if (ctx.arg.relocatable && (shdr.sh_flags & SHF_GROUP)) {
OutputSection<E> *osec =
new OutputSection<E>(ctx, isec->name(), shdr.sh_type, shdr.sh_flags);
isec->output_section = osec;
ctx.osec_pool.emplace_back(osec);
continue;
}
std::unique_ptr<OutputSection<E>> osec =
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
std::unique_lock lock(mu);
OutputSectionKey key = get_output_section_key(ctx, *isec);
auto [it, inserted] = map.insert({key, osec.get()});
isec->output_section = it->second;
if (inserted)
ctx.osec_pool.emplace_back(std::move(osec));
if (auto it = cache.find(key); it != cache.end()) {
isec->output_section = it->second;
continue;
}
auto get_or_insert = [&] {
{
std::shared_lock lock(mu);
if (auto it = map.find(key); it != map.end())
return it->second;
}
std::unique_ptr<OutputSection<E>> osec =
std::make_unique<OutputSection<E>>(ctx, key.name, key.type, key.flags);
std::unique_lock lock(mu);
auto [it, inserted] = map.insert({key, osec.get()});
OutputSection<E> *ret = it->second;
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});
}
});
// Add input sections to output sections
std::vector<Chunk<E> *> chunks;
for (i64 i = size; i < ctx.osec_pool.size(); i++)
chunks.push_back(ctx.osec_pool[i].get());
for (ObjectFile<E> *file : ctx.objs)
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (isec && isec->is_alive)
isec->output_section->members.push_back(isec.get());
// Add output sections and mergeable sections to ctx.chunks
std::vector<Chunk<E> *> vec;
for (std::pair<const OutputSectionKey, OutputSection<E> *> &kv : map)
vec.push_back(kv.second);
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
if (osec->shdr.sh_size)
vec.push_back(osec.get());
chunks.push_back(osec.get());
// Sections are added to the section lists in an arbitrary order
// because they are created in parallel. Sort them to to make the
// output deterministic.
tbb::parallel_sort(vec.begin(), vec.end(), [](Chunk<E> *x, Chunk<E> *y) {
tbb::parallel_sort(chunks.begin(), chunks.end(), [](Chunk<E> *x, Chunk<E> *y) {
return std::tuple(x->name, x->shdr.sh_type, x->shdr.sh_flags) <
std::tuple(y->name, y->shdr.sh_type, y->shdr.sh_flags);
});
append(ctx.chunks, vec);
append(ctx.chunks, chunks);
}
// Create a dummy object file containing linker-synthesized
@@ -615,7 +644,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 +657,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};
@@ -649,10 +678,10 @@ template <typename E>
void add_synthetic_symbols(Context<E> &ctx) {
ObjectFile<E> &obj = *ctx.internal_obj;
auto add = [&](std::string_view name) {
auto add = [&](std::string_view name, u32 type = STT_NOTYPE) {
ElfSym<E> esym;
memset(&esym, 0, sizeof(esym));
esym.st_type = STT_NOTYPE;
esym.st_type = type;
esym.st_shndx = SHN_ABS;
esym.st_bind = STB_GLOBAL;
esym.st_visibility = STV_HIDDEN;
@@ -698,7 +727,7 @@ void add_synthetic_symbols(Context<E> &ctx) {
ctx.__dso_handle = add("__dso_handle");
if constexpr (supports_tlsdesc<E>)
ctx._TLS_MODULE_BASE_ = add("_TLS_MODULE_BASE_");
ctx._TLS_MODULE_BASE_ = add("_TLS_MODULE_BASE_", STT_TLS);
if constexpr (is_riscv<E>)
if (!ctx.arg.shared)
@@ -728,7 +757,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 +794,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 +833,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())
@@ -962,33 +969,30 @@ template <typename E>
void check_symbol_types(Context<E> &ctx) {
Timer t(ctx, "check_symbol_types");
auto check = [&](InputFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
std::vector<InputFile<E> *> files;
append(files, ctx.objs);
append(files, ctx.dsos);
if (!sym.file)
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
Symbol<E> &sym = *file->symbols[i];
if (!sym.file || sym.file == file)
continue;
u32 x = sym.esym().st_type;
if (x == STT_GNU_IFUNC)
x = STT_FUNC;
const ElfSym<E> &esym1 = sym.esym();
const ElfSym<E> &esym2 = file->elf_syms[i];
u32 y = esym.st_type;
if (y == STT_GNU_IFUNC)
y = STT_FUNC;
u32 ty1 = (esym1.st_type == STT_GNU_IFUNC) ? STT_FUNC : esym1.st_type;
u32 ty2 = (esym2.st_type == STT_GNU_IFUNC) ? STT_FUNC : esym2.st_type;
if (x != STT_NOTYPE && y != STT_NOTYPE && x != y)
if (ty1 != STT_NOTYPE && ty2 != STT_NOTYPE && ty1 != ty2)
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
<< ">>> defined in " << *sym.file << " as "
<< stt_to_string<E>(sym.esym().st_type) << '\n'
<< stt_to_string<E>(esym1.st_type) << '\n'
<< ">>> defined in " << *file << " as "
<< stt_to_string<E>(esym.st_type);
<< stt_to_string<E>(esym2.st_type);
}
};
tbb::parallel_for_each(ctx.objs, check);
tbb::parallel_for_each(ctx.dsos, check);
});
}
template <typename E>
@@ -1011,8 +1015,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 +1058,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 +1100,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();
}
}
@@ -1159,19 +1169,16 @@ void compute_section_sizes(Context<E> &ctx) {
}
});
i64 offset = 0;
i64 p2align = 0;
ElfShdr<E> &shdr = osec->shdr;
shdr.sh_size = 0;
for (i64 i = 0; i < groups.size(); i++) {
offset = align_to(offset, 1 << groups[i].p2align);
groups[i].offset = offset;
offset += groups[i].size;
p2align = std::max(p2align, groups[i].p2align);
shdr.sh_size = align_to(shdr.sh_size, 1 << groups[i].p2align);
groups[i].offset = shdr.sh_size;
shdr.sh_size += groups[i].size;
shdr.sh_addralign = std::max<u32>(shdr.sh_addralign, 1 << groups[i].p2align);
}
osec->shdr.sh_size = offset;
osec->shdr.sh_addralign = 1 << p2align;
// Assign offsets to input sections.
tbb::parallel_for_each(groups, [](Group &group) {
i64 offset = group.offset;
@@ -1191,30 +1198,103 @@ void compute_section_sizes(Context<E> &ctx) {
// inserting thunks. This pass cannot be parallelized. That is,
// create_range_extension_thunks is parallelized internally, but the
// function itself is not thread-safe.
if constexpr (needs_thunk<E>) {
for (Chunk<E> *chunk : ctx.chunks) {
OutputSection<E> *osec = chunk->to_osec();
if (osec && (osec->shdr.sh_flags & SHF_EXECINSTR) && !ctx.arg.relocatable) {
create_range_extension_thunks(ctx, *osec);
for (InputSection<E> *isec : osec->members)
osec->shdr.sh_addralign =
std::max<u32>(osec->shdr.sh_addralign, 1 << isec->p2align);
}
}
}
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (u32 align = ctx.arg.section_align[osec->name])
osec->shdr.sh_addralign = std::max<u32>(osec->shdr.sh_addralign, align);
if constexpr (needs_thunk<E>)
if (!ctx.arg.relocatable)
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_flags & SHF_EXECINSTR)
osec->create_range_extension_thunks(ctx);
}
// Find all unresolved symbols and attach them to the most appropriate files.
// 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 +1327,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 +1347,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 +1367,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,9 +1380,6 @@ 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();
@@ -1336,6 +1387,36 @@ void scan_relocations(Context<E> &ctx) {
Warn(ctx) << "creating a DT_TEXTREL in an output file";
}
// Report all undefined symbols, grouped by symbol.
template <typename E>
void report_undef_errors(Context<E> &ctx) {
constexpr i64 max_errors = 3;
for (auto &pair : ctx.undef_errors) {
std::string_view sym_name = pair.first;
std::span<std::string> errors = pair.second;
if (ctx.arg.demangle)
sym_name = demangle(sym_name);
std::stringstream ss;
ss << "undefined symbol: " << sym_name << "\n";
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
ss << errors[i];
if (errors.size() > max_errors)
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
Error(ctx) << ss.str();
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
Warn(ctx) << ss.str();
}
ctx.checkpoint();
}
template <typename E>
void create_reloc_sections(Context<E> &ctx) {
Timer t(ctx, "create_reloc_sections");
@@ -1376,6 +1457,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>)
@@ -1387,11 +1472,8 @@ void construct_relr(Context<E> &ctx) {
Timer t(ctx, "construct_relr");
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
if (OutputSection<E> *osec = chunk->to_osec())
osec->construct_relr(ctx);
chunk->construct_relr(ctx);
});
ctx.got->construct_relr(ctx);
}
template <typename E>
@@ -1492,17 +1574,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 +1614,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 +1633,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,13 +1791,14 @@ 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) |
(!tls << 6) | (!relro << 5) | (is_bss << 4);
};
// Ties are broken by additional rules
auto get_rank2 = [&](Chunk<E> *chunk) -> i64 {
if (chunk->shdr.sh_type == SHT_NOTE)
return -chunk->shdr.sh_addralign;
@@ -1724,14 +1815,8 @@ void sort_output_sections_regular(Context<E> &ctx) {
};
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
// Sort sections by segments
i64 x = get_rank1(a);
i64 y = get_rank1(b);
if (x != y)
return x < y;
// Ties are broken by additional rules
return get_rank2(a) < get_rank2(b);
return std::tuple{get_rank1(a), get_rank2(a)} <
std::tuple{get_rank1(b), get_rank2(b)};
});
}
@@ -1840,7 +1925,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 +2232,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;
}
}
@@ -2296,7 +2382,7 @@ void fix_synthetic_symbols(Context<E> &ctx) {
// this symbol.
if (ctx._TLS_MODULE_BASE_) {
ctx._TLS_MODULE_BASE_->set_output_section(sections[0]);
ctx._TLS_MODULE_BASE_->value = ctx.tls_begin;
ctx._TLS_MODULE_BASE_->value = ctx.dtp_addr;
}
// __GNU_EH_FRAME_HDR
@@ -2397,7 +2483,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 +2596,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 +2604,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 +2614,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> &);
+4 -1
View File
@@ -40,7 +40,7 @@ namespace mold::elf {
// Create linker-synthesized sections
template <typename E>
static void r_create_synthetic_sections(Context<E> &ctx) {
auto push = [&]<typename T>(T *x) {
auto push = [&](auto *x) {
ctx.chunks.push_back(x);
ctx.chunk_pool.emplace_back(x);
return x;
@@ -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
+116 -68
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,37 +30,44 @@
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
template <typename E>
static constexpr i64 max_distance =
1LL << (is_arm64<E> ? 27 : is_arm32<E> ? 24 : 25);
using E = MOLD_TARGET;
// Returns a branch reach in bytes for a given target.
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() / 10;
// We assume that a single thunk group is smaller than 100 KiB.
static constexpr i64 max_thunk_size = 102400;
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
// Returns true if a given relocation is of type used for function calls.
template <typename E>
static bool needs_thunk_rel(const ElfRel<E> &r) {
@@ -67,12 +77,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 +115,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,10 +137,9 @@ 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() <= val && val < max_distance();
}
template <typename E>
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
for (Symbol<E> *sym : thunk.symbols) {
sym->extra.thunk_idx = -1;
@@ -125,9 +149,8 @@ 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) {
@@ -166,9 +189,9 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
}
}
template <typename E>
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
std::span<InputSection<E> *> m = osec.members;
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
std::span<InputSection<E> *> m = members;
if (m.empty())
return;
@@ -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()) {
offset = align_to(offset, 1 << m[d]->p2align);
m[d]->offset = offset;
offset += m[d]->sh_size;
@@ -206,53 +248,58 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
// to ensure progress.
c = b + 1;
while (c < m.size() &&
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size<E>)
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
c++;
// Move A forward so that A is reachable from C.
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
while (a < 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() < c_offset)
a++;
// Create a thunk for input sections between B and C and place it at D.
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
// Erase references to out-of-range thunks.
while (t < thunks.size() && thunks[t]->offset < m[a]->offset)
reset_thunk(*thunks[t++]);
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
thunk.thunk_idx = osec.thunks.size() - 1;
offset = align_to(offset, thunk.alignment);
thunk.offset = offset;
// Create a new thunk and place it at D.
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
i64 thunk_idx = thunks.size();
RangeExtensionThunk<E> *thunk = new RangeExtensionThunk<E>(*this, offset);
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);
// Scan relocations between B and C to collect symbols that need
// entries in the new 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();
// the thunk's 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,16 @@ 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 < thunks.size())
reset_thunk(*thunks[t++]);
osec.shdr.sh_size = offset;
this->shdr.sh_size = offset;
for (InputSection<E> *isec : members)
this->shdr.sh_addralign =
std::max<u32>(this->shdr.sh_addralign, 1 << isec->p2align);
}
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
using E = MOLD_TARGET;
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
#endif
} // namespace mold::elf
#endif
+213
View File
@@ -0,0 +1,213 @@
// 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>) {
// On PowerPC and m68k, TP is 0x7000 (28 KiB) past the beginning
// of the TLV block to maximize the addressable range of 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 initial TLV block, and the runtime wants to access
// them efficiently too.
return phdr->p_vaddr + 0x7000;
} else {
// RISC-V and LoongArch just uses the beginning of the main executable's
// TLV block as TP. Their 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>) {
// On PowerPC 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 of 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
+19 -10
View File
@@ -1,6 +1,6 @@
#!/bin/bash
#!/bin/sh
set -e
source /etc/os-release
. /etc/os-release
set -x
@@ -12,33 +12,42 @@ set -x
case "$ID-$VERSION_ID" in
ubuntu-20.* | pop-20.*)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
apt-get install -y cmake gcc g++ g++-10
apt-get install -y file
;;
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
apt-get install -y cmake gcc g++
apt-get install -y file
;;
fedora-*)
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
dnf install -y gcc-g++ cmake
dnf install -y glibc-static file libstdc++-static diffutils util-linux
;;
opensuse-leap-*)
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
zypper install -y make cmake gcc-c++ gcc11-c++
zypper install -y glibc-devel-static tar diffutils util-linux
;;
opensuse-tumbleweed-*)
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
zypper install -y make cmake gcc-c++
zypper install -y glibc-devel-static tar diffutils util-linux
;;
gentoo-*)
emerge-webrsync
emerge dev-util/cmake sys-libs/zlib
emerge dev-util/cmake
;;
arch-*)
arch-* | archarm-*)
pacman -Sy
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
pacman -S --needed --noconfirm base-devel cmake util-linux
;;
void-*)
xbps-install -Sy xbps
xbps-install -Sy bash make cmake gcc
xbps-install -Sy tar diffutils util-linux
;;
alpine-*)
apk update
apk add bash make linux-headers cmake gcc g++
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
set -e
. /etc/os-release
set -x
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
# to enable cross-target tests.
#
# Feel free to send me a PR if your OS is not on this list.
case "$ID-$VERSION_ID" in
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
exit 1
esac
-81
View File
@@ -1,81 +0,0 @@
#pragma once
#include <cstdint>
typedef uint8_t u8;
static constexpr int64_t SHA256_SIZE = 32;
#ifdef _WIN32
// On Windows, we use Microsoft CNG.
#include <Windows.h>
#include <bcrypt.h>
#include <ntstatus.h>
inline static BCRYPT_ALG_HANDLE get_sha256_handle() {
static std::once_flag once;
static BCRYPT_ALG_HANDLE alg;
std::call_once(once, [&] {
BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
});
return alg;
}
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
BCryptHash(get_sha256_handle(), nullptr, 0, in, len, out, SHA256_SIZE);
}
class SHA256Hash {
public:
SHA256Hash() {
BCryptCreateHash(get_sha256_handle(), &handle, nullptr, 0, nullptr, 0, 0);
}
void update(u8 *data, size_t len) {
BCryptHashData(handle, data, len, 0);
}
void finish(u8 *out) {
BCryptFinishHash(handle, out, SHA256_SIZE, 0);
}
private:
BCRYPT_HASH_HANDLE handle;
};
#else
// On Unix, we use OpenSSL or the Apple's OpenSSL-compatible API.
#ifdef __APPLE__
# define COMMON_DIGEST_FOR_OPENSSL
# include <CommonCrypto/CommonDigest.h>
# define SHA256(data, len, md) CC_SHA256(data, len, md)
#else
# define OPENSSL_SUPPRESS_DEPRECATED 1
# include <openssl/sha.h>
#endif
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
SHA256(in, len, out);
}
class SHA256Hash {
public:
SHA256Hash() {
SHA256_Init(&ctx);
}
void update(u8 *data, size_t len) {
SHA256_Update(&ctx, data, len);
}
void finish(u8 *out) {
SHA256_Final(out, &ctx);
}
private:
SHA256_CTX ctx;
};
#endif
+24 -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,25 @@ 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)
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()
+1
View File
@@ -6,6 +6,7 @@
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = s390x ] && skip
[ $MACHINE = alpha ] && 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 'add\s+ip, ip, pc' $t/log
grep -Eq 'bx\s+pc' $t/log
grep -Eq 'add\s+pc, ip, pc' $t/log
+21
View File
@@ -0,0 +1,21 @@
#!/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/exe $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
readelf -W --dynamic $t/exe > $t/log2
grep -q libbar $t/log2
! grep -q libfoo $t/log2 || false
+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
-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
int foo() { return 3; }
EOF
cat <<EOF | $CC -shared -fPIC -o $t/b.so -xc -
int bar() { return 3; }
EOF
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
int foo();
int baz() { return foo(); }
EOF
$CC -shared -o $t/c.so $t/c.o $t/a.so
cat <<EOF | $CC -c -o $t/d.o -xc -
#include <stdio.h>
int baz();
int main() {
printf("%d\n", baz());
}
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$'
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
+37 -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,35 @@ 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
if [ $MACHINE = x86_64 -o $MACHINE = i386 -o $MACHINE = arm ]; then
tlsdesc_opt=-mtls-dialect=gnu2
elif [ $MACHINE = aarch64 ]; then
tlsdesc_opt=-mtls-dialect=desc
fi
# Common functions
@@ -53,6 +69,10 @@ supports_ifunc() {
$CC -c -o /dev/null -xc - >& /dev/null
}
supports_tlsdesc() {
[ -n "$tlsdesc_opt" ]
}
skip() {
echo skipped
trap - EXIT
@@ -77,5 +97,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 @@
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -fPIC -shared -o $t/a.so -xc -
__attribute__((aligned(32))) int foo = 5;
+1
View File
@@ -4,6 +4,7 @@
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
extern int foo;
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
extern char readonly[100];
extern char readwrite[100];
static int segv = 0;
static jmp_buf buf;
void handler(int sig) {
segv = 1;
longjmp(buf, 1);
}
int main() {
signal(SIGSEGV, handler);
readwrite[0] = 5;
int x = segv;
if (setjmp(buf) == 0)
*(char *)readonly = 5;
int y = segv;
printf("sigsegv %d %d\n", x, y);
}
EOF
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
__attribute__((section (".data.rel.ro"))) char readonly[100] = "abc";
char readwrite[100] = "abc";
EOF
$CC -B. $t/a.o $t/b.so -o $t/exe -no-pie
$QEMU $t/exe | grep -q '^sigsegv 0 1$'
+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'
+15 -22
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,10 +54,8 @@ fi
if [ $MACHINE = x86_64 -o $MACHINE = aarch64 ]; then
$CXX -c -o $t/e.o $t/a.cc -mcmodel=large -fno-PIC
if [ $static = 1 ]; then
$CXX -B. -o $t/exe9 $t/e.o -static
$QEMU $t/exe9
fi
$CXX -B. -o $t/exe9 $t/e.o -no-pie $static
$QEMU $t/exe9
$CXX -B. -o $t/exe10 $t/e.o -no-pie
$QEMU $t/exe10
+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'
+8
View File
@@ -1,6 +1,8 @@
#!/bin/bash
. $(dirname $0)/common.inc
# MIPS ABIs are not compatible with .gnu.hash
cat <<EOF | $CC -o $t/a.o -c -xc -
void foo() {}
EOF
@@ -9,3 +11,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$'
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -flto -c -o $t/a.o -xc -
void _start() {}
EOF
$CC -B. -o $t/exe -flto $t/a.o -nostdlib
readelf -Ws $t/exe | grep -Eq ' _start$'
+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
+1
View File
@@ -8,6 +8,7 @@
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = sh4 ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -shared -o $t/a.so -xc -
int foo = 3;
+3
View File
@@ -3,6 +3,9 @@
[ $MACHINE = ppc64 ] && 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 1151 files, more files were not shown because too many files have changed in this diff. Show more