Compare commits

...
175 Commits
Author SHA1 Message Date
Rui Ueyama 282ca44e90 Bump mold version to 2.40.3 2025-07-30 13:42:59 +09:00
Rui Ueyama 201bc71b42 Revert f0b933820d
Emitting multiple zlib streams in a single .debug_info section broke
a few tools.

Fixes https://github.com/rui314/mold/issues/1491
2025-07-29 14:57:39 +09:00
Rui Ueyama de3a25f3ca [sh4] Use invalid instruction to fill gaps in executable sections
Quote from SH-4 CPU Core Architecture manual:

  The opcode 0xFFFD is guaranteed to be defined in any SH-4
  architecture revision. Other unused opcodes may be treated as
  reserved in any particular SH-4 implementation.

  The only undefined opcode which the architecture guarantees to cause
  a General Illegal Instruction Exception is 0xFFFD.
2025-07-28 10:06:57 +09:00
Rui Ueyama 5aa8809932 Refactor 2025-07-28 10:06:57 +09:00
Rui Ueyama ce04287237 [s390x] Fill gaps in executable sections with invalid insns instead of NOPs 2025-07-28 10:06:57 +09:00
Rui Ueyama 5485fdec1b [PPC64LE] Support R_PPC64_GOT16* relocations
Fixes https://github.com/rui314/mold/issues/1490
2025-07-27 18:33:25 +09:00
Rui Ueyama 69fee4966f Make --sysroot work with --chroot 2025-07-24 11:20:29 +09:00
Rui Ueyama 1b76a11af2 Upgrade bundled mimalloc to 3.0.8 2025-07-22 14:40:02 +09:00
Rui Ueyama a531ce43b7 Build daily Windows archives 2025-07-19 16:41:13 +09:00
Rui Ueyama 6fb9564b4a Build a Zip file for Windows binary distribution 2025-07-18 09:44:16 +09:00
Rui Ueyama 4e44823cc0 Refactor 2025-07-15 15:33:51 +09:00
Rui Ueyama dd44e4768d Add a comment 2025-07-15 15:29:43 +09:00
Rui Ueyama 00efcf3d36 Refactor 2025-07-15 11:55:52 +09:00
Rui Ueyama 01358f680d Remove an issue template 2025-07-15 11:43:18 +09:00
Rui Ueyama 6e8852ea2a Allow partially-quoted tokens in response files
Fixes https://github.com/rui314/mold/issues/1485
2025-07-15 10:04:16 +09:00
Rui Ueyama 12e701cf32 Fix test
Closes https://github.com/rui314/mold/issues/1483
2025-07-14 21:44:44 +09:00
Rui Ueyama 5a144e161e Merge pull request #1482 from sicherha/fix_mimalloc_on_raspberry_pi
Build bundled mimalloc library without architecture-specific optimisations
2025-07-14 14:19:52 +09:00
Christoph Erhardt 278750593f Build bundled mimalloc library without architecture-specific optimisations
On 64-bit ARM, mimalloc is built with the compiler flag
`-march=armv8.1-a` by default. This flag advises GCC to make use of
ARMv8.1-A-specific instructions such as `ldaddal`. Machines based on the
older ARMv8-A instruction set - for example the Raspberry Pi 4 -
experience 'Illegal instruction (core dumped)' crashes as a result.

To ensure compatibility with machines that do not support ARMv8.1-A,
disable architecture-specific optimisations by setting the CMake option
`MI_NO_OPT_ARCH` to `ON`.

As of mimalloc 2.2.3, 64-bit ARM is the only architecture affected by
the `MI_NO_OPT_ARCH` option.
2025-07-13 18:26:57 +02:00
Rui Ueyama b862729608 Merge pull request #1481 from Ni-g-3l/add-zorin-distrib
Add Zorin Distribution to install deps script
2025-07-12 15:30:22 +09:00
Nig3l 6a842c7182 Add Zorin Distribution to install deps script 2025-07-12 07:14:58 +02:00
Rui Ueyama e61093dfd6 Bump mold version to 2.40.2 2025-07-12 10:36:11 +09:00
Rui Ueyama a01599cb4d Refactor 2025-07-08 09:38:53 +09:00
Rui Ueyama 87e4c7f552 Update toolchains 2025-07-02 13:10:41 +09:00
Rui Ueyama fa74740703 Refactor 2025-06-30 16:10:42 +09:00
Rui Ueyama 121c0ed23d Accept DSO-only linking
Fixes https://github.com/rui314/mold/issues/1478
2025-06-29 09:34:44 +09:00
Rui Ueyama ed7ccccf54 Add comments 2025-06-27 21:54:48 +09:00
Rui Ueyama 23789a7936 Add an overflow check for R_LARCH_32_PCREL 2025-06-27 09:58:38 +09:00
Rui Ueyama df91548a62 Add comments 2025-06-26 22:39:46 +09:00
Rui Ueyama bc7e4af733 Refactor 2025-06-26 10:25:40 +09:00
Rui Ueyama 2f6cf922e9 Add comments 2025-06-25 14:17:14 +09:00
Rui Ueyama 949e72307f Check certificates when downloading source archives
Pointed out at https://github.com/rui314/mold/discussions/1477
2025-06-25 10:35:29 +09:00
Rui Ueyama b3778207fc Refactor 2025-06-24 14:18:24 +09:00
Rui Ueyama 8eafdfbd0a Add a timer 2025-06-24 13:18:47 +09:00
Rui Ueyama 72c0340ca5 Ignore -fuse-ld
GCC passes the flag to the linker for some reason.
2025-06-24 13:07:49 +09:00
Rui Ueyama 934ca5bb5b Refactor 2025-06-24 10:54:05 +09:00
Rui Ueyama c9fa7111a2 Ensure that our Integer types are compiled to single load/store instructions
It turns out that at least clang-18 fails to compile our code to load
a 32-bit native-endian value from memory using shift + bitwise OR into
a single instruction when compiled with `-march=native`. It instead
compiles the code to complicated vector instructions.

It seems that the good old bswap and memcpy are the best option to
prevent the compiler from overthinking.
2025-06-23 15:09:50 +09:00
Rui Ueyama a3682ddbfd Refactor 2025-06-23 11:20:23 +09:00
Rui Ueyama 75772401cc Make loongarch64 build reproducible 2025-06-20 14:06:24 +09:00
Rui Ueyama 36f8ead511 Refactor 2025-06-20 14:06:24 +09:00
Rui Ueyama 30303d716a Fix test 2025-06-19 10:19:46 +09:00
Rui Ueyama 281d78caea Add a test 2025-06-19 09:53:58 +09:00
Rui Ueyama e9afa21adc Merge pull request #1475 from andreas-schwab/main
[RISC-V] Fix range checks for HI20/LO12 reloc addends
2025-06-19 09:18:15 +09:00
Andreas Schwab 52c698d625 [RISC-V] Fix range checks for HI20/LO12 reloc addends
Fixes #1474

Signed-off-by: Andreas Schwab <[email protected]>
2025-06-18 15:46:46 +02:00
Rui Ueyama 7eedb949b7 [RISC-V] Add additional relocation overflow checks
Fixes https://github.com/rui314/mold/issues/1474
2025-06-18 21:00:19 +09:00
Rui Ueyama 85408b4976 Refactor 2025-06-18 16:22:58 +09:00
Rui Ueyama 4cb46531d0 Refactor 2025-06-17 15:31:17 +09:00
Rui Ueyama ea98b0cb71 Refactor 2025-06-17 11:13:50 +09:00
Rui Ueyama 27edd50815 Fix CI 2025-06-16 14:11:09 +09:00
Rui Ueyama 18ecf979b8 Make the size of the thunk entry vary based on the distance to the target 2025-06-16 14:02:58 +09:00
Rui Ueyama 47f6c2839c Long range extension thunk for ARM64 2025-06-14 10:22:29 +09:00
Rui Ueyama da144e9d23 Refactor 2025-06-12 14:34:46 +09:00
Rui Ueyama f556d2b4ae Extend CI timeout 2025-06-11 22:02:41 +09:00
Rui Ueyama f2a62f70ef Set timeout to CI 2025-06-11 21:13:20 +09:00
Rui Ueyama 1956ae6ac8 Close fd when doing LTO
Fixes https://github.com/rui314/mold/issues/1362
2025-06-11 10:21:59 +09:00
Rui Ueyama 96c798d7be Bump mold version to 2.40.1 2025-06-08 10:06:35 +09:00
Rui Ueyama 6214f7be34 Refactor 2025-06-07 14:06:13 +09:00
Rui Ueyama 9b8b666da8 Update mold.1 (automated commit) 2025-06-07 04:03:43 +00:00
Rui Ueyama 6465232f0c Update a man page
Fixes https://github.com/rui314/mold/issues/1471
2025-06-07 13:02:20 +09:00
Rui Ueyama cc32a6d559 Reduce memory usage of Aho-Corasick search 2025-06-07 12:43:54 +09:00
Rui Ueyama 6214e132f8 Add a comment 2025-06-06 15:03:25 +09:00
Rui Ueyama 6ca242b8f0 Refactor 2025-06-06 14:24:13 +09:00
Rui Ueyama 3721c52d34 Refactor 2025-06-06 10:11:28 +09:00
Rui Ueyama 341c88e426 Refactor 2025-06-05 23:01:55 +09:00
Rui Ueyama 8f3f71663e Refactor 2025-06-05 18:54:39 +09:00
Rui Ueyama 1012a4804c Refactor 2025-06-05 18:13:47 +09:00
Rui Ueyama 5962014fd8 Fix CI 2025-06-05 17:40:58 +09:00
Rui Ueyama dac20fa243 Improve multi-glob pattern matcher so that it's linear-time
I asked ChatGPT how to match multiple glob patterns simultaneously with a
given input string, while avoiding the textbook-style NFA-to-DFA conversion.
Then it suggested that I implement a bitvector-based NFA simulation
algorithm that I wasn't aware. I don't think I could have come up with it
myself easily. This is impressive. ChatGPT is so good at programming and
sometimes much better than me! It may not be oto long before AI writes all
the code for me.
2025-06-05 17:14:34 +09:00
Rui Ueyama 8fa6fc86d3 Optimize glob matcher 2025-06-05 10:32:09 +09:00
Rui Ueyama 1c64d40ab8 Do not use recursion for glob pattern matching
Our glob pattern matcher was an exponential-time implementation.
This can easily be improved by avoiding recursion as explained at
https://research.swtch.com/glob.
2025-06-05 09:12:57 +09:00
Rui Ueyama 58cf042386 Refactor 2025-06-04 14:46:21 +09:00
Rui Ueyama d2f8bffdba Rename lib/common.h -> lib/lib.h 2025-06-04 14:23:43 +09:00
Rui Ueyama 60a9e013d3 Move jobs-{unix,win32}.cc from lib/ to src/ 2025-06-04 13:30:40 +09:00
Rui Ueyama a8893494ae Revert "Use std::syncostream"
This reverts commit 2479b81f8a because
it cannot be built with clang-16.
2025-06-04 12:49:52 +09:00
Rui Ueyama 2479b81f8a Use std::syncostream 2025-06-04 10:45:04 +09:00
Rui Ueyama 4dd133a31e Move {mapped-file,signal}-{unix,win32}.cc from lib/ to src/
The distinction between lib and src no longer makes much sense since
we no longer support Mach-O.
2025-06-04 10:44:54 +09:00
Rui Ueyama 02678c8af0 Refactor 2025-06-03 19:41:31 +09:00
Rui Ueyama 29e8b83886 Refactor 2025-06-03 16:56:23 +09:00
Rui Ueyama f0b933820d Emit multiple zlib streams to compressed sections
I learned that tools supporting compressed sections generally support
concatenated zlib streams within a single compressed debug section.
Examples include GNU/LLVM binutils and libbacktrace. So let's emit
compressed data that way, because it's much easier to do.

It is actually desirable to emit multiple small compressed blocks for
extremely large debug sections, because zlib doesn't support compressed
streams larger than 4 GiB on machines where the long type is 32 bits.

Context: https://sourceware.org/pipermail/binutils/2025-June/141514.html
2025-06-03 13:53:13 +09:00
Rui Ueyama 9a2f31164b Refactor 2025-06-03 08:44:29 +09:00
Rui Ueyama 8ef93b14d6 Refactor 2025-06-02 12:03:19 +09:00
Rui Ueyama 8d57a4419d Refactor 2025-06-02 11:35:21 +09:00
Rui Ueyama 0ef77ebd98 Do not use std::vector<u8> to avoid zero-initialization 2025-06-02 09:31:15 +09:00
Rui Ueyama 00d45d317f Merge pull request #1470 from a5ehren/cachyos 2025-06-02 06:57:32 +09:00
Ehren Bendler cdb63bb2a1 Update install-build-deps.sh to know what cachyos is
It's just a popular arch fork, so put it in the arch block
2025-06-01 11:52:56 -04:00
Rui Ueyama bcd77395d4 Use & instead of \0 in sed
\0 is not a portable way to represent the entire match; & is.
2025-06-01 10:52:55 +09:00
Rui Ueyama 4074707891 Fix CI 2025-06-01 10:52:55 +09:00
Rui Ueyama 079f914898 Use all available cores to compress debug sections 2025-06-01 10:03:05 +09:00
Rui Ueyama d59c55959c Avoid zero-initializing a very large vector
Chunk<E>::uncompressed_data can be very large as it contains an entire
output section contents. std::vector<u8>::resize() zero-initializes the
contents. We want to avoid that cost.

On my AMD Threadripper 7980X machine, zero-initializing 1 GiB of
memory takes ~200 ms. I can confirm that this change shaves off ~1.2s
for an executable with 5 GiB of debug info sections in uncompressed
form.
2025-06-01 10:03:05 +09:00
Rui Ueyama 5e19601a17 Fix debug section prefix 2025-05-31 11:32:50 +09:00
Rui Ueyama 3de3dcc001 Remove unused #include 2025-05-31 09:01:12 +09:00
Rui Ueyama 0d8334e5a2 Report an error if .dynsym refers to a section whose index is >65280
Unlike .symtab, .dynsym cannot have an extended symbol table, so a
section index that is larger than 65279 is not representable in an
output.

Fixes https://github.com/rui314/mold/issues/1468
2025-05-30 11:48:19 +09:00
Rui Ueyama a731b4c58c Build distribution binaries using GitHub Actions 2025-05-29 21:01:43 +09:00
Rui Ueyama 971317f3a9 Fix CI 2025-05-29 15:00:25 +09:00
Rui Ueyama 0a55058042 Do not link with the bundled TBB if BLAKE3_USE_TBB is ON
Recent versions of BLAKE3 library can be built with OneTBB for parallelization.
If that is enabled, we don't want to build and link our bundled OneTBB to avoid
conflict.

Fixes https://github.com/rui314/mold/issues/1466
2025-05-29 11:52:28 +09:00
Rui Ueyama 32746ebbb3 Upgrade bundled blake3 to v1.8.2 2025-05-29 11:51:01 +09:00
Rui Ueyama 1f44438b63 Add Manjaro Linux 2025-05-29 11:35:10 +09:00
Rui Ueyama b53197300b Bump mold version to 2.40.0 2025-05-26 11:30:00 +09:00
Rui Ueyama 3e9321b9a0 Fix test flakiness 2025-05-26 11:30:00 +09:00
Rui Ueyama 142eeaba86 Fix CI 2025-05-25 11:42:00 +09:00
Rui Ueyama 15f6b9f966 Refactor 2025-05-24 13:43:17 +09:00
Rui Ueyama a310cd27d4 Refactor 2025-05-23 18:33:04 +09:00
Rui Ueyama 2d178eb1ed Fix ASAN failure on an invalid downcast 2025-05-22 20:35:04 +09:00
Rui Ueyama 0fdffad57f Fix an assertion failure when a word-size weak abs reloc is promoted to dynamic
Fixes https://github.com/rui314/mold/issues/1462
2025-05-22 19:53:19 +09:00
Rui Ueyama d05272c4ee Fix CI 2025-05-22 12:11:22 +09:00
Rui Ueyama 1683826f2f Fix tests
Closes https://github.com/rui314/mold/issues/1461
2025-05-22 10:34:29 +09:00
Rui Ueyama 9ddec785b1 Do not sort debug info sections unless we have a mix of DWARF32 and DWARF64 2025-05-21 21:38:55 +09:00
Rui Ueyama e4f867ad77 Stabilize test 2025-05-21 21:38:55 +09:00
Rui Ueyama b7ce902e52 Add a test 2025-05-20 22:23:10 +09:00
Rui Ueyama 133613a493 Refactor 2025-05-20 21:29:37 +09:00
Rui Ueyama b026d7119b Sort debug sections in a separate debug file 2025-05-20 18:27:56 +09:00
Rui Ueyama fc0dd85d87 Add a comment 2025-05-20 18:01:27 +09:00
Rui Ueyama 6577232458 Rename a variable 2025-05-20 16:22:19 +09:00
Rui Ueyama b504cacf25 Fix CI 2025-05-20 15:45:20 +09:00
Rui Ueyama 159ce3ba67 Place strings referenced by DWARF32 at the beginning of .debug_str 2025-05-20 15:29:50 +09:00
Rui Ueyama 4c2dae0a2d Fix CI 2025-05-20 14:11:42 +09:00
Rui Ueyama 275c53d615 Merge pull request #1459 from el-remph/static-ldso
Fix linked executable segfaulting if -I and -static are both passed
2025-05-20 11:43:36 +09:00
Rui Ueyama fdd959880a Sort not only .debug_info but also other debug info sections
I believe it is unlikely that sections such as .debug_line grow beyond
4 GiB to cause relocation overflows, but doing some extra work is
trivial, so just do it.
2025-05-20 11:35:07 +09:00
Rui Ueyama 26d827d6e7 Read not only one CU but all CUs in .debug_info 2025-05-19 23:37:58 +09:00
Rui Ueyama 858fd8c495 Refactor 2025-05-19 23:35:06 +09:00
remph c13ecc99eb Fix linked executable segfaulting if -I and -static are both passed 2025-05-19 15:27:06 +01:00
Rui Ueyama 122268d147 Add a test 2025-05-19 16:22:07 +09:00
Rui Ueyama b990467c16 Upgrade bundled OneTBB to 2022.1.0
Specifically, we need https://github.com/uxlfoundation/oneTBB/commit/3c85ad77664f6ec1d50d9aebbd2d2f21adae0e21
to fix our ASAN bot.
2025-05-19 14:58:05 +09:00
Rui Ueyama 3d866bea61 Fix CI 2025-05-19 14:26:09 +09:00
Rui Ueyama 19a1bc651f Layout DWARF32 before DWARF64 to mitigate relocation overflow issues
Previously, if the combined size of DWARF32 and DWARF64 debug
information exceeded 2^32 bytes, a relocation overflow could occur.

Since not all static libraries are built with -gdwarf64, this overflow
issue prevented some very large programs from using static libraries
built without -gdwarf64. Recompiling everything with -gdwarf64 is the
"right" way to fix it, but that's not always feasible for various
reasons.

This commit mitigates the issue.

By sorting the input .debug_info sections so that DWARF32 debug info
comes before DWARF64, a relocation overflow can occur only if the size
of DWARF32 debug info alone exceeds 2^32 bytes.
2025-05-19 14:12:39 +09:00
Rui Ueyama 18c813a21c Make all tests pass on FreeBSD/ARM64 2025-05-16 18:06:21 +09:00
Rui Ueyama af36625a02 Fix bug where --as-needed did not work if --allow-shlib-undefined is given 2025-05-16 15:40:21 +09:00
Rui Ueyama 53c1758503 Align TLS segment correctly
This effectively reverts 5249edc57f.
2025-05-16 11:42:26 +09:00
Rui Ueyama 67ceb41b79 Refactor 2025-05-15 16:08:50 +09:00
Rui Ueyama 7d99824c3d Bump mold version to 2.39.1 2025-05-12 11:26:07 +09:00
Rui Ueyama 344dcff8e8 Change an error mesasge 2025-05-12 10:51:51 +09:00
Rui Ueyama d0dffd5985 Attempt to fix a use-after-free
https://github.com/rui314/mold/issues/1456
2025-05-11 11:12:57 +09:00
Rui Ueyama 251041e371 Fix comment 2025-05-06 11:13:42 +09:00
Rui Ueyama 4dd872ee8d Do not make a call to an unresolved weak symbol into an infinite loop
We don't do that other than RISC-V, so let's stop doing it entirely.
2025-05-06 10:41:39 +09:00
Rui Ueyama bb1b9dda64 Refactor 2025-05-06 10:26:15 +09:00
Rui Ueyama 78ec306cf4 Merge pull request #1453 from ziyao233/riscv/relax-undef-weak-sym
shrink-sections: Relax weak undef symbols if promotion is possible
2025-05-06 08:41:41 +09:00
Yao Zi 163c62f491 shrink-sections: Relax weak undef symbols if promotion is possible
A call to weak undefined symbol could be relaxed as long as the symbol
could be promoted to a dynamic symbol, where the relaxation is done
against the known PLT address and the address of the symbol itself
doesn't really matter.

Signed-off-by: Yao Zi <[email protected]>
2025-05-05 07:01:21 -04:00
Rui Ueyama 026fb54b9d Bump mold version to 2.39.0 2025-05-05 12:27:19 +09:00
Rui Ueyama cd162ba9f6 Remove an unnecessary constraint 2025-05-05 11:47:36 +09:00
Rui Ueyama e08e7f6aa4 Do not turn a call to a resolved weak undefined symbol into an infinite loop
Fixes https://github.com/rui314/mold/issues/1451
2025-05-05 11:35:03 +09:00
Rui Ueyama 6be2e676d2 Refactor 2025-05-04 20:07:24 +09:00
Rui Ueyama b6565025a3 Merge pull request #1450 from pcc/cargo
Update instructions to enable mold in Cargo on all architectures
2025-05-04 18:01:12 +09:00
Peter Collingbourne 118dc5de8e Update instructions to enable mold in Cargo on all architectures
Make it so that the Cargo instructions enable mold for Linux on all
architectures not just x86_64.
2025-05-04 01:21:20 -07:00
Rui Ueyama f3475d9f09 Refactor 2025-05-04 16:04:33 +09:00
Rui Ueyama 299904e56f Give the lowest priority to gentoo-test script 2025-05-04 14:21:55 +09:00
Rui Ueyama fded2d8a77 Remove redundant thunks after computing section addresses
Previously, we might mistakenly remove necessary symbols from thunks,
causing the linker to crash.

This fixes https://github.com/rui314/mold/issues/1449
2025-05-04 13:36:23 +09:00
Rui Ueyama f0e726024e Remove an unused function 2025-05-03 16:27:00 +09:00
Rui Ueyama 3d29f1678d Refactor 2025-05-03 11:44:50 +09:00
Rui Ueyama 559f23ff81 Give the lowest priority to gentoo-test script 2025-05-03 11:43:53 +09:00
Rui Ueyama 3274bcbdf0 Fix spurious --no-allow-shlib-undefined error
Fixes https://github.com/rui314/mold/issues/1446
2025-05-02 21:34:01 +09:00
Rui Ueyama 83dd353979 Prevent oscillation when computing the size of the phdr
Fixes https://github.com/rui314/mold/issues/1447
2025-05-01 09:34:27 +09:00
Rui Ueyama 8bae43b842 Make --default-symver work with version scripts
Previously, symbols specified with the `global:` label were unversioned
even if --default-symver was given. Now, such symbols are versioned with
the output file's soname.

Fixes https://github.com/rui314/mold/issues/1448
2025-04-30 20:45:09 +09:00
Rui Ueyama 4038cfd64d Refactor 2025-04-30 16:20:36 +09:00
Rui Ueyama 41079c64f5 [arm32be] Report error if --be8 is missing 2025-04-30 08:24:49 +09:00
Rui Ueyama 02e410133b Refactor 2025-04-29 14:34:25 +09:00
Rui Ueyama e70236a45a Refactor 2025-04-29 13:03:28 +09:00
Rui Ueyama 285c5bb509 Refactor 2025-04-29 09:12:43 +09:00
Rui Ueyama 975da12333 Fix CI 2025-04-28 22:12:05 +09:00
Rui Ueyama 157b16adb5 Support big-endian ARM32 target 2025-04-28 21:09:59 +09:00
Rui Ueyama 564757a0f5 [RV32] Do not report error on R_RISCV_32 2025-04-28 21:09:59 +09:00
Rui Ueyama 216b4a2325 Refactor 2025-04-28 15:01:56 +09:00
Rui Ueyama 954107e29c Bump mold version to 2.38.1 2025-04-28 11:10:38 +09:00
Rui Ueyama 21e20e01da Make --no-allow-shlib-undefined not report an error on circular dependencies
Fixes https://github.com/rui314/mold/issues/1445
2025-04-28 10:50:48 +09:00
Rui Ueyama 161cb6c99c Fix CI 2025-04-26 11:40:07 +09:00
Rui Ueyama 21cfee556a Transitively test library dependencies for --no-allow-shlib-undefined
This change is needed to build sci-libs/spqr-2.0.9-r4::gentoo
2025-04-26 11:30:38 +09:00
Rui Ueyama 06203c4a72 Make dist.sh reproducible for ARM32 on ARM64 2025-04-23 15:36:06 +09:00
Rui Ueyama 232067634f Simplify 2025-04-23 13:25:15 +09:00
Rui Ueyama 3f9aab7066 Build with -Wl,--gdb-index only for Debug build 2025-04-23 12:42:27 +09:00
Rui Ueyama 8b88595f26 Make dist.sh's output deterministic
It looks like `podman build` command does not push the newly created
image with the `latest` tag to ghcr.io, so the following `podman run`
pulled an older image. That caused an issue that the scirpt created
a different tarball than the one on my local machine.

I don't know what is the "proper" fix, but it looks like adding
`--pull=never` to `podman run` forbids it from pulling an older image
from ghcr.io, solving the issue.
2025-04-23 12:31:28 +09:00
Rui Ueyama fa09c562c9 Simplify 2025-04-23 11:53:24 +09:00
Rui Ueyama 9457d993c4 Upgrade Clang 2025-04-22 18:10:19 +09:00
Rui Ueyama 43810dfecd Make --no-allow-shlib-undefined on by default for executables
Fixes https://github.com/rui314/mold/issues/1443
2025-04-22 13:20:13 +09:00
Rui Ueyama 2d6061a233 Change how we resolve versioned symbols with default version
If a versioned symbol is a default one, it can be referred to with two
names, e.g. `foo` and `foo@VERSION`. Previously, we forward
`foo@VERSION` to `foo` if it is undefined, but it didn't always work
correctly. Now, we resolve both `foo` and `foo@VERSION` as usual and
then forward `foo@VERSION` to `foo` afterwards.

Fixes https://github.com/rui314/mold/issues/1169
2025-04-22 12:21:50 +09:00
Rui Ueyama 3d6aad65b0 Simplify 2025-04-21 13:55:31 +09:00
Rui Ueyama f7b1920f84 Make dist.sh reproducible again
mimalloc 2.2.2 started using __DATE__ and __TIME__ macros which make
the output non-reproducible. We can set the SOURCE_DATE_EPOCH environment
variable to fix the "current" timestamp.
2025-04-16 16:17:28 +09:00
Rui Ueyama b42f978212 Bump mold version to 2.38.0 2025-04-16 11:57:48 +09:00
456 changed files with 18970 additions and 10878 deletions

No files matched your search

-10
View File
@@ -1,10 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
+26 -3
View File
@@ -3,12 +3,10 @@ name: Build all tarballs
on:
schedule:
- cron: '0 0 * * *'
release:
types: [ published ]
workflow_dispatch:
jobs:
build-tarballs:
build-linux:
strategy:
matrix:
include:
@@ -49,3 +47,28 @@ jobs:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
build-windows:
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Build and Archive
shell: pwsh
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
cmake --install . --config Release --prefix ../mold-install
cd ..
New-Item -ItemType Directory -Force dist | Out-Null
$version = $Env:TAG -replace '^v', ''
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: win-x86_64
path: dist/mold-*.*
+7
View File
@@ -13,6 +13,7 @@ jobs:
- '-DMOLD_USE_ASAN=On'
- '-DMOLD_USE_TSAN=On'
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: rui314/setup-mold@staging
@@ -36,6 +37,7 @@ jobs:
build-multi-archs:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: install-build-deps
@@ -72,6 +74,7 @@ jobs:
- ubuntu:22.04
- ubuntu:25.04
runs-on: ubuntu-latest
timeout-minutes: 60
container: ${{ matrix.distro }}
steps:
- uses: actions/checkout@v4
@@ -94,6 +97,7 @@ jobs:
build-macos:
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: build
@@ -105,6 +109,7 @@ jobs:
build-windows:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: build
@@ -116,6 +121,7 @@ jobs:
build-msys:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Setup MSYS2
@@ -134,6 +140,7 @@ jobs:
build-freebsd:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Build and test
+24 -10
View File
@@ -5,7 +5,7 @@ apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.06.13/riscv32-glibc-ubuntu-24.04-gcc-nightly-2025.06.13-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
@@ -18,7 +18,7 @@ done
# Install a LoongArch toolchain
mkdir /larch
wget -O- -q https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_15.1.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
@@ -27,25 +27,39 @@ for i in gcc g++ objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
# Install ARM32 big-endian toolchain
mkdir /armeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
done
# Install ARM64 big-endian toolchain
mkdir /aarch64be
wget -O- -q https://sources.buildroot.net/toolchain-external-arm-aarch64-be/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64_be-none-linux-gnu.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
ln -sf /aarch64be/aarch64_be-none-linux-gnu/libc /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
wget -O- -q https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
@@ -57,5 +71,5 @@ done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
wget -O- -q https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://downloadmirror.intel.com/850782/sde-external-9.53.0-2025-03-16-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
ln -s /sde/sde64 /usr/bin
+104
View File
@@ -0,0 +1,104 @@
name: Build & attach tarballs on tag push
on:
push:
tags: ['v[0-9]*']
permissions:
contents: write
packages: write
id-token: write
env:
TAG: ${{ github.ref_name }}
jobs:
build-linux:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Build and Archive
shell: pwsh
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
cmake --install . --config Release --prefix ../mold-install
cd ..
New-Item -ItemType Directory -Force dist | Out-Null
$version = $Env:TAG -replace '^v', ''
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
- uses: actions/upload-artifact@v4
with:
name: win-x86_64
path: dist/mold-*.*
publish:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: dist
pattern: '*'
merge-multiple: true
- name: Ensure release exists (create if missing)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
if gh release view "$TAG" >& /dev/null; then
echo "Existing release found for $TAG"
else
echo "Creating draft release for $TAG"
gh release create "$TAG" --draft \
--title "$TAG" \
--notes "Automated draft – fill the changelog later."
fi
- name: Upload tarballs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$TAG" dist/* --clobber
gh release view "$TAG" --json assets --jq '.assets[].name'
+38 -30
View File
@@ -44,7 +44,7 @@
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.37.1)
project(mold VERSION 2.40.3)
include(CMakeDependentOption)
include(CheckSymbolExists)
@@ -62,7 +62,11 @@ endif()
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
if(MOLD_USE_MOLD)
target_link_options(mold PRIVATE -fuse-ld=mold -Wl,--gdb-index)
target_link_options(mold PRIVATE -fuse-ld=mold)
if(CMAKE_BUILD_TYPE MATCHES "Deb")
target_link_options(mold PRIVATE -Wl,--gdb-index)
endif()
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
@@ -160,13 +164,13 @@ check_include_file(zstd.h HAVE_ZSTD_H)
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE zstd)
else()
set(ZSTD_BUILD_PROGRAMS OFF)
set(ZSTD_BUILD_CONTRIB OFF)
set(ZSTD_BUILD_TESTS OFF)
set(ZSTD_MULTITHREAD_SUPPORT OFF)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_BUILD_STATIC ON)
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
target_compile_definitions(libzstd_static PRIVATE
ZSTD_BUILD_STATIC=1
ZSTD_BUILD_SHARED=0
ZSTD_BUILD_PROGRAMS=0
ZSTD_MULTITHREAD_SUPPORT=0
ZSTD_BUILD_TESTS=0)
target_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
@@ -197,6 +201,7 @@ if(MOLD_USE_MIMALLOC)
function(mold_add_mimalloc)
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
set(MI_NO_OPT_ARCH ON CACHE INTERNAL "")
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
target_link_libraries(mold PRIVATE mimalloc-static)
@@ -213,7 +218,7 @@ endif()
# to mold. If you want to link to the system's libtbb2.so, pass
# -DMOLD_USE_SYSTEM_TBB=ON.
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
if(MOLD_USE_SYSTEM_TBB)
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
find_package(TBB REQUIRED)
target_link_libraries(mold PRIVATE TBB::tbb)
else()
@@ -289,10 +294,10 @@ check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
add_custom_target(git_hash
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS git-hash.cc
BYPRODUCTS mold-git-hash.h
VERBATIM)
add_dependencies(mold git_hash)
@@ -310,15 +315,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 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64)
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
LOONGARCH32 LOONGARCH64)
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/arch-arm32.cc
src/arch-arm64.cc
src/arch-loongarch.cc
src/arch-riscv.cc
src/arch-sh4.cc
src/archive-file.cc
src/cmdline.cc
src/error.cc
src/filetype.cc
src/gc-sections.cc
src/gdb-index.cc
@@ -373,19 +382,16 @@ endforeach()
# Add other non-template source files.
target_sources(mold PRIVATE
git-hash.cc
lib/aho-corasick.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/mimalloc.cc
lib/multi-glob.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/arch-arm32.cc
src/arch-i386.cc
src/arch-m68k.cc
src/arch-ppc32.cc
@@ -394,22 +400,22 @@ target_sources(mold PRIVATE
src/arch-s390x.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/config.cc
src/elf.cc
src/entry.cc
third-party/rust-demangle/rust-demangle.c
)
if(WIN32)
target_sources(mold PRIVATE
lib/jobs-win32.cc
lib/mapped-file-win32.cc
lib/signal-win32.cc
src/jobs-win32.cc
src/mapped-file-win32.cc
src/signal-win32.cc
)
else()
target_sources(mold PRIVATE
lib/jobs-unix.cc
lib/mapped-file-unix.cc
lib/signal-unix.cc
src/jobs-unix.cc
src/mapped-file-unix.cc
src/signal-unix.cc
)
endif()
@@ -448,10 +454,12 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
endfunction()
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
if(NOT WIN32)
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)
endif()
endif()
+2 -2
View File
@@ -120,7 +120,7 @@ accept an absolute path as an argument for `-fuse-ld`.
Create `.cargo/config.toml` in your project directory with the following:
```toml
[target.x86_64-unknown-linux-gnu]
[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
@@ -131,7 +131,7 @@ example above, we use `clang` as a linker driver since it always accepts the
may be able to remove the `linker = "clang"` line.
```toml
[target.x86_64-unknown-linux-gnu]
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
+78 -50
View File
@@ -1,40 +1,45 @@
#!/bin/bash
#
# This script creates a mold binary distribution. The output is written in
# `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
# `mold-1.0.3-x86_64-linux.tar.gz`).
# This script creates a mold binary distribution. The output is written to
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
# `mold-2.40.0-x86_64-linux.tar.gz`).
#
# The mold executable created by this script is statically linked to
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
# these libraries almost always exist on any Linux system. We can't
# statically link libc because doing so would disable dlopen(), which is
# necessary to open the LTO linker plugin.
#
# This script aims to produce reproducible outputs. That means if you run
# the script twice on the same git commit, it should produce bit-by-bit
# identical binary files. This property is crucial as a countermeasure
# against supply chain attacks. With this, you can verify that the binary
# files distributed on the GitHub release pages are created from the
# commit with release tags by rebuilding the binaries yourself.
# This script aims to produce reproducible outputs. That means each time
# it's run on the same git commit, it generates a bit-for-bit identical
# binary file regardless of when or where it's executed. This property
# serves as a strong safeguard against supply chain attacks. With a
# reproducible build, anyone can independently verify that the binary
# files published on our GitHub release page were built from the git
# commit tagged for release by rebuilding the binaries themselves.
#
# Debian provides snapshot.debian.org to host all historical binary
# packages. We use it to construct Podman images pinned to a
# particular timestamp.
# packages. We use it to construct a container image pinned to a
# particular timestamp. snapshot.debian.org is known to be very slow,
# but that shouldn't be a big problem for us because we only need that
# site the first time.
#
# We aim to use a reasonably old Debian version because we'll dynamically
# link glibc to mold, and a binary linked against a newer version of glibc
# won't work on a system with an older version of glibc.
# The mold executable created by this script is statically linked to
# libstdc++, but dynamically linked to glibc, libm and a few other
# libraries, as these libraries are almost always available on any Linux
# system. We can't statically link glibc because doing so would disable
# dlopen(), which is required to load the LTO linker plugin.
#
# We want to build mold with Clang rather than GCC because mold's
# We use a reasonably old Debian version for the build environment because
# a binary dynamically linked against a newer version of glibc won't work
# on a system with an older version of glibc.
#
# We prefer to build mold with Clang rather than GCC because mold's
# Identical Code Folding works best with the LLVM address significance
# table (.llvm_addrsig). Building a release binary with GCC yields a
# slightly larger binary than Clang's.
# table (.llvm_addrsig). Building a release binary with GCC produces a
# slightly larger binary than with Clang.
#
# We need a recent version of Clang to build mold. If it's not available
# via apt-get, we'll build it ourselves.
#
# You may need to install qemu-user-static package to build non-native
# binaries.
# This script can be used to create non-native binaries (e.g., building
# aarch64 binary on x86-64) because Podman automatically runs everything
# under QEMU if the container image is not native. To use this script for
# non-native builds, you may need to install the qemu-user-static package.
set -e -x
cd "$(dirname $0)"
@@ -74,21 +79,29 @@ fi
case $arch in
x86_64)
# Debian 9 (Stretch) released in June 2017.
# We use a Google-provided mirror (gcr.io) of the official Docker hub
# (docker.io) because docker.io has a strict rate limit policy.
#
# We use a Google-provided mirror (gcr.io) instead of the official Docker
# Hub (docker.io) because docker.io has a strict rate limit policy.
#
# The toolchain in Debian 9 is too old to build mold, so we rebuild it
# from source. We download source archives from official sites and build
# them locally, rather than downloading pre-built binaries from somewhere
# else, to avoid relying on unverifiable third-party binary blobs. Podman
# caches the result of each RUN command, so rebuilding is done only once
# per host.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends wget file make gcc g++ zlib1g-dev libssl-dev && \
apt-get install -y --no-install-recommends wget file make gcc g++ zlib1g-dev libssl-dev ca-certificates && \
rm -rf /var/lib/apt/lists
# Build CMake 3.27
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
wget -O- --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
./bootstrap --parallel=\$(nproc) && \
make -j\$(nproc) && \
make install && \
@@ -97,11 +110,11 @@ RUN mkdir /build && \
# Build GCC 14
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
mkdir gmp mpc mpfr && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
make -j\$(nproc) && \
make install && \
@@ -111,7 +124,7 @@ RUN mkdir /build && \
# Build GNU binutils 2.43
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
./configure --prefix=/usr && \
make -j\$(nproc) && \
make install && \
@@ -120,16 +133,16 @@ RUN mkdir /build && \
# Build Python 3.12.7
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
wget -O- --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
./configure && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build LLVM 19
# Build LLVM 20
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-20.1.3.tar.gz | tar xzf - --strip-components=1 && \
mkdir b && \
cd b && \
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
@@ -141,13 +154,13 @@ EOF
aarch64 | arm | ppc64le | s390x)
# Debian 11 (Bullseye) released in August 2021
#
# We don't want to build Clang for these targets with Qemu becuase
# that'd take extremely long time. Also I believe old build machines
# are usually x86-64.
# We don't want to build Clang for these targets on QEMU becuase it
# would take an extremely long time. Also, I believe old Linux boxes
# are typically x86-64.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake && \
@@ -170,11 +183,12 @@ RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debi
EOF
;;
loongarch64)
# LoongArch build is not reproducible yet
cat <<EOF | $image_build
FROM mirror.gcr.io/loongarch64/debian:sid
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN apt-get update && \
RUN sed -i -e 's!http[^ ]*!http://snapshot.debian.org/archive/debian-ports/20250620T014755Z!g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake && \
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
@@ -189,20 +203,34 @@ esac
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
dest=mold-$version-$arch-linux
# Source tarballs available on GitHub don't contain .git history.
# Source tarballs available on GitHub don't contain .git directory.
# Clone the repo if missing.
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
# We use the timestamp of the last Git commit as the file timestamp
# for build artifacts.
timestamp="$(git log -1 --format=%ci)"
timestamp=$(git log -1 --format=%ct)
# `uname -m` in an ARM32 container running on an ARM64 host reports it
# not as ARM32 but as ARM64. That confuses BLAKE3's cmake script and
# erroneously enables NEON SIMD instructions. `setarch` can be used to
# change the output of `uname -m`.
setarch=
[ $arch = arm ] && setarch='setarch linux32'
# Build mold in a container.
mkdir -p dist
# Build mold in a container.
#
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
# build artifacts to appear as if they were built at a specific time.
# We use it to control how the compiler expands the C/C++ __DATE__ and
# __TIME__ macros.
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image bash -c "
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
$setarch bash -c "
set -e
export SOURCE_DATE_EPOCH=$timestamp
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
@@ -212,7 +240,7 @@ cmake -DMOLD_USE_MOLD=1 .
cmake --build . -j\$(nproc)
ctest --output-on-failure -j\$(nproc)
cmake --install . --prefix $dest --strip
find $dest -print | xargs touch --no-dereference --date='$timestamp'
find $dest -print | xargs touch --no-dereference --date=@$timestamp
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
cp mold /dist
sha256sum /dist/$dest.tar.gz
+3 -1
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "MOLD" "1" "February 2025" ""
.TH "MOLD" "1" "June 2025" ""
.SH "NAME"
\fBmold\fR \- a modern linker
.SH "SYNOPSIS"
@@ -70,6 +70,8 @@ Permit or do not permit mold to create a debug info file in the background\.
.TP
\fB\-\-fork\fR, \fB\-\-no\-fork\fR
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 \fBmold\fR process\. \fB\-\-fork\fR hides that latency\. By default, it does fork\.
.IP
Note that \fB\-\-fork\fR also hides the resource usage statistics reported by time(2), since it doesn't call waitpid(2) on the child process\. If you need those statistics, pass \fB\-\-no\-fork\fR\.
.TP
\fB\-\-perf\fR
Print performance statistics\.
+4
View File
@@ -159,6 +159,10 @@ but as `-o magic`.
program, the OS kernel can take a few hundred milliseconds to terminate a
`mold` process. `--fork` hides that latency. By default, it does fork.
Note that `--fork` also hides the resource usage statistics reported by
time(2), since it doesn't call waitpid(2) on the child process. If you
need those statistics, pass `--no-fork`.
* `--perf`:
Print performance statistics.
+2 -2
View File
@@ -8,7 +8,7 @@ set -e
set -x
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian | neon)
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
apt-get update
apt-get install -y cmake gcc g++ clang gdb
;;
@@ -25,7 +25,7 @@ gentoo)
emerge-webrsync
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
;;
arch | archarm | artix | endeavouros)
arch | archarm | artix | endeavouros | manjaro | cachyos)
pacman -Sy --needed --noconfirm base-devel cmake util-linux
;;
void)
+132
View File
@@ -0,0 +1,132 @@
// This file implements the Aho-Corasick algorithm to search multiple
// strings within an input string simultaneously. It is essentially a
// trie with additional links. For details, see
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
//
// We use it for simple glob patterns in version scripts or dynamic
// list files. Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// Aho-Corasick can do only substring search, so it cannot handle
// complex glob patterns such as `*foo*bar*`. We handle such patterns
// with the Glob class.
#include "lib.h"
#include <queue>
namespace mold {
bool AhoCorasick::can_handle(std::string_view pat) {
if (pat.starts_with('*'))
pat.remove_prefix(1);
if (pat.ends_with('*'))
pat.remove_suffix(1);
return pat.find_first_of("*?[") == pat.npos;
}
i64 AhoCorasick::find(std::string_view str) {
if (nodes.empty())
return -1;
i64 idx = 0;
i64 val = -1;
auto walk = [&](u8 c) {
for (i64 j = idx; j != -1; j = nodes[j].suffix_link) {
i64 child = nodes[j].children[c];
if (child != -1) {
idx = child;
val = std::max(val, nodes[child].value);
return;
}
}
idx = 0;
};
walk('\0');
for (u8 c : str)
walk(c);
walk('\0');
return val;
}
bool AhoCorasick::add(std::string_view pat, i64 val) {
assert(can_handle(pat));
if (nodes.empty())
nodes.resize(1);
i64 idx = 0;
auto walk = [&](u8 c) {
if (nodes[idx].children[c] == -1) {
nodes[idx].children[c] = nodes.size();
nodes.resize(nodes.size() + 1);
}
idx = nodes[idx].children[c];
};
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
// substring matching, so we use \0 as a beginning/end-of-string
// markers.
if (!pat.starts_with('*'))
walk('\0');
for (u8 c : pat)
if (c != '*')
walk(c);
if (!pat.ends_with('*'))
walk('\0');
nodes[idx].value = std::max(nodes[idx].value, val);
return true;
}
void AhoCorasick::compile() {
if (nodes.empty())
return;
fix_suffix_links(0);
fix_values();
}
void AhoCorasick::fix_suffix_links(i64 idx) {
for (i64 i = 0; i < 256; i++) {
i64 child = nodes[idx].children[i];
if (child == -1)
continue;
i64 j = nodes[idx].suffix_link;
for (; j != -1; j = nodes[j].suffix_link) {
if (nodes[j].children[i] != -1) {
nodes[child].suffix_link = j;
break;
}
}
if (j == -1)
nodes[child].suffix_link = 0;
fix_suffix_links(child);
}
}
void AhoCorasick::fix_values() {
std::queue<i64> queue;
queue.push(0);
do {
i64 idx = queue.front();
queue.pop();
for (i64 child : nodes[idx].children) {
if (child != -1) {
i64 suffix = nodes[child].suffix_link;
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
queue.push(child);
}
}
} while (!queue.empty());
}
} // namespace mold
+50
View File
@@ -0,0 +1,50 @@
// This is the same as std::atomic except that the default memory
// order is relaxed instead of sequential consistency.
#pragma once
#include <atomic>
namespace mold {
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) : std::atomic<T>(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::memory_order order = relaxed) {
std::atomic<T>::store(val, order);
}
T load(std::memory_order order = relaxed) const {
return std::atomic<T>::load(order);
}
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
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);
}
};
} // namespace mold
+78
View File
@@ -0,0 +1,78 @@
#pragma once
#include "integers.h"
#include <cassert>
#include <vector>
namespace mold {
class BitvectorProxy {
public:
BitvectorProxy(u64 &word, size_t pos)
: word(word), mask(1ULL << pos) {}
BitvectorProxy &operator=(bool val) {
if (val)
word |= mask;
else
word &= ~mask;
return *this;
}
BitvectorProxy &operator=(const BitvectorProxy &other) {
return *this = (bool)other;
}
operator bool() const {
return word & mask;
}
private:
u64 &word;
u64 mask;
};
class Bitvector {
public:
Bitvector() = default;
Bitvector(i64 n) : size(n), words((n + 63) / 64) {}
void resize(i64 n) {
words.clear();
words.resize((n + 63) / 64);
size = n;
}
Bitvector &operator|=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] |= x.words[i];
return *this;
}
Bitvector &operator&=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] &= x.words[i];
return *this;
}
Bitvector &operator<<=(size_t n) {
assert(n == 1);
for (i64 i = words.size() - 1; i > 0; i--)
words[i] = (words[i] << 1) | (words[i - 1] >> 63);
words[0] <<= 1;
return *this;
}
BitvectorProxy operator[](size_t pos) {
assert(pos < size);
return {words[pos / 64], pos % 64};
}
i64 size = 0;
std::vector<u64> words;
};
} // namespace mold
+37 -46
View File
@@ -14,7 +14,7 @@
// a little bit. However, if a shard size is large enough, that loss
// is negligible in practice.
#include "common.h"
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
@@ -30,42 +30,42 @@ namespace mold {
static constexpr i64 SHARD_SIZE = 1024 * 1024;
static std::vector<std::string_view> split(std::string_view input) {
std::vector<std::string_view> shards;
while (input.size() >= SHARD_SIZE) {
shards.push_back(input.substr(0, SHARD_SIZE));
input = input.substr(SHARD_SIZE);
}
if (!input.empty())
shards.push_back(input);
return shards;
Compressor::~Compressor() {
for (std::span<u8> shard : shards)
delete[] shard.data();
}
static std::vector<u8> zlib_compress(std::string_view input) {
static std::vector<std::span<u8>> split(std::span<u8> input) {
std::vector<std::span<u8>> vec;
while (!input.empty()) {
i64 sz = std::min<i64>(SHARD_SIZE, input.size());
vec.push_back(input.subspan(0, sz));
input = input.subspan(sz);
}
return vec;
}
static std::span<u8> zlib_compress(std::span<u8> input) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, we chose compression
// level 1.
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
z_stream strm = {};
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
// Set an input buffer
strm.avail_in = input.size();
strm.next_in = (u8 *)input.data();
strm.next_in = input.data();
// Set an output buffer. deflateBound() returns an upper bound
// on the compression size. +16 for Z_SYNC_FLUSH.
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
i64 bufsize = deflateBound(&strm, strm.avail_in) + 16;
u8 *buf = new u8[bufsize];
// Compress data. It writes all compressed bytes except the last
// partial byte, so up to 7 bits can be held to be written to the
// buffer.
strm.avail_out = buf.size();
strm.next_out = buf.data();
strm.avail_out = bufsize;
strm.next_out = buf;
CHECK(deflate(&strm, Z_BLOCK));
// This is a workaround for libbacktrace before 2022-04-06.
@@ -90,23 +90,20 @@ static std::vector<u8> zlib_compress(std::string_view input) {
CHECK(deflatePrime(&strm, 10, 2));
CHECK(deflate(&strm, Z_SYNC_FLUSH));
assert(strm.avail_out > 0);
buf.resize(buf.size() - strm.avail_out);
buf.shrink_to_fit();
deflateEnd(&strm);
return buf;
return {buf, (size_t)(bufsize - strm.avail_out)};
}
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
std::vector<u64> adlers(inputs.size());
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
std::vector<u32> adlers(inputs.size());
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = zlib_compress(inputs[i]);
std::span<u8> in = inputs[i];
adlers[i] = adler32(1, in.data(), in.size());
shards[i] = zlib_compress(in);
});
// Combine checksums
@@ -116,7 +113,7 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
// Comput the total size
compressed_size = 8; // the header and the trailer
for (std::vector<u8> &shard : shards)
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
@@ -127,7 +124,7 @@ void ZlibCompressor::write_to(u8 *buf) {
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 2; // +2 for header
offsets[0] = 2; // +2 for the header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
@@ -139,26 +136,20 @@ void ZlibCompressor::write_to(u8 *buf) {
u8 *end = buf + compressed_size;
end[-6] = 3;
end[-5] = 0;
// Write a checksum
*(ub32 *)(end - 4) = checksum;
}
static std::vector<u8> zstd_compress(std::string_view input) {
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
constexpr int LEVEL = 3; // compression level; must be between 1 to 22
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
LEVEL);
static std::span<u8> zstd_compress(std::span<u8> input) {
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
u8 *buf = new u8[bufsize];
int level = 3; // compression level; must be between 1 to 22
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
assert(!ZSTD_isError(sz));
buf.resize(sz);
buf.shrink_to_fit();
return buf;
return {buf, sz};
}
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
shards.resize(inputs.size());
// Compress each shard
@@ -167,7 +158,7 @@ ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
});
compressed_size = 0;
for (std::vector<u8> &shard : shards)
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <cstdlib>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <filesystem>
#include <sys/stat.h>
+2 -2
View File
@@ -54,14 +54,14 @@ dir=gentoo/$git_hash
mkdir -p "$dir"/success "$dir"/failure
$podman nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
$podman chrt --idle 0 nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
if [ $? = 0 ]; then
mv "$dir"/"$filename".mold "$dir"/success
else
mv "$dir"/"$filename".mold "$dir"/failure
fi
$podman nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
$podman chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
if [ $? = 0 ]; then
mv "$dir"/"$filename".ld "$dir"/success
else
+172 -76
View File
@@ -1,17 +1,94 @@
#include "common.h"
// This file implements a glob matcher that can run multiple glob patterns
// against an input string. mold uses the glob matcher for symbol name
// patterns in a version script or a dynamic list file. Since we may need to
// match hundreds of glob patterns against millions of symbol names, the
// speed of the matcher is very important.
//
// The pattern match implemented in this file is NFA-based, although the
// cost of the function is O(n*m), where n is the number of NFA states and m
// is the length of the input string. We do not use recursion or
// backtracking, unlike a generic NFA-based regular expression matcher. This
// is doable because glob patterns are very limited subsets of regexes.
//
// Here is the explanation of the algorithm. Observe that the only "tricky"
// meta-character in a glob pattern is "*", which matches zero or more
// characters. Other characters and meta-characters always match a single
// input character. So the key of the algorithm is to handle "*" efficiently.
//
// We can represent a glob pattern "a*b*" with three NFA states: q_start, q1
// and q_accept, with the following transition functions:
//
// δ(q_start, "a") = q1
// δ(q1, <any character>) = q1
// δ(q1, "b") = q_accept
// δ(q_accept, <any character>) = q_accept
//
// We can construct such an NFA in a straightforward manner. We maintain NFA
// states as a list, with the initial contents being the start state. Each
// character except for "*" creates a new NFA state, adds a transition from
// the last state in the list to the new one, and appends the new state at
// the end of the list. "*" sets the "is_star" flag on the last NFA state.
// The flag indicates that the state machine can remain in the state for any
// input character.
//
// An NFA constructed this way doesn't have any complicated loops,
// ε-transitions, or anything like that. Each state has only one incoming
// edge. The only loops in the state transition are the self-loops on states
// followed by a "*". Aside from that, the state machine progresses linearly
// from the start state to the accept state.
//
// Each state of an NFA can be represented by a single bit. If a bit is 1,
// the non-deterministic state machine is in that state. Otherwise, it's
// not. Observe that a state with the "is_star" flag will continued to be 1
// once it becomes 1, since the state machine can loop over the state on any
// input character.
//
// With that observation, we can represent an NFA with a bit vector of N
// bits, where N is the number of NFA states. For each input character, bit
// M becomes 1 if
//
// - bit M-1 is 1 and there's a transition from state_{M-1} to state_M
// with the given character, or
// - bit M is 1 and state_M's "is_star" flag is 1.
//
// Initially, the 0th bit is 1 for the start state. At each step, the bits
// propagate from least significant to most significant positions, at most
// one bit at a time. If the most significant bit is 1 after the entire
// input has been processed, the string matches.
//
// This propagation can be implemented with bitwise OR, bitwise AND, and a
// one-bit bit shift on the bit vector. All these operations are very cheap.
//
// We can combine multiple glob matchers into a single matcher by simply
// concatenating the bit vectors of their state machines.
#include "lib.h"
#include <cstring>
namespace mold {
std::optional<Glob> Glob::compile(std::string_view pat) {
std::vector<Element> vec;
static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
std::vector<MultiGlob::State> vec(1);
while (!pat.empty()) {
u8 c = pat[0];
pat = pat.substr(1);
std::bitset<256> chars;
switch (c) {
case '*':
vec.back().is_star = true;
continue;
case '?':
chars.set();
break;
case '\\':
if (pat.empty())
return {};
chars[pat[0]] = true;
pat = pat.substr(1);
break;
case '[': {
// Here are a few bracket pattern examples:
//
@@ -19,17 +96,14 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [^a-z]: Any character except lowercase letters
vec.emplace_back(BRACKET);
std::bitset<256> &bitset = vec.back().bitset;
bool negate = false;
bool closed = false;
if (!pat.empty() && pat[0] == '^') {
negate = true;
pat = pat.substr(1);
}
bool closed = false;
while (!pat.empty()) {
if (pat[0] == ']') {
pat = pat.substr(1);
@@ -59,9 +133,9 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
return {};
for (i64 i = start; i <= end; i++)
bitset[i] = true;
chars[i] = true;
} else {
bitset[(u8)pat[0]] = true;
chars[pat[0]] = true;
pat = pat.substr(1);
}
}
@@ -70,87 +144,109 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
return {};
if (negate)
bitset.flip();
chars.flip();
break;
}
case '?':
vec.emplace_back(QUESTION);
break;
case '*':
vec.emplace_back(STAR);
break;
case '\\':
if (pat.empty())
return {};
if (vec.empty() || vec.back().kind != STRING)
vec.emplace_back(STRING);
vec.back().str += pat[0];
pat = pat.substr(1);
break;
default:
if (vec.empty() || vec.back().kind != STRING)
vec.emplace_back(STRING);
vec.back().str += c;
chars[c] = true;
break;
}
}
return {Glob{std::move(vec)}};
vec.push_back({chars, false});
}
return vec;
}
bool Glob::match(std::string_view str) {
return do_match(str, elements);
// Instead of returning just a match/no match boolean value, our glob
// matcher returns an integer value associated with each given pattern.
// If multiple patterns match at the same time, the largest associated
// value will be returned by find().
bool MultiGlob::add(std::string_view pat, i64 val) {
std::vector<State> vec = parse_glob(pat);
if (vec.empty())
return false;
patterns.push_back({std::move(vec), val});
return true;
}
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
while (!elements.empty()) {
Element &e = elements[0];
elements = elements.subspan(1);
void MultiGlob::compile() {
if (patterns.empty())
return;
switch (e.kind) {
case STRING:
if (!str.starts_with(e.str))
return false;
str = str.substr(e.str.size());
break;
case STAR:
if (elements.empty())
return true;
ranges::stable_sort(patterns, ranges::greater(), &GlobPattern::value);
// Patterns like "*foo*bar*" should be much more common than more
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
// the former case here.
if (elements[0].kind == STRING) {
for (;;) {
size_t pos = str.find(elements[0].str);
if (pos == str.npos)
return false;
if (do_match(str.substr(pos + elements[0].str.size()),
elements.subspan(1)))
return true;
str = str.substr(pos + 1);
}
}
std::vector<State> states;
for (GlobPattern &p : patterns)
append(states, p.states);
i64 sz = states.size();
// Other cases are handled here.
for (i64 j = 0; j < str.size(); j++)
if (do_match(str.substr(j), elements))
return true;
return false;
case QUESTION:
if (str.empty())
return false;
str = str.substr(1);
break;
case BRACKET:
if (str.empty() || !e.bitset[str[0]])
return false;
str = str.substr(1);
break;
}
start_states.resize(sz);
for (i64 pos = 0; GlobPattern &p : patterns) {
start_states[pos] = true;
pos += p.states.size();
}
return str.empty();
star_mask.resize(sz);
for (i64 i = 0; i < sz; i++)
if (states[i].is_star)
star_mask[i] = true;
for (i64 i = 0; i < 256; i++) {
char_mask[i].resize(sz);
for (i64 j = 0; j < sz; j++)
if (states[j].incoming_edge[i])
char_mask[i][j] = true;
}
}
i64 MultiGlob::find(std::string_view str) {
if (patterns.empty())
return -1;
Bitvector bits = start_states;
Bitvector tmp;
for (u8 c : str) {
// This is equivalent to
//
// bits = (bits & star_mask) | ((bits << 1) & char_mask[c])
//
// but we update the existing objects in place to avoid allocating
// temporary objects.
tmp = bits;
tmp &= star_mask;
bits <<= 1;
bits &= char_mask[c];
bits |= tmp;
}
for (i64 pos = 0; GlobPattern &p : patterns) {
pos += p.states.size();
if (bits[pos - 1])
return p.value;
}
return -1;
}
bool Glob::add(std::string_view pat, i64 val) {
assert(val >= 0);
assert(!is_compiled);
// If the pattern requires only a single substring search, the
// Aho-Corasick algorithm is even faster than our glob matcher.
if (aho_corasick.can_handle(pat))
return aho_corasick.add(pat, val);
return multi_glob.add(pat, val);
}
i64 Glob::find(std::string_view str) {
std::call_once(once, [&] {
multi_glob.compile();
aho_corasick.compile();
is_compiled = true;
});
return std::max(multi_glob.find(str), aho_corasick.find(str));
}
} // namespace mold
+1 -1
View File
@@ -4,7 +4,7 @@
// For more info, read
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
#include "common.h"
#include "lib.h"
#include <cmath>
+83 -78
View File
@@ -5,24 +5,30 @@
//
// 1. mold is always a cross linker and should not depend on what host it
// is running on. For example, users should be able to run mold on a
// big-endian SPARC machine to create a little-endian RV64 binary.
// little-endian x86 machine to create a big-endian s390x binary.
//
// 2. Even though data members in all ELF data strucutres are naturally
// aligned, they are not guaranteed to be aligned on memory because of
// archive files. Archive files (.a files) align each member only to a
// archive files. Archive files (.a files) align each file only to a
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
// in an mmap'ed memory. Misaligned access is an undefined behavior in
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
// example, to read a 32 bit value.
//
// The data types defined in this file don't depend on host byte order and
// don't do unaligned access. Note that modern compilers are smart enough
// to recognize shift and bitwise OR patterns and compile them into a
// single load instruction.
// The data types defined in this file are independent of the host byte
// order and are designed to avoid unaligned access.
//
// Note that in C/C++, memcpy is a portable and efficient way to access
// unaligned data, as it is typically treated as an intrinsic. Compilers
// can easily optimize memcpy calls in this file into a single load or
// store instruction.
#pragma once
#include <bit>
#include <cstdint>
#include <cstring>
#include <type_traits>
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@@ -36,93 +42,92 @@
namespace mold {
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
template <typename T, bool is_le, int size = sizeof(T)>
class Integer {
public:
constexpr Integer() = default;
constexpr Integer(T x) requires (is_le && size == 2)
: buf{(u8)x, (u8)(x >> 8)} {}
constexpr Integer(T x) requires (is_le && size == 3)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16)} {}
constexpr Integer(T x) requires (is_le && size == 4)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24)} {}
constexpr Integer(T x) requires (is_le && size == 8)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24),
(u8)(x >> 32), (u8)(x >> 40), (u8)(x >> 48), (u8)(x >> 56)} {}
constexpr Integer(T x) requires (!is_le && size == 2)
: buf{(u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 3)
: buf{(u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 4)
: buf{(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 8)
: buf{(u8)(x >> 56), (u8)(x >> 48), (u8)(x >> 40), (u8)(x >> 32),
(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
operator T() const {
if constexpr (is_le) {
if constexpr (size == 2)
return buf[1] << 8 | buf[0];
else if constexpr (size == 3)
return buf[2] << 16 | buf[1] << 8 | buf[0];
else if constexpr (size == 4)
return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
else
return (u64)buf[7] << 56 | (u64)buf[6] << 48 |
(u64)buf[5] << 40 | (u64)buf[4] << 32 |
(u64)buf[3] << 24 | (u64)buf[2] << 16 |
(u64)buf[1] << 8 | (u64)buf[0];
constexpr Integer(T v) {
if (std::is_constant_evaluated()) {
for (int i = 0; i < size; i++) {
int j = is_le ? i : (size - i - 1);
buf[j] = v >> (i * 8);
}
} else {
if constexpr (size == 2)
return buf[0] << 8 | buf[1];
else if constexpr (size == 3)
return buf[0] << 16 | buf[1] << 8 | buf[2];
else if constexpr (size == 4)
return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
else
return (u64)buf[0] << 56 | (u64)buf[1] << 48 |
(u64)buf[2] << 40 | (u64)buf[3] << 32 |
(u64)buf[4] << 24 | (u64)buf[5] << 16 |
(u64)buf[6] << 8 | (u64)buf[7];
store(v);
}
}
Integer &operator=(T x) {
new (this) Integer(x);
return *this;
}
operator T() const { return load(); }
Integer &operator=(T v) { store(v); return *this; }
Integer &operator++() { return *this = *this + 1; }
Integer operator++(int) { return ++*this - 1; }
Integer operator++(int) { auto x = *this; ++*this; return x; }
Integer &operator--() { return *this = *this - 1; }
Integer operator--(int) { return --*this + 1; }
Integer &operator+=(T x) { return *this = *this + x; }
Integer &operator-=(T x) { return *this = *this - x; }
Integer &operator&=(T x) { return *this = *this & x; }
Integer &operator|=(T x) { return *this = *this | x; }
Integer operator--(int) { auto x = *this; --*this; return x; }
Integer &operator+=(T v) { return *this = *this + v; }
Integer &operator-=(T v) { return *this = *this - v; }
Integer &operator&=(T v) { return *this = *this & v; }
Integer &operator|=(T v) { return *this = *this | v; }
private:
u8 buf[size];
static constexpr bool is_native =
(std::endian::native == (is_le ? std::endian::little : std::endian::big));
static T bswap(T v) {
switch (size) {
case 2: return __builtin_bswap16(v);
case 3: __builtin_unreachable();
case 4: return __builtin_bswap32(v);
case 8: return __builtin_bswap64(v);
}
}
T load() const {
if (size == 3) {
if (is_le)
return buf[2] << 16 | buf[1] << 8 | buf[0];
return buf[0] << 16 | buf[1] << 8 | buf[2];
}
T v;
memcpy(&v, buf, size);
return is_native ? v : bswap(v);
}
// We cannot merge this with the constructor because memcpy is not
// allowed to use in a compile-time constant expression.
void store(T v) {
if (size == 3) {
if (is_le) {
buf[0] = v;
buf[1] = v >> 8;
buf[2] = v >> 16;
} else {
buf[0] = v >> 16;
buf[1] = v >> 8;
buf[2] = v;
}
return;
}
if (!is_native)
v = bswap(v);
memcpy(buf, &v, size);
}
uint8_t buf[size];
};
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using il16 = Integer<i16, true>;
using il32 = Integer<i32, true>;
using il64 = Integer<i64, true>;
+54 -328
View File
@@ -1,7 +1,8 @@
#pragma once
#include "config.h"
#include "atomics.h"
#include "integers.h"
#include "bitvector.h"
#include <array>
#include <atomic>
@@ -64,199 +65,10 @@ namespace ranges = std::ranges;
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
inline char *output_tmpfile;
inline u8 *output_buffer_start = nullptr;
inline u8 *output_buffer_end = nullptr;
extern std::string mold_git_hash;
std::string errno_string();
void cleanup();
void install_signal_handler();
inline u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
//
// Error output
//
// Some C++ stdlibs don't support std::osyncstream even though
// it's is in the C++20 standard. So we implement it ourselves.
class SyncStream {
public:
SyncStream(std::ostream &out) : out(out) {}
~SyncStream() {
emit();
}
template <typename T> SyncStream &operator<<(T &&val) {
ss << std::forward<T>(val);
return *this;
}
void emit() {
if (emitted)
return;
std::scoped_lock lock(mu);
out << ss.str() << '\n';
emitted = true;
}
private:
std::ostream &out;
std::stringstream ss;
bool emitted = false;
static inline std::mutex mu;
};
template <typename Context>
class Out {
public:
Out(Context &ctx) {}
template <typename T> Out &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cout};
};
static std::string_view fatal_mono = "mold: fatal: ";
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
static std::string_view error_mono = "mold: error: ";
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
static std::string_view warning_mono = "mold: warning: ";
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
template <typename Context>
class Fatal {
public:
Fatal(Context &ctx) {
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
}
[[noreturn]] ~Fatal() {
out.emit();
cleanup();
_exit(1);
}
template <typename T> Fatal &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename Context>
class Error {
public:
Error(Context &ctx) {
if (ctx.arg.noinhibit_exec) {
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
} else {
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
}
}
template <typename T> Error &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename Context>
class Warn {
public:
Warn(Context &ctx) {
if (ctx.arg.suppress_warnings)
return;
out.emplace(std::cerr);
if (ctx.arg.fatal_warnings) {
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
} else {
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
}
}
template <typename T> Warn &operator<<(T &&val) {
if (out)
*out << std::forward<T>(val);
return *this;
}
private:
std::optional<SyncStream> 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) : std::atomic<T>(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::memory_order order = relaxed) {
std::atomic<T>::store(val, order);
}
T load(std::memory_order order = relaxed) const {
return std::atomic<T>::load(order);
}
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
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);
}
};
//
// mimalloc.cc
//
void set_mimalloc_options();
//
// perf.cc
//
@@ -667,7 +479,7 @@ public:
// Since the shard is circular, we need to handle the last entries
// as if they were next to the first entries.
while (entries[end - 1].key)
while (begin < end && entries[end - 1].key)
vec.push_back(entries + --end);
// Find entries contiguous in the buckets and sort them.
@@ -737,58 +549,72 @@ private:
};
//
// glob.cc
// aho-corasick.cc
//
class Glob {
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
struct Element {
Element(Kind k) : kind(k) {}
Kind kind;
std::string str;
std::bitset<256> bitset;
};
class AhoCorasick {
public:
static std::optional<Glob> compile(std::string_view pat);
bool match(std::string_view str);
bool add(std::string_view pat, i64 val);
bool empty() const { return nodes.empty(); }
void compile();
i64 find(std::string_view str);
static bool can_handle(std::string_view str);
private:
Glob(std::vector<Element> &&vec) : elements(vec) {}
static bool do_match(std::string_view str, std::span<Element> elements);
struct TrieNode {
TrieNode() { children.fill(-1); }
i64 value = -1;
i32 suffix_link = -1;
std::array<i32, 256> children;
};
std::vector<Element> elements;
void fix_suffix_links(i64 idx);
void fix_values();
std::vector<TrieNode> nodes;
};
//
// multi-glob.cc
// glob.cc
//
class MultiGlob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return strings.empty(); }
std::optional<i64> find(std::string_view str);
bool empty() const { return patterns.empty(); }
void compile();
i64 find(std::string_view str);
private:
struct TrieNode {
i64 value = -1;
TrieNode *suffix_link = nullptr;
std::unique_ptr<TrieNode> children[256];
struct State {
std::bitset<256> incoming_edge;
bool is_star = false;
};
void compile();
void fix_suffix_links(TrieNode &node);
void fix_values();
i64 find_aho_corasick(std::string_view str);
struct GlobPattern {
std::vector<State> states;
i64 value = -1;
};
std::vector<std::string> strings;
std::unique_ptr<TrieNode> root;
std::vector<std::pair<Glob, i64>> globs;
private:
std::vector<GlobPattern> patterns;
Bitvector start_states;
Bitvector star_mask;
Bitvector char_mask[256];
};
class Glob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return multi_glob.empty() && aho_corasick.empty(); }
i64 find(std::string_view str);
private:
std::once_flag once;
bool is_compiled = false;
bool prefix_match = false;
MultiGlob multi_glob;
AhoCorasick aho_corasick;
};
//
@@ -816,13 +642,6 @@ std::string get_self_path();
std::optional<std::string_view> demangle_cpp(std::string_view name);
std::optional<std::string_view> demangle_rust(std::string_view name);
//
// jbos.cc
//
void acquire_global_lock();
void release_global_lock();
//
// crc32.cc
//
@@ -837,8 +656,11 @@ std::vector<u8> crc32_solve(u32 current, u32 desired);
class Compressor {
public:
virtual void write_to(u8 *buf) = 0;
virtual ~Compressor() {}
virtual ~Compressor();
i64 compressed_size = 0;
protected:
std::vector<std::span<u8>> shards;
};
class ZlibCompressor : public Compressor {
@@ -847,17 +669,13 @@ public:
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
u64 checksum = 0;
u32 checksum = 0;
};
class ZstdCompressor : public Compressor {
public:
ZstdCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
};
//
@@ -884,96 +702,4 @@ private:
std::string basedir;
};
//
// Memory-mapped file
//
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
class MappedFile {
public:
~MappedFile() { unmap(); }
void unmap();
void close_fd();
void reopen_fd(const std::string &path);
template <typename Context>
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile;
mf->name = name;
mf->data = data + start;
mf->size = size;
mf->parent = this;
ctx.mf_pool.emplace_back(mf);
return mf;
}
std::string_view get_contents() {
return std::string_view((char *)data, size);
}
i64 get_offset() const {
return parent ? (data - parent->data + parent->get_offset()) : 0;
}
// Returns a string that uniquely identify a file that is possibly
// in an archive.
std::string get_identifier() const {
if (parent) {
// We use the file offset within an archive as an identifier
// because archive members may have the same name.
return parent->name + ":" + std::to_string(get_offset());
}
if (thin_parent) {
// If this is a thin archive member, the filename part is
// guaranteed to be unique.
return thin_parent->name + ":" + name;
}
return name;
}
std::string name;
u8 *data = nullptr;
i64 size = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
// For --dependency-file
bool is_dependency = true;
#ifdef _WIN32
HANDLE fd = INVALID_HANDLE_VALUE;
#else
int fd = -1;
#endif
};
MappedFile *open_file_impl(const std::string &path, std::string &error);
template <typename Context>
MappedFile *open_file(Context &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
std::string error;
MappedFile *mf = open_file_impl(path, error);
if (!error.empty())
Fatal(ctx) << error;
if (mf)
ctx.mf_pool.emplace_back(mf);
return mf;
}
template <typename Context>
MappedFile *must_open_file(Context &ctx, std::string path) {
MappedFile *mf = open_file(ctx, path);
if (!mf)
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
return mf;
}
} // namespace mold
-24
View File
@@ -1,24 +0,0 @@
#include "common.h"
// Including mimalloc-new-delete.h overrides new/delete operators.
// We need it only when we are using mimalloc as a dynamic library.
#if MOLD_USE_SYSTEM_MIMALLOC
# include <mimalloc-new-delete.h>
#endif
// Silence mimalloc warning messages that users can just ignore.
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
namespace mold {
void set_mimalloc_options() {
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
}
}
#else
namespace mold {
void set_mimalloc_options() {}
}
#endif
-186
View File
@@ -1,186 +0,0 @@
// This file implements the Aho-Corasick algorithm to match multiple
// glob patterns to symbol strings as quickly as possible.
//
// Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// `*` is a wildcard that matches any substring. We sometimes have
// hundreds of glob patterns and have to match them against millions
// of symbol strings.
//
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
// We handle such patterns with the Glob class. Glob is relatively
// slow, but complex patterns are rare in practice, so it should be
// OK.
#include "common.h"
#include <queue>
#include <regex>
namespace mold {
std::optional<i64> MultiGlob::find(std::string_view str) {
std::call_once(once, [&] { compile(); });
i64 val = -1;
// Match against simple glob patterns
if (root)
val = find_aho_corasick(str);
// Match against complex glob patterns
for (std::pair<Glob, i64> &glob : globs)
if (glob.first.match(str))
val = std::max(val, glob.second);
if (val == -1)
return {};
return val;
}
i64 MultiGlob::find_aho_corasick(std::string_view str) {
TrieNode *node = root.get();
i64 val = -1;
auto walk = [&](u8 c) {
for (;;) {
if (node->children[c]) {
node = node->children[c].get();
val = std::max(val, node->value);
return;
}
if (!node->suffix_link)
return;
node = node->suffix_link;
}
};
walk('\0');
for (u8 c : str) {
if (prefix_match && node == root.get())
return val;
walk(c);
}
walk('\0');
return val;
}
static bool is_simple_pattern(std::string_view pat) {
static std::regex re(R"(\*?[^*[?]+\*?)", std::regex_constants::optimize);
return std::regex_match(pat.begin(), pat.end(), re);
}
static std::string handle_stars(std::string_view pat) {
std::string str(pat);
// Convert "foo" -> "\0foo\0", "*foo" -> "foo\0", "foo*" -> "\0foo"
// and "*foo*" -> "foo". Aho-Corasick can do only substring matching,
// so we use \0 as beginning/end-of-string markers.
if (str.starts_with('*') && str.ends_with('*'))
return str.substr(1, str.size() - 2);
if (str.starts_with('*'))
return str.substr(1) + "\0"s;
if (str.ends_with('*'))
return "\0"s + str.substr(0, str.size() - 1);
return "\0"s + str + "\0"s;
}
bool MultiGlob::add(std::string_view pat, i64 val) {
assert(!is_compiled);
assert(!pat.empty());
strings.emplace_back(pat);
// Complex glob pattern
if (!is_simple_pattern(pat)) {
if (std::optional<Glob> glob = Glob::compile(pat)) {
globs.emplace_back(std::move(*glob), val);
return true;
}
return false;
}
// Simple glob pattern
if (!root)
root.reset(new TrieNode);
TrieNode *node = root.get();
for (u8 c : handle_stars(pat)) {
if (!node->children[c])
node->children[c].reset(new TrieNode);
node = node->children[c].get();
}
node->value = std::max(node->value, val);
return true;
}
void MultiGlob::compile() {
is_compiled = true;
if (root) {
fix_suffix_links(*root);
fix_values();
// If no pattern starts with '*', set prefix_match to true.
// We'll use this flag for optimization.
prefix_match = true;
for (i64 i = 1; i < 256; i++) {
if (root->children[i]) {
prefix_match = false;
break;
}
}
}
}
void MultiGlob::fix_suffix_links(TrieNode &node) {
for (i64 i = 0; i < 256; i++) {
if (!node.children[i])
continue;
TrieNode &child = *node.children[i];
TrieNode *cur = node.suffix_link;
for (;;) {
if (!cur) {
child.suffix_link = root.get();
break;
}
if (cur->children[i]) {
child.suffix_link = cur->children[i].get();
break;
}
cur = cur->suffix_link;
}
fix_suffix_links(child);
}
}
void MultiGlob::fix_values() {
std::queue<TrieNode *> queue;
queue.push(root.get());
do {
TrieNode *node = queue.front();
queue.pop();
for (std::unique_ptr<TrieNode> &child : node->children) {
if (!child)
continue;
child->value = std::max(child->value, child->suffix_link->value);
queue.push(child.get());
}
} while (!queue.empty());
}
} // namespace mold
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <functional>
#include <iomanip>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <random>
+1 -1
View File
@@ -1,6 +1,6 @@
// This file contains functions to create a tar file.
#include "common.h"
#include "lib.h"
#ifdef _WIN32
# define ftruncate _chsize_s
+5 -5
View File
@@ -17,11 +17,11 @@ if(EXISTS "${SOURCE_DIR}/.git/HEAD")
endif()
# Create new file contents and update a given file if necessary.
set(NEW_CONTENTS "#include <string>
namespace mold {
std::string mold_git_hash = \"${HASH}\";
}
")
if("${HASH}" STREQUAL "")
set(NEW_CONTENTS "")
else()
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
endif()
if(EXISTS "${OUTPUT_FILE}")
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
+179 -112
View File
@@ -31,18 +31,21 @@
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
#if MOLD_ARM32LE || MOLD_ARM32BE
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold {
using E = ARM32;
using E = MOLD_TARGET;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
ul32 *arm = (ul32 *)loc;
ul16 *thm = (ul16 *)loc;
U32<E> *arm = (U32<E> *)loc;
U16<E> *thm = (U16<E> *)loc;
switch (rel.r_type) {
case R_ARM_ABS32:
@@ -59,7 +62,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
return (il32)*arm;
return (I32<E>)*arm;
case R_ARM_THM_JUMP8:
return sign_extend(thm[0], 8) << 1;
case R_ARM_THM_JUMP11:
@@ -125,7 +128,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
static void write_arm_mov(u8 *loc, u32 val) {
u32 imm12 = bits(val, 11, 0);
u32 imm4 = bits(val, 15, 12);
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
}
static void write_thm_b21(u8 *loc, u32 val) {
@@ -135,7 +138,7 @@ static void write_thm_b21(u8 *loc, u32 val) {
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
ul16 *buf = (ul16 *)loc;
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
@@ -149,7 +152,7 @@ static void write_thm_b25(u8 *loc, u32 val) {
u32 imm10 = bits(val, 21, 12);
u32 imm11 = bits(val, 11, 1);
ul16 *buf = (ul16 *)loc;
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
@@ -160,7 +163,7 @@ static void write_thm_mov(u8 *loc, u32 val) {
u32 imm3 = bits(val, 10, 8);
u32 imm8 = bits(val, 7, 0);
ul16 *buf = (ul16 *)loc;
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
}
@@ -184,13 +187,13 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
*(ul32 *)loc = val;
*(U32<E> *)loc = val;
break;
case R_ARM_THM_JUMP8:
*(ul16 *)loc = (*(ul16 *)loc & 0xff00) | bits(val, 8, 1);
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
break;
case R_ARM_THM_JUMP11:
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
break;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
@@ -200,7 +203,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
@@ -209,7 +212,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
write_arm_mov(loc, val);
break;
case R_ARM_PREL31:
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
@@ -236,7 +239,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
static constexpr ul32 plt_entry[] = {
@@ -249,13 +252,13 @@ static constexpr ul32 plt_entry[] = {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
@@ -267,24 +270,16 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
case R_NONE:
break;
case R_ARM_ABS32:
*(ul32 *)loc = val;
*(U32<E> *)loc = val;
break;
case R_ARM_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static Thunk<E> &get_reachable_thunk(OutputSection<E> &osec, u64 addr) {
auto it = std::upper_bound(osec.thunks.begin(), osec.thunks.end(), addr,
[](u64 addr, std::unique_ptr<Thunk<E>> &thunk) {
return addr < thunk->get_addr();
});
return **it;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
@@ -312,7 +307,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
auto get_tlsdesc_trampoline_addr = [&] {
return get_reachable_thunk(*output_section, P).get_addr();
auto it = ranges::upper_bound(output_section->thunks, P, {},
[](std::unique_ptr<Thunk<E>> &thunk) {
return thunk->get_addr();
});
return (*it)->get_addr();
};
switch (rel.r_type) {
@@ -320,13 +319,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_ARM_TARGET1:
break;
case R_ARM_REL32:
*(ul32 *)loc = S + A - P;
*(U32<E> *)loc = S + A - P;
break;
case R_ARM_THM_CALL: {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0x8000'f3af; // NOP.W
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
break;
}
@@ -337,61 +336,61 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
i64 val2 = align_to(S + A - P, 4);
if (T && is_int(val1, 25)) {
*(ul16 *)(loc + 2) |= 0x1000; // BL
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, val1);
} else if (!T && is_int(val2, 25)) {
*(ul16 *)(loc + 2) &= ~0x1000; // BLX
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
write_thm_b25(loc, val2);
} else {
*(ul16 *)(loc + 2) |= 0x1000; // BL
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
}
break;
}
case R_ARM_BASE_PREL:
*(ul32 *)loc = GOT + A - P;
*(U32<E> *)loc = GOT + A - P;
break;
case R_ARM_GOTOFF32:
*(ul32 *)loc = ((S + A) | T) - GOT;
*(U32<E> *)loc = ((S + A) | T) - GOT;
break;
case R_ARM_GOT_PREL:
case R_ARM_TARGET2:
*(ul32 *)loc = GOT + G + A - P;
*(U32<E> *)loc = GOT + G + A - P;
break;
case R_ARM_GOT_BREL:
*(ul32 *)loc = G + A;
*(U32<E> *)loc = G + A;
break;
case R_ARM_CALL: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
*(U32<E> *)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);
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
i64 val = S + A - P;
if (is_int(val, 26)) {
if (T) {
*(ul32 *)loc = 0xfa00'0000; // BLX
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
*(U32<E> *)loc = 0xfa00'0000; // BLX
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
} else {
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(val, 25, 2);
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(val, 25, 2);
}
} else {
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
}
break;
}
case R_ARM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
*(U32<E> *)loc = 0xe320'f000; // NOP
break;
}
@@ -404,26 +403,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
i64 val = S + A - P;
if (T || !is_int(val, 26))
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
}
case R_ARM_PLT32:
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
*(U32<E> *)loc = 0xe320'f000; // NOP
} else {
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP8:
check(S + A - P, -(1 << 8), 1 << 8);
*(ul16 *)loc &= 0xff00;
*(ul16 *)loc |= bits(S + A - P, 8, 1);
*(U16<E> *)loc &= 0xff00;
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
break;
case R_ARM_THM_JUMP11:
check(S + A - P, -(1 << 11), 1 << 11);
*(ul16 *)loc &= 0xf800;
*(ul16 *)loc |= bits(S + A - P, 11, 1);
*(U16<E> *)loc &= 0xf800;
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
break;
case R_ARM_THM_JUMP19:
check(S + A - P, -(1 << 20), 1 << 20);
@@ -431,7 +430,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_THM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0x8000'f3af; // NOP
*(U32<E> *)loc = 0x8000'f3af; // NOP
break;
}
@@ -454,8 +453,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_PREL31:
check(S + A - P, -(1LL << 30), 1LL << 30);
*(ul32 *)loc &= 0x8000'0000;
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
*(U32<E> *)loc &= 0x8000'0000;
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
break;
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov(loc, (S + A) | T);
@@ -473,19 +472,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_thm_mov(loc, (S + A) >> 16);
break;
case R_ARM_TLS_GD32:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDM32:
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDO32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_ARM_TLS_IE32:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_ARM_TLS_LE32:
*(ul32 *)loc = S + A - ctx.tp_addr;
*(U32<E> *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
// ARM32 TLSDESC uses the following code sequence to materialize
@@ -514,35 +513,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// .L2: .word foo(gottpoff) + . - .L1
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
*(U32<E> *)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);
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
} else {
*(ul32 *)loc = S - ctx.tp_addr;
*(U32<E> *)loc = S - ctx.tp_addr;
}
break;
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = 0xeb00'0000; // bl 0
*(ul32 *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
*(U32<E> *)loc = 0xeb00'0000; // bl 0
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
*(ul32 *)loc = 0xe320'f000; // nop
*(U32<E> *)loc = 0xe320'f000; // nop
}
break;
case R_ARM_THM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
write_thm_b25(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
*(U16<E> *)(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]
*(U16<E> *)loc = 0x4478; // add r0, pc
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
} else {
*(ul32 *)loc = 0x8000'f3af; // nop.w
*(U32<E> *)loc = 0x8000'f3af; // nop.w
}
break;
default:
@@ -573,15 +572,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_ARM_ABS32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
*(U32<E> *)loc = *val;
else
*(ul32 *)loc = S + A;
*(U32<E> *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
*(U32<E> *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.dtp_addr;
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
@@ -695,27 +694,28 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
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);
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
memcpy(base, hdr, sizeof(hdr));
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
for (Symbol<E> *sym : symbols) {
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
memcpy(buf, entry, sizeof(entry));
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
buf += sizeof(entry);
P += sizeof(entry);
*(U32<E> *)(buf + 12) = S - P - 16;
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return EF_ARM_EABI_VER5;
if constexpr (E::is_le)
return EF_ARM_EABI_VER5;
else
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
}
void create_arm_exidx_section(Context<E> &ctx) {
template <>
void create_arm_exidx_section<E>(Context<E> &ctx) {
for (i64 i = 0; i < ctx.chunks.size(); i++) {
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
@@ -732,28 +732,20 @@ void create_arm_exidx_section(Context<E> &ctx) {
}
}
void Arm32ExidxSection::compute_section_size(Context<E> &ctx) {
template <>
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
output_section.compute_section_size(ctx);
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
}
void Arm32ExidxSection::update_shdr(Context<E> &ctx) {
template <>
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
// .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 (Chunk<E> *chunk = find_chunk(ctx, ".text"))
this->shdr.sh_link = chunk->shndx;
}
void Arm32ExidxSection::remove_duplicate_entries(Context<E> &ctx) {
this->shdr.sh_size = get_contents(ctx).size();
}
void Arm32ExidxSection::copy_buf(Context<E> &ctx) {
std::vector<u8> contents = get_contents(ctx);
assert(this->shdr.sh_size == contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
// Returns the end of the text segment
static u64 get_text_end(Context<E> &ctx) {
u64 ret = 0;
@@ -772,7 +764,8 @@ static u64 get_text_end(Context<E> &ctx) {
// likely that it's due to some historical reason.
//
// This function returns contents of .ARM.exidx.
std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
template <>
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
// .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
@@ -788,8 +781,8 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
const u32 CANTUNWIND = 1;
struct Entry {
ul32 addr;
ul32 val;
U32<E> addr;
U32<E> val;
};
// We reserve one extra slot for the sentinel
@@ -820,19 +813,13 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
std::sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
return a.addr < b.addr;
});
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
// Remove duplicate adjacent entries. That is, if two adjacent functions
// have the same compact unwind info or are both CANTUNWIND, we can
// merge them into a single address range.
auto it = std::unique(ent, ent + num_entries,
[](const Entry &a, const Entry &b) {
return a.val == b.val;
});
num_entries = it - ent;
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
num_entries -= tail.size();
buf.resize(num_entries * sizeof(Entry));
// Make addresses relative to themselves.
@@ -846,4 +833,84 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
return buf;
}
template <>
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
this->shdr.sh_size = get_contents(ctx).size();
}
template <>
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
std::vector<u8> contents = get_contents(ctx);
assert(this->shdr.sh_size == contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
#if MOLD_ARM32BE
// Even though using ARM32 in big-endian mode is very rare, the processor
// technically supports both little- and big-endian modes. There are two
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
// data are encoded in big-endian. In BE8, instructions are encoded in
// little-endian, and only data is in big-endian. BE8 is the de facto
// standard for ARMv6 or later. We support only BE8.
//
// A tricky thing is that instructions in an object file are always
// big-endian if the file is compiled for big-endian mode. In other words,
// the compiler always emit code in BE32 if -mbig-endian is specified. It
// is the linker's responsibility to rewrite instructions from big-endian
// to little-endian for an BE8 output. This function does that.
//
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
// Thumb instructions, and data. We need to distinguish them to swap 4
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
// and $d, respectively. We use mapping symbols to determine what to do
// with the text section.
//
// This function is called after we copy the input section contents to the
// output file. We rewrite instructions in the output buffer in place.
void arm32be_swap_bytes(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
// Collect mapping symbols
std::vector<Symbol<E> *> syms;
for (Symbol<E> *sym : file->get_local_syms())
if (InputSection<E> *isec = sym->get_input_section())
if (isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
if (std::string_view x = sym->name();
x == "$a" || x.starts_with("$a.") ||
x == "$t" || x.starts_with("$t.") ||
x == "$d" || x.starts_with("$d."))
syms.push_back(sym);
// Group mapping symbols by input section and sort by address
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
});
// Swap bytes
for (i64 i = 0; i < syms.size(); i++) {
Symbol<E> &sym = *syms[i];
if (sym.name().starts_with("$d"))
continue;
InputSection<E> &isec = *sym.get_input_section();
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
u8 *start = base + sym.value;
u8 *end;
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
end = base + syms[i + 1]->value;
else
end = base + isec.sh_size;
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
for (u8 *p = start; p < end; p += sz)
std::reverse(p, p + sz);
}
});
}
#endif
} // namespace mold
#endif
+63 -13
View File
@@ -587,31 +587,81 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
// The size of a thunk entry varies on ARM64 depending on the distance to
// the branch target. This function computes the size of each thunk entry.
template <>
void Thunk<E>::shrink_size(Context<E> &ctx) {
offsets.clear();
offsets.push_back(0);
i64 off = 0;
// The distance between S and P is only reduced by shrink_size(), but
// page(S) – page(P) may still increase by one page due to address
// changes, so we add a safety margin.
//
// For example, page(0x1200) – page(0x1000) is 0, whereas
// page(0x1100) – page(0xfff) is 0x1000, even though the latter
// distance is shorter than the former.
auto is_small = [](i64 prel) {
return is_int(prel + 0x1000, 33) && is_int(prel - 0x1000, 33);
};
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
u64 P = get_addr() + off;
i64 prel = page(S) - page(P);
off += is_small(prel) ? 16 : 32;
offsets.push_back(off);
}
}
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
// Short thunk with a 33 bit displacement
constexpr ul32 insn1[] = {
0x9000'0010, // adrp x16, 0
0x9100'0210, // add x16, x16
0xd61f'0200, // br x16
0xd420'7d00, // brk
};
static_assert(E::thunk_size == sizeof(insn));
// Long thunk with a 64 bit displacement
constexpr ul32 insn2[] = {
0x1000'0010, // adr x16, 0
0xd2a0'0011, // movz x17, 0, lsl #16
0xf2c0'0011, // movk x17, 0, lsl #32
0xf2e0'0011, // movk x17, 0, lsl #48
0x8b11'0210, // add x16, x16, x17
0xd61f'0200, // br x16
0xd420'7d00, // brk
0xd420'7d00, // brk
};
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
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;
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
buf += E::thunk_size;
P += E::thunk_size;
if (offsets[i + 1] - offsets[i] == 16) {
i64 prel = page(S) - page(P);
assert(is_int(prel, 33));
memcpy(buf, insn1, sizeof(insn1));
write_adrp(buf, prel);
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
} else {
memcpy(buf, insn2, sizeof(insn2));
write_adr(buf, bits(S - P, 15, 0));
*(ul32 *)(buf + 4) |= bits(S - P, 31, 16) << 5;
*(ul32 *)(buf + 8) |= bits(S - P, 47, 32) << 5;
*(ul32 *)(buf + 12) |= bits(S - P, 63, 48) << 5;
}
}
}
template class Thunk<E>;
} // namespace mold
#endif
+2 -1
View File
@@ -66,7 +66,7 @@ static u64 higher(u64 val, u64 pc) {
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
//
// Compensating all the sign-extensions is a bit complicated. The
// psABI gave the following formula.
// psABI gives the following formula.
val = val + 0x8000'0000 + ((val & 0x800) ? (0x1000 - 0x1'0000'0000) : 0);
return page(val) - page(pc - 8);
}
@@ -518,6 +518,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc -= S + A;
break;
case R_LARCH_32_PCREL:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = S + A - P;
break;
case R_LARCH_64_PCREL:
+9 -10
View File
@@ -416,26 +416,25 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_entry));
static_assert(E::thunk_size == sizeof(local_thunk));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
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);
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
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 {
i64 val = sym->get_addr(ctx) - P - 8;
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;
}
}
+16 -21
View File
@@ -452,27 +452,29 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(local_thunk));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
u64 TOC = ctx.extra.TOC->value;
for (Symbol<E> *sym : symbols) {
if (sym->has_got(ctx)) {
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
u8 *buf = base + offsets[i];
if (sym.has_got(ctx)) {
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
i64 val = sym.get_got_addr(ctx) - TOC;
*(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;
} else if(sym.has_plt(ctx)) {
memcpy(buf, plt_thunk, sizeof(plt_thunk));
i64 val = sym.get_gotplt_addr(ctx) - TOC;
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else {
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
memcpy(buf, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx, NO_OPD) - TOC;
*(ub32 *)buf |= higha(val);
*(ub32 *)(buf + 4) |= lo(val);
}
buf += E::thunk_size;
}
}
@@ -484,17 +486,12 @@ static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
}
static ElfRel<E> *
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
[](const ElfRel<E> &r, i64 offset) {
return r.r_offset < offset;
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
return (u64)r.r_offset;
});
if (it == rels.end())
return nullptr;
if (it->r_offset != offset)
if (it == rels.end() || it->r_offset != offset)
return nullptr;
return &*it;
}
@@ -509,9 +506,7 @@ struct OpdSymbol {
static Symbol<E> *
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
if (it == syms.end())
return nullptr;
if (it->r_offset != offset)
if (it == syms.end() || it->r_offset != offset)
return nullptr;
return it->sym;
}
+26 -12
View File
@@ -255,6 +255,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_REL16_LO:
*(ul16 *)loc = lo(S + A - P);
break;
case R_PPC64_GOT16:
*(ul16 *)loc = G - TOC;
break;
case R_PPC64_GOT16_LO:
*(ul16 *)loc = lo(G - TOC);
break;
case R_PPC64_GOT16_HI:
*(ul16 *)loc = hi(G - TOC);
break;
case R_PPC64_GOT16_HA:
*(ul16 *)loc = ha(G - TOC);
break;
case R_PPC64_PLT16_HA:
*(ul16 *)loc = ha(G + GOT - TOC);
break;
@@ -416,6 +428,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_PLT;
ctx.extra.is_power10 = true;
break;
case R_PPC64_GOT16:
case R_PPC64_GOT16_LO:
case R_PPC64_GOT16_HI:
case R_PPC64_GOT16_HA:
case R_PPC64_PLT16_HA:
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
@@ -431,8 +447,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
ctx.needs_tlsld = true;
break;
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL16_LO_DS:
case R_PPC64_TPREL34:
check_tlsle(ctx, sym, rel);
break;
@@ -456,6 +470,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL16_LO_DS:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
@@ -518,15 +534,16 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(local_thunk));
static_assert(E::thunk_size == sizeof(local_thunk_power10));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
u64 TOC = ctx.extra.TOC->value;
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);
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
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);
write34(buf + 8, got - P - 8);
@@ -536,7 +553,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
*(ul32 *)(buf + 12) |= lo(got - TOC);
}
} else {
u64 S = sym->get_addr(ctx);
u64 S = sym.get_addr(ctx);
if (ctx.extra.is_power10) {
memcpy(buf, local_thunk_power10, E::thunk_size);
write34(buf + 8, S - P - 8);
@@ -546,9 +563,6 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
*(ul32 *)(buf + 12) |= lo(S - TOC);
}
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
+22 -25
View File
@@ -301,10 +301,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check_range(ctx, i, val, lo, hi);
};
auto utype = [&](i64 val) {
check(val, -(1LL << 31) - 0x800, (1LL << 31) - 0x800);
write_utype(loc, val);
};
switch (rel.r_type) {
case R_RISCV_32:
assert(E::is_64);
*(U32<E> *)loc = S + A;
if (E::is_64)
*(U32<E> *)loc = S + A;
break;
case R_RISCV_64:
break;
@@ -321,12 +326,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
i64 val = S + A - P;
i64 rd = get_rd(buf + rel.r_offset + 4);
// Calling an undefined weak symbol does not make sense.
// We make such call into an infinite loop. This should
// help debugging of a faulty program.
if (sym.esym().is_undef_weak())
val = 0;
if (removed_bytes == 4) {
// auipc + jalr -> jal
*(ul32 *)loc = (rd << 7) | 0b1101111;
@@ -342,8 +341,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_cjtype(loc, val);
} else {
assert(removed_bytes == 0);
check(val, -(1LL << 31), 1LL << 31);
write_utype(loc, val);
utype(val);
write_itype(loc + 4, val);
}
break;
@@ -372,26 +370,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
is_got_load_pair(ctx, *this, rels, i) && is_int(val, 32)) {
// auipc <rd>, %hi20(val)
write_utype(loc, val);
utype(val);
// addi <rd>, <rd>, %lo12(val)
*(ul32 *)(loc + 4) = 0b0010011 | (rd << 15) | (rd << 7);
write_itype(loc + 4, val);
i += 3;
} else {
write_utype(loc, G + GOT + A - P);
utype(G + GOT + A - P);
}
}
break;
}
case R_RISCV_TLS_GOT_HI20:
write_utype(loc, sym.get_gottp_addr(ctx) + A - P);
utype(sym.get_gottp_addr(ctx) + A - P);
break;
case R_RISCV_TLS_GD_HI20:
write_utype(loc, sym.get_tlsgd_addr(ctx) + A - P);
utype(sym.get_tlsgd_addr(ctx) + A - P);
break;
case R_RISCV_PCREL_HI20:
write_utype(loc, S + A - P);
utype(S + A - P);
break;
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S: {
@@ -429,8 +427,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc = 0b011'0'00000'00000'01 | (rd << 7);
write_citype(loc, (S + A + 0x800) >> 12);
} else if (removed_bytes == 0) {
check(S + A, -(1LL << 31), 1LL << 31);
write_utype(loc, S + A);
utype(S + A);
}
break;
case R_RISCV_LO12_I:
@@ -449,7 +446,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_RISCV_TPREL_HI20:
assert(removed_bytes == 0 || removed_bytes == 4);
if (removed_bytes == 0)
write_utype(loc, S + A - ctx.tp_addr);
utype(S + A - ctx.tp_addr);
break;
case R_RISCV_TPREL_ADD:
// This relocation just annotates an ADD instruction that can be
@@ -473,7 +470,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
case R_RISCV_TLSDESC_HI20:
// RISC-V TLSDESC uses the following code sequence to materialize
// a TP-relative address in x0.
// a TP-relative address in a0.
//
// .L0:
// auipc tX, 0
@@ -511,7 +508,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// original useless instructions instead of deleting them, but we
// accept that because relaxations are enabled by default.
if (sym.has_tlsdesc(ctx) && removed_bytes == 0)
write_utype(loc, sym.get_tlsdesc_addr(ctx) + A - P);
utype(sym.get_tlsdesc_addr(ctx) + A - P);
break;
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
@@ -538,10 +535,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
} else if (sym2.has_gottp(ctx)) {
*(ul32 *)loc = 0x517; // auipc a0,<hi20>
write_utype(loc, sym2.get_gottp_addr(ctx) + A - P);
utype(sym2.get_gottp_addr(ctx) + A - P);
} else {
*(ul32 *)loc = 0x537; // lui a0,<hi20>
write_utype(loc, S + A - ctx.tp_addr);
utype(S + A - ctx.tp_addr);
}
break;
case R_RISCV_TLSDESC_CALL:
@@ -773,9 +770,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S:
case R_RISCV_TPREL_ADD:
check_tlsle(ctx, sym, rel);
break;
case R_RISCV_64:
@@ -785,6 +779,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_PCREL_LO12_S:
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S:
case R_RISCV_TPREL_ADD:
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL:
+11 -5
View File
@@ -48,18 +48,24 @@ static void write_mid20(u8 *loc, u64 val) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static u8 insn[] = {
// Compute PLT_INDEX
// Compute the offset into .rela.plt. This is equivalent to
// (%r0 - %r1 - 48 - 14) * 3/2 where %r0 is the PLT entry address
// plus 14, %r1 is the start address of .plt, and 48 is the size
// of this PLT header. We multiply by 3/2 because each PLT entry
// is 16 bytes, whereas each .rela.plt entry is 24 bytes.
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
// Store the computed value to 56(%r15) and .got.plt[1] to 48(%15)
// where %r15 is the stack pointer.
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)
// Branch to _dl_runtime_resolve
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, // nopr; nopr
0x00, 0x00, 0x00, 0x00, // (filler)
};
memcpy(buf, insn, sizeof(insn));
@@ -73,7 +79,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0x0d, 0x01, // basr %r0, %r1
0x07, 0x00, // nopr
0x00, 0x00, // (filler)
};
memcpy(buf, insn, sizeof(insn));
@@ -86,7 +92,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, // nopr
0x00, 0x00, // (filler)
};
memcpy(buf, insn, sizeof(insn));
+1 -1
View File
@@ -137,7 +137,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
0x5221, // mov.l @(4, r2), r2
0x402b, // jmp @r0
0xe000, // mov #0, r0
0x0009, // nop
0xfffd, // (illegal)
0, 0, // 1: .long GOTPLT
};
+2 -2
View File
@@ -599,8 +599,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (Symbol<E> *sym = ctx.extra.tls_get_addr; sym->is_imported)
sym->flags |= NEEDS_PLT;
if (ctx.extra.tls_get_addr->is_imported)
ctx.extra.tls_get_addr->flags |= NEEDS_PLT;
break;
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
+24 -9
View File
@@ -23,12 +23,11 @@
// see the contents of libc.a by running `ar t
// /usr/lib/x86_64-linux-gnu/libc.a`.
#pragma once
#include "common.h"
#include "mold.h"
namespace mold {
namespace {
struct ArHdr {
char ar_name[16];
char ar_date[12];
@@ -74,10 +73,11 @@ struct ArHdr {
return {ar_name, sizeof(ar_name)};
}
};
}
template <typename Context>
template <typename E>
std::vector<MappedFile *>
read_thin_archive_members(Context &ctx, MappedFile *mf) {
read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
@@ -123,8 +123,9 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
return vec;
}
template <typename Context>
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
template <typename E>
std::vector<MappedFile *>
read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
@@ -161,13 +162,27 @@ std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf)
return vec;
}
template <typename Context>
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
template <typename E>
std::vector<MappedFile *>
read_archive_members(Context<E> &ctx, MappedFile *mf) {
std::string_view str = mf->get_contents();
if (str.starts_with("!<arch>\n"))
return read_fat_archive_members(ctx, mf);
assert(str.starts_with("!<thin>\n"));
return read_thin_archive_members(ctx, mf);
}
using E = MOLD_TARGET;
template std::vector<MappedFile *>
read_thin_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_fat_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_archive_members(Context<E> &, MappedFile *);
} // namespace mold
+120 -66
View File
@@ -1,3 +1,4 @@
#include "config.h"
#include "mold.h"
#include <filesystem>
@@ -233,75 +234,112 @@ Options:
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf64-bigaarch64 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-loongarch elf32-loongarch
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64elf aarch64linux aarch64elfb aarch64linuxb elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux shelf_linux elf64loongarch elf32loongarch)";
// If a command line argument is in the form of `@path/to/some/file` (i.e.
// it starts with an atsign), the linker reads the given file and
// interprets its contents as a list of command line arguments. A file
// containing command line arguments is called a "response file".
//
// A response file is often used to pass a very large number of arguments
// to the linker without exceeding the kernel's command line length limit.
//
// This function opens a given file, tokenizes its contents, and returns a
// list of tokens.
template <typename E>
static std::vector<std::string_view>
read_response_file(Context<E> &ctx, std::string_view path, i64 depth) {
if (depth > 10)
Fatal(ctx) << path << ": response file nesting too deep";
std::vector<std::string_view> vec;
MappedFile *mf = must_open_file(ctx, std::string(path));
std::string_view data((char *)mf->data, mf->size);
mf->is_dependency = false;
while (!data.empty()) {
if (isspace(data[0])) {
data = data.substr(1);
continue;
}
std::vector<std::string> vec;
std::ostringstream os;
char quote = 0;
auto read_quoted = [&]() {
char quote = data[0];
data = data.substr(1);
// Each state represents the type of characters currently being read.
// SPACE indicates blank characters between tokens, BARE indicates an
// unquoted token, and QUOTED indicates a quoted token.
enum { SPACE, BARE, QUOTED } state = SPACE;
std::string buf;
while (!data.empty() && data[0] != quote) {
if (data[0] == '\\' && data.size() >= 1) {
buf.append(1, data[1]);
data = data.substr(2);
} else {
buf.append(1, data[0]);
data = data.substr(1);
}
}
if (data.empty())
Fatal(ctx) << path << ": premature end of input";
data = data.substr(1);
return save_string(ctx, buf);
};
for (i64 i = 0; i <= mf->size; i++) {
char c = (i < mf->size) ? mf->data[i] : 0;
char c2 = (i + 1 < mf->size) ? mf->data[i + 1] : 0;
auto read_unquoted = [&] {
std::string buf;
while (!data.empty()) {
if (data[0] == '\\' && data.size() >= 1) {
buf.append(1, data[1]);
data = data.substr(2);
continue;
}
if (c == '\\' && c2 == 0)
Fatal(ctx) << path << ": premature end of input";
if (!isspace(data[0])) {
buf.append(1, data[0]);
data = data.substr(1);
continue;
}
switch (state) {
case SPACE:
if (c == 0 || isspace(c))
break;
if (c == '\\') {
os << c2;
state = BARE;
i++;
break;
}
return save_string(ctx, buf);
};
std::string_view tok;
if (data[0] == '\'' || data[0] == '\"')
tok = read_quoted();
else
tok = read_unquoted();
if (c == '\'' || c == '"') {
quote = c;
state = QUOTED;
break;
}
if (tok.starts_with('@'))
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
else
vec.push_back(tok);
os << c;
state = BARE;
break;
case BARE:
if (c == 0 || isspace(c)) {
vec.push_back(os.str());
os = {};
state = SPACE;
break;
}
if (c == '\\') {
os << c2;
i++;
break;
}
if (c == '\'' || c == '"') {
quote = c;
state = QUOTED;
break;
}
os << c;
break;
case QUOTED:
if (c == 0)
Fatal(ctx) << path << ": premature end of input";
if (c == '\\') {
os << c2;
i++;
break;
}
if (c == quote) {
state = BARE;
break;
}
os << c;
break;
}
}
return vec;
std::vector<std::string_view> vec2;
for (std::string &tok : vec) {
if (tok.starts_with('@'))
append(vec2, read_response_file(ctx, tok.substr(1), depth + 1));
else
vec2.push_back(save_string(ctx, tok));
}
return vec2;
}
// Replace "@path/to/some/text/file" with its file contents.
@@ -587,6 +625,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
bool warn_shared_textrel = false;
bool error_unresolved_symbols = true;
std::optional<SeparateCodeKind> z_separate_code;
std::optional<bool> allow_shlib_undefined;
std::optional<bool> report_undefined;
std::optional<bool> z_relro;
std::optional<bool> z_dynamic_undefined_weak;
@@ -705,13 +744,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("no-dynamic-linker")) {
ctx.arg.dynamic_linker = "";
} else if (read_flag("v")) {
Out(ctx) << get_mold_version();
Out(ctx) << mold_version;
version_shown = true;
} else if (read_flag("version")) {
Out(ctx) << get_mold_version();
Out(ctx) << mold_version;
exit(0);
} else if (read_flag("V")) {
Out(ctx) << get_mold_version()
Out(ctx) << mold_version
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
<< " aarch64elf\n aarch64linux\n aarch64elfb\n"
<< " aarch64linuxb\n armelf_linux_eabi\n elf64lriscv\n"
@@ -732,7 +771,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (arg == "aarch64elfb" || arg == "aarch64linuxb") {
ctx.arg.emulation = ARM64BE::name;
} else if (arg == "armelf_linux_eabi") {
ctx.arg.emulation = ARM32::name;
ctx.arg.emulation = ARM32LE::name;
} else if (arg == "armelfb_linux_eabi") {
ctx.arg.emulation = ARM32BE::name;
} else if (arg == "elf64lriscv") {
ctx.arg.emulation = RV64LE::name;
} else if (arg == "elf64briscv") {
@@ -781,7 +822,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("Bno-symbolic")) {
ctx.arg.Bsymbolic = BSYMBOLIC_NONE;
} else if (read_arg("exclude-libs")) {
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
for (std::string_view lib : split_by_comma_or_colon(arg))
ctx.arg.exclude_libs.insert(lib);
} else if (read_flag("q") || read_flag("emit-relocs")) {
ctx.arg.emit_relocs = true;
ctx.arg.discard_locals = false;
@@ -855,10 +897,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("sysroot")) {
ctx.arg.sysroot = arg;
} else if (read_arg("unique")) {
std::optional<Glob> pat = Glob::compile(arg);
if (!pat)
if (!ctx.arg.unique.add(arg, 1))
Fatal(ctx) << "-unique: invalid glob pattern: " << arg;
ctx.arg.unique = std::move(*pat);
} else if (read_arg("unresolved-symbols")) {
if (arg == "report-all" || arg == "ignore-in-shared-libs")
report_undefined = true;
@@ -978,11 +1018,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.execute_only = true;
} else if (read_arg("compress-debug-sections")) {
if (arg == "zlib" || arg == "zlib-gabi")
ctx.arg.compress_debug_sections = COMPRESS_ZLIB;
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
else if (arg == "zstd")
ctx.arg.compress_debug_sections = COMPRESS_ZSTD;
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
else if (arg == "none")
ctx.arg.compress_debug_sections = COMPRESS_NONE;
ctx.arg.compress_debug_sections = ELFCOMPRESS_NONE;
else
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
} else if (read_arg("wrap")) {
@@ -1291,6 +1331,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
}
} else if (read_flag("no-build-id")) {
ctx.arg.build_id.kind = BuildId::NONE;
} else if (read_flag("be8")) {
ctx.arg.be8 = true;
} else if (read_flag("be32")) {
ctx.arg.be8 = false;
} else if (read_arg("format") || read_arg("b")) {
if (arg == "binary")
Fatal(ctx)
@@ -1298,14 +1342,15 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
<< " binary file into an object file, use `objcopy -I binary -O"
<< " default <input-file> <output-file.o>` instead.";
Fatal(ctx) << "unknown command line option: -b " << arg;
} else if (read_arg("fuse-ld")) {
} else if (read_arg("auxiliary") || read_arg("f")) {
ctx.arg.auxiliary.push_back(arg);
} else if (read_arg("filter") || read_arg("F")) {
ctx.arg.filter.push_back(arg);
} else if (read_flag("allow-shlib-undefined")) {
ctx.arg.allow_shlib_undefined = true;
allow_shlib_undefined = true;
} else if (read_flag("no-allow-shlib-undefined")) {
ctx.arg.allow_shlib_undefined = false;
allow_shlib_undefined = false;
} else if (read_arg("O")) {
} else if (read_flag("EB")) {
} else if (read_flag("EL")) {
@@ -1412,6 +1457,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.pic)
ctx.arg.image_base = 0;
if (allow_shlib_undefined)
ctx.arg.allow_shlib_undefined = *allow_shlib_undefined;
else
ctx.arg.allow_shlib_undefined = ctx.arg.shared;
if (!report_undefined)
report_undefined = !ctx.arg.shared;
@@ -1432,6 +1482,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.relocatable)
ctx.arg.static_ = true;
if (ctx.arg.static_)
ctx.arg.dynamic_linker = "";
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_SHUFFLE) {
if (shuffle_sections_seed)
ctx.arg.shuffle_sections_seed = *shuffle_sections_seed;
@@ -1485,6 +1538,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.image_base % ctx.page_size)
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
if (ctx.arg.emulation == ARM32BE::name && !ctx.arg.be8)
Fatal(ctx) << "--be32 is not supported";
if (ctx.arg.thread_count == 0)
ctx.arg.thread_count = get_default_thread_count();
@@ -1495,9 +1551,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
std::string ver = ctx.arg.soname;
if (ver.empty())
ver = path_filename(ctx.arg.output);
ctx.arg.version_definitions.push_back(ver);
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
}
for (std::string_view path : version_scripts) {
-12
View File
@@ -1,12 +0,0 @@
#include "mold.h"
namespace mold {
std::string get_mold_version() {
if (mold_git_hash.empty())
return "mold "s + MOLD_VERSION + " (compatible with GNU ld)";
return "mold "s + MOLD_VERSION + " (" + mold_git_hash +
"; compatible with GNU ld)";
}
} // namespace mold
+10 -4
View File
@@ -1,11 +1,12 @@
#include "elf.h"
#include <sstream>
namespace mold {
static std::string unknown_type(u32 r_type) {
char buf[50];
snprintf(buf, sizeof(buf), "unknown (0x%x)", r_type);
return buf;
std::stringstream ss;
ss << "unknown (0x" << std::hex << r_type << ")";
return ss.str();
}
#define CASE(x) case x: return #x
@@ -232,7 +233,7 @@ std::string rel_to_string<ARM64BE>(u32 r_type) {
}
template <>
std::string rel_to_string<ARM32>(u32 r_type) {
std::string rel_to_string<ARM32LE>(u32 r_type) {
switch (r_type) {
CASE(R_ARM_NONE);
CASE(R_ARM_PC24);
@@ -373,6 +374,11 @@ std::string rel_to_string<ARM32>(u32 r_type) {
return unknown_type(r_type);
}
template <>
std::string rel_to_string<ARM32BE>(u32 r_type) {
return rel_to_string<ARM32LE>(r_type);
}
template <>
std::string rel_to_string<RV64LE>(u32 r_type) {
switch (r_type) {
+33 -23
View File
@@ -13,7 +13,8 @@ struct X86_64;
struct I386;
struct ARM64LE;
struct ARM64BE;
struct ARM32;
struct ARM32LE;
struct ARM32BE;
struct RV64LE;
struct RV64BE;
struct RV32LE;
@@ -352,14 +353,16 @@ enum : u32 {
};
enum : u32 {
ELFCOMPRESS_NONE = 0,
ELFCOMPRESS_ZLIB = 1,
ELFCOMPRESS_ZSTD = 2,
};
enum : u32 {
EF_ARM_ABI_FLOAT_SOFT = 0x00000200,
EF_ARM_ABI_FLOAT_HARD = 0x00000400,
EF_ARM_EABI_VER5 = 0x05000000,
EF_ARM_ABI_FLOAT_SOFT = 0x200,
EF_ARM_ABI_FLOAT_HARD = 0x400,
EF_ARM_BE8 = 0x800000,
EF_ARM_EABI_VER5 = 0x5000000,
};
enum : u32 {
@@ -1810,7 +1813,8 @@ 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_arm64le = std::same_as<E, ARM64LE>;
template <typename E> concept is_arm64be = std::same_as<E, ARM64BE>;
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
template <typename E> concept is_arm32le = std::same_as<E, ARM32LE>;
template <typename E> concept is_arm32be = std::same_as<E, ARM32BE>;
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>;
@@ -1827,6 +1831,7 @@ 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> concept is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> concept is_arm32 = is_arm32le<E> || is_arm32be<E>;
template <typename E> concept is_arm64 = is_arm64le<E> || is_arm64be<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>;
@@ -1848,7 +1853,7 @@ struct X86_64 {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0xcc }; // int3
static constexpr u8 trap[] = { 0xcc }; // int3
static constexpr u32 R_COPY = R_X86_64_COPY;
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
@@ -1873,7 +1878,7 @@ struct I386 {
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0xcc }; // int3
static constexpr u8 trap[] = { 0xcc }; // int3
static constexpr u32 R_COPY = R_386_COPY;
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
@@ -1899,8 +1904,8 @@ struct ARM64LE {
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 u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 }; // brk
static constexpr u32 thunk_size = 32;
static constexpr u8 trap[] = { 0x00, 0x7d, 0x20, 0xd4 }; // brk
static constexpr u32 R_COPY = R_AARCH64_COPY;
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
@@ -1920,7 +1925,7 @@ struct ARM64BE : ARM64LE {
static constexpr bool is_le = false;
};
struct ARM32 {
struct ARM32LE {
static constexpr std::string_view name = "arm32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
@@ -1932,7 +1937,7 @@ struct ARM32 {
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 16;
static constexpr u32 thunk_size = 16;
static constexpr u8 filler[] = { 0xff, 0xde }; // udf
static constexpr u8 trap[] = { 0xff, 0xde }; // udf
static constexpr u32 R_COPY = R_ARM_COPY;
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
@@ -1950,6 +1955,11 @@ struct ARM32 {
};
};
struct ARM32BE : ARM32LE {
static constexpr std::string_view name = "arm32be";
static constexpr bool is_le = false;
};
struct RV64LE {
static constexpr std::string_view name = "riscv64";
static constexpr bool is_64 = true;
@@ -1960,7 +1970,7 @@ struct RV64LE {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u8 trap[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
@@ -1990,7 +2000,7 @@ struct RV32LE {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u8 trap[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
@@ -2022,7 +2032,7 @@ struct PPC32 {
static constexpr u32 pltgot_size = 36;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 36;
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
static constexpr u8 trap[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
static constexpr u32 R_COPY = R_PPC_COPY;
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
@@ -2064,7 +2074,7 @@ struct PPC64V1 : PPC64 {
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 28;
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
static constexpr u8 trap[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
};
struct PPC64V2 : PPC64 {
@@ -2075,7 +2085,7 @@ struct PPC64V2 : PPC64 {
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 24;
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f }; // trap
static constexpr u8 trap[] = { 0x08, 0x00, 0xe0, 0x7f }; // trap
};
struct S390X {
@@ -2088,7 +2098,7 @@ struct S390X {
static constexpr u32 plt_hdr_size = 48;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x07, 0x00 }; // nop
static constexpr u8 trap[] = { 0x00, 0x00 }; // invalid
static constexpr u32 R_COPY = R_390_COPY;
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
@@ -2112,7 +2122,7 @@ struct SPARC64 {
static constexpr u32 plt_hdr_size = 128;
static constexpr u32 plt_size = 32;
static constexpr u32 pltgot_size = 32;
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 }; // ta 5
static constexpr u8 trap[] = { 0x91, 0xd0, 0x20, 0x05 }; // ta 5
static constexpr u32 R_COPY = R_SPARC_COPY;
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
@@ -2136,7 +2146,7 @@ struct M68K {
static constexpr u32 plt_hdr_size = 18;
static constexpr u32 plt_size = 14;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0x4a, 0xfc }; // illegal
static constexpr u8 trap[] = { 0x4a, 0xfc }; // illegal
static constexpr u32 R_COPY = R_68K_COPY;
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
@@ -2159,7 +2169,7 @@ struct SH4LE {
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 12;
static constexpr u8 filler[] = { 0x00, 0x90 }; // nop
static constexpr u8 trap[] = { 0xfd, 0xff }; // illegal
static constexpr u32 R_COPY = R_SH_COPY;
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
@@ -2175,7 +2185,7 @@ struct SH4LE {
struct SH4BE : SH4LE {
static constexpr std::string_view name = "sh4be";
static constexpr bool is_le = false;
static constexpr u8 filler[] = { 0x90, 0x00 }; // nop
static constexpr u8 trap[] = { 0xff, 0xfd }; // illegal
};
struct LOONGARCH64 {
@@ -2188,7 +2198,7 @@ struct LOONGARCH64 {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u8 trap[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
@@ -2213,7 +2223,7 @@ struct LOONGARCH32 {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u8 trap[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
+32
View File
@@ -0,0 +1,32 @@
#include "config.h"
#include "mold.h"
#include "mold-git-hash.h"
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
#endif
#if MOLD_USE_SYSTEM_MIMALLOC
// Including mimalloc-new-delete.h overrides the new/delete operators.
// We need it only when using mimalloc as a dynamic library.
// This header should be included in only one source file, so we do
// it in this file.
# include <mimalloc-new-delete.h>
#endif
std::string mold::mold_version =
#ifdef MOLD_GIT_HASH
"mold " MOLD_VERSION " (" MOLD_GIT_HASH "; compatible with GNU ld)";
#else
"mold " MOLD_VERSION " (compatible with GNU ld)";
#endif
int main(int argc, char **argv) {
#if MOLD_USE_MIMALLOC
// Silence mimalloc warnings that users can ignore
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
#endif
return mold::mold_main<mold::X86_64>(argc, argv);
}
+55
View File
@@ -0,0 +1,55 @@
#include "mold.h"
namespace mold {
static std::string_view fatal_mono = "mold: fatal: ";
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
static std::string_view error_mono = "mold: error: ";
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
static std::string_view warning_mono = "mold: warning: ";
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
template <typename E>
Fatal<E>::Fatal(Context<E> &ctx) {
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
}
template <typename E>
[[noreturn]] Fatal<E>::~Fatal() {
out.emit();
cleanup();
_exit(1);
}
template <typename E>
Error<E>::Error(Context<E> &ctx) {
if (ctx.arg.noinhibit_exec) {
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
} else {
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
}
}
template <typename E>
Warn<E>::Warn(Context<E> &ctx) {
if (ctx.arg.suppress_warnings)
return;
out.emplace(std::cerr);
if (ctx.arg.fatal_warnings) {
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
} else {
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
}
}
using E = MOLD_TARGET;
template class Fatal<E>;
template class Error<E>;
template class Warn<E>;
} // namespace mold
+1 -2
View File
@@ -1,5 +1,4 @@
#include "mold.h"
#include "../lib/archive-file.h"
namespace mold {
@@ -139,7 +138,7 @@ static std::string_view get_elf_type(u8 *buf) {
case EM_X86_64:
return X86_64::name;
case EM_ARM:
return ARM32::name;
return is_le ? ARM32LE::name : ARM32BE::name;
case EM_AARCH64:
return is_le ? ARM64LE::name : ARM64BE::name;
case EM_RISCV:
+4 -2
View File
@@ -629,8 +629,10 @@ static std::vector<Compunit> read_compunits(Context<E> &ctx) {
template <typename E>
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
if (chunk->is_compressed)
return chunk->uncompressed_data;
if (chunk->is_compressed) {
CompressedSection<E> &sec = *(CompressedSection<E> *)chunk;
return {sec.uncompressed_data.get(), (size_t)sec.chdr.ch_size};
}
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
}
+61 -19
View File
@@ -112,7 +112,7 @@ std::string_view InputFile<E>::get_source_name() const {
template <typename E>
static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
return !(shdr.sh_flags & SHF_ALLOC) && name.starts_with(".debug");
return !(shdr.sh_flags & SHF_ALLOC) && name.starts_with(".debug_");
}
template <typename E>
@@ -425,6 +425,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
continue;
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
InputSection<E> *isec = this->sections[i].get();
// Save .llvm_addrsig for --icf=safe.
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
@@ -446,19 +447,17 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
this->has_ctors = true;
if (name == ".eh_frame")
eh_frame_sections.push_back(this->sections[i].get());
eh_frame_sections.push_back(isec);
if (name == ".debug_info" && !(shdr.sh_flags & SHF_ALLOC))
debug_info = isec;
if constexpr (is_ppc32<E>)
if (name == ".got2")
extra.got2 = this->sections[i].get();
extra.got2 = isec;
// Save debug sections for --gdb-index.
if (ctx.arg.gdb_index) {
InputSection<E> *isec = this->sections[i].get();
if (name == ".debug_info")
debug_info = isec;
// If --gdb-index is given, contents of .debug_gnu_pubnames and
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
// in an output file is just a waste of space.
@@ -999,6 +998,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
}
std::scoped_lock lock(sym.mu);
if (get_rank(this, esym, !this->is_reachable) < get_rank(sym)) {
sym.file = this;
sym.set_input_section(isec);
@@ -1006,6 +1006,7 @@ 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_versioned_default = false;
}
}
}
@@ -1205,7 +1206,7 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
if (ctx.symtab_shndx)
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + idx;
symtab_base[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());
};
@@ -1273,23 +1274,48 @@ void SharedFile<E>::parse(Context<E> &ctx) {
if (vers.empty() || esyms[i].is_undef())
ver = VER_NDX_GLOBAL;
else
ver = (vers[i] & ~VERSYM_HIDDEN);
ver = vers[i] & ~VERSYM_HIDDEN;
if (ver == VER_NDX_LOCAL)
continue;
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
bool is_default = vers.empty() || !(vers[i] & VERSYM_HIDDEN);
this->elf_syms2.push_back(esyms[i]);
this->versyms.push_back(ver);
if (is_default) {
this->symbols.push_back(get_symbol(ctx, name));
} else {
std::string_view mangled_name = save_string(
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
auto get_versioned_sym = [&] {
std::string_view key = save_string(
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
return get_symbol(ctx, key, name);
};
// Symbol resolution involving symbol versioning is tricky because one
// symbol can be resolved with two different identifiers. Among
// symbols with the same name but different versions, one of them is
// always marked as the "default" one. This symbol is often denoted
// with two atsigns as `foo@@VERSION` and can be referred to either
// as `foo` or `foo@VERSION`. No other symbols have two names like that.
//
// On contrary, a versioned non-default symbol can be referred only
// with an explicit version suffix, e.g., `foo@VERSION`.
//
// Here is how we resolve versioned default symbols. We resolve `foo`
// and `foo@VERSION` as usual, but with information to forward
// references to `foo@VERSION` to `foo`. After name resolution, we
// visit all symbol references to redirect `foo@VERSION` to `foo`.
if (vers.empty() || ver == VER_NDX_GLOBAL) {
// Unversioned symbol
this->symbols.push_back(get_symbol(ctx, name));
this->symbols2.push_back(nullptr);
} else if (vers[i] & VERSYM_HIDDEN) {
// Versioned non-default symbol
this->symbols.push_back(get_versioned_sym());
this->symbols2.push_back(nullptr);
} else {
// Versioned default symbol
this->symbols.push_back(get_symbol(ctx, name));
this->symbols2.push_back(get_versioned_sym());
}
}
@@ -1396,6 +1422,22 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
sym.sym_idx = i;
sym.ver_idx = versyms[i];
sym.is_weak = true;
sym.is_versioned_default = false;
}
// A symbol with the default version is a special case because, unlike
// other symbols, the symbol can be referred by two names, `foo` and
// `foo@VERSION`. Here, we resolve `foo@VERSOIN` as a proxy of `foo`.
Symbol<E> *sym2 = this->symbols2[i];
if (sym2 && sym2 != &sym) {
std::scoped_lock lock2(sym2->mu);
if (get_rank(this, esym, false) < get_rank(*sym2)) {
sym2->file = this;
sym2->origin = (uintptr_t)&sym;
sym2->sym_idx = i;
sym2->is_versioned_default = true;
}
}
}
}
@@ -1514,7 +1556,7 @@ void SharedFile<E>::populate_symtab(Context<E> &ctx) {
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);
*symtab++ = *to_output_esym(ctx, *sym, strtab_off, xindex);
strtab_off += write_string(strtab + strtab_off, sym->name());
i++;
}
+44 -35
View File
@@ -63,16 +63,16 @@ void InputSection<E>::uncompress(Context<E> &ctx) {
return;
u8 *buf = new u8[sh_size];
copy_contents(ctx, buf);
copy_contents_to(ctx, buf, sh_size);
contents = std::string_view((char *)buf, sh_size);
ctx.string_pool.emplace_back(buf);
uncompressed = true;
}
template <typename E>
void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
void InputSection<E>::copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
memcpy(buf, contents.data(), contents.size());
memcpy(buf, contents.data(), sz);
return;
}
@@ -84,16 +84,35 @@ void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
switch (hdr.ch_type) {
case ELFCOMPRESS_ZLIB: {
unsigned long size = sh_size;
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
Fatal(ctx) << *this << ": uncompress failed";
assert(size == sh_size);
z_stream s = {};
inflateInit(&s);
s.next_in = (u8 *)data.data();
s.avail_in = data.size();
s.next_out = buf;
s.avail_out = sz;
int r;
while (s.total_out < sz && (r = inflate(&s, Z_NO_FLUSH)) == Z_OK);
if (s.total_out < sz && r != Z_STREAM_END)
Fatal(ctx) << *this << ": uncompress failed: " << s.msg;
inflateEnd(&s);
break;
}
case ELFCOMPRESS_ZSTD:
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
Fatal(ctx) << *this << ": ZSTD_decompress failed";
case ELFCOMPRESS_ZSTD: {
ZSTD_DCtx *dctx = ZSTD_createDCtx();
ZSTD_inBuffer in = { data.data(), data.size() };
ZSTD_outBuffer out = { buf, (size_t)sz };
while (out.pos < out.size) {
size_t r = ZSTD_decompressStream(dctx, &out, &in);
if (ZSTD_isError(r))
Fatal(ctx) << *this << ": uncompress failed: " << ZSTD_getErrorName(r);
if (r == 0 && out.pos < out.size)
Fatal(ctx) << *this << ": uncompress failed: premature end of input";
}
ZSTD_freeDCtx(dctx);
break;
}
default:
Fatal(ctx) << *this << ": unsupported compression type: 0x"
<< std::hex << hdr.ch_type;
@@ -114,12 +133,6 @@ static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
<< sym << "' can not be used; recompile with -fPIC";
break;
case COPYREL:
// Create a copy relocation
if (!sym.file->is_dso) {
assert(sym.esym().is_undef_weak());
Error(ctx) << isec << ": cannot create a copy relocation for "
<< sym <<"; recompile with -fPIE or -fPIC";
}
sym.flags |= NEEDS_COPYREL;
break;
case PLT:
@@ -234,7 +247,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
if (deltas.empty()) {
// If a section is not relaxed, we can copy it as a one big chunk.
copy_contents(ctx, buf);
copy_contents_to(ctx, buf, sh_size);
} else {
// A relaxed section is copied piece-wise.
memcpy(buf, contents.data(), deltas[0].offset);
@@ -250,7 +263,7 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
}
}
} else {
copy_contents(ctx, buf);
copy_contents_to(ctx, buf, sh_size);
}
// Apply relocations
@@ -266,17 +279,12 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
template <typename E>
std::string_view
InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
for (Symbol<E> *sym : file.symbols) {
if (sym->file == &file) {
const ElfSym<E> &esym = sym->esym();
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
if (ctx.arg.demangle)
return demangle(*sym);
return sym->name();
}
}
}
for (Symbol<E> *sym : file.symbols)
if (sym->file == &file)
if (const ElfSym<E> &esym = sym->esym();
esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size)
return ctx.arg.demangle ? demangle(*sym) : sym->name();
return "";
}
@@ -343,8 +351,8 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
template <typename E>
MergeableSection<E>::MergeableSection(Context<E> &ctx, MergedSection<E> &parent,
std::unique_ptr<InputSection<E>> &isec)
: parent(parent), p2align(isec->p2align), section(std::move(isec)) {
section->uncompress(ctx);
: parent(parent), p2align(isec->p2align), input_section(std::move(isec)) {
input_section->uncompress(ctx);
std::scoped_lock lock(parent.mu);
parent.members.push_back(this);
@@ -380,9 +388,9 @@ static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
// We do not support mergeable sections that have relocations.
template <typename E>
void MergeableSection<E>::split_contents(Context<E> &ctx) {
std::string_view data = section->contents;
std::string_view data = input_section->contents;
if (data.size() > UINT32_MAX)
Fatal(ctx) << *section << ": mergeable section too large";
Fatal(ctx) << *input_section << ": mergeable section too large";
i64 entsize = parent.shdr.sh_entsize;
@@ -392,12 +400,13 @@ void MergeableSection<E>::split_contents(Context<E> &ctx) {
frag_offsets.push_back(pos);
size_t end = find_null(data, pos, entsize);
if (end == data.npos)
Fatal(ctx) << *section << ": string is not null terminated";
Fatal(ctx) << *input_section << ": string is not null terminated";
pos = end + entsize;
}
} else {
if (data.size() % entsize)
Fatal(ctx) << *section << ": section size is not multiple of sh_entsize";
Fatal(ctx) << *input_section
<< ": section size is not multiple of sh_entsize";
frag_offsets.reserve(data.size() / entsize);
for (i64 pos = 0; pos < data.size(); pos += entsize)
+1 -1
View File
@@ -9,7 +9,7 @@
// mold processes to just 1 for each user. It is intended to be used as
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
#include "common.h"
#include "mold.h"
#include <cstdlib>
#include <fcntl.h>
File renamed without changes.
+5 -1
View File
@@ -124,7 +124,11 @@ Script<E>::read_output_format(std::span<std::string_view> tok) {
template <typename E>
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
std::string rel = std::filesystem::relative(path, ctx.arg.sysroot).string();
std::string sysroot = ctx.arg.sysroot;
if (sysroot.starts_with('/') && !ctx.arg.chroot.empty())
sysroot = ctx.arg.chroot + "/" + path_clean(sysroot);
std::string rel = std::filesystem::relative(path, sysroot).string();
return rel != "." && !rel.starts_with("../");
}
+15 -20
View File
@@ -29,8 +29,8 @@
//
// 4. Continue the rest of the linking process as usual.
//
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
// the linker. The given .so file provides a way to call the compiler
// When gcc or clang inovkes ld, they pass `-plugin /path/to/linker-plugin.so`
// to the linker. The given .so file provides a way to call the compiler
// backend.
//
// The linker plugin API is documented at
@@ -425,7 +425,7 @@ get_api_version(const char *plugin_identifier,
if (LAPI_V1 < minimal_api_supported)
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
std::string version = get_mold_version() + "\0"s;
std::string version = mold_version + "\0"s;
*linker_identifier = "mold";
*linker_version = version.data();
@@ -536,8 +536,6 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
}
switch (psym.symbol_type) {
case LDST_UNKNOWN:
break;
case LDST_FUNCTION:
esym.st_type = STT_FUNC;
break;
@@ -547,8 +545,6 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
};
switch (psym.visibility) {
case LDPV_DEFAULT:
break;
case LDPV_PROTECTED:
esym.st_visibility = STV_PROTECTED;
break;
@@ -600,10 +596,10 @@ create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
template <typename E>
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
if (ctx.arg.plugin.empty())
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
<< "because no -plugin option was given. Please make sure you "
<< "added -flto not only for creating object files but also for "
<< "creating the final executable.";
Fatal(ctx) << mf->name << ": unable to handle this LTO object file because "
<< "the -plugin option was not provided. Please make sure you "
<< "added -flto not only when creating object files but also "
<< "when linking the final executable.";
load_lto_plugin(ctx);
@@ -638,13 +634,10 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
<< " please make sure you are using the same compiler of the"
<< " same version for all object files";
// It looks like GCC doesn't need fd after claim_file_hook() while
// LLVM needs it and takes the ownership of fd. To prevent "too many
// open files" issue, we close fd only for GCC. This is ugly, though.
if (!is_llvm(ctx)) {
MappedFile *mf2 = mf->parent ? mf->parent : mf;
mf2->close_fd();
}
if (mf->parent)
mf->parent->close_fd();
else
mf->close_fd();
// Create a symbol strtab
i64 strtab_size = 1;
@@ -670,8 +663,10 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
// section. We handle such symbols differently than comdat symbols in
// a regular file because, unlike regular object files, IR files don't
// have input sections.
if (psym.comdat_key)
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, psym.comdat_key);
if (psym.comdat_key) {
std::string_view key = save_string(ctx, psym.comdat_key);
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, key);
}
}
obj->symbol_strtab = save_string(ctx, strtab);
+30 -30
View File
@@ -1,11 +1,9 @@
#include "mold.h"
#include "../lib/archive-file.h"
#include <cstring>
#include <functional>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/global_control.h>
#include <unordered_set>
#ifdef _WIN32
@@ -15,13 +13,6 @@
# include <unistd.h>
#endif
#ifdef MOLD_X86_64
int main(int argc, char **argv) {
mold::set_mimalloc_options();
return mold::mold_main<mold::X86_64>(argc, argv);
}
#endif
namespace mold {
template <typename E>
@@ -44,8 +35,8 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, archive_name);
ctx.obj_pool.emplace_back(file);
file->priority = ctx.file_priority++;
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
file->as_needed =
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
@@ -65,8 +56,8 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
ObjectFile<E> *file = read_lto_object(ctx, mf);
file->priority = ctx.file_priority++;
file->archive_name = archive_name;
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
file->as_needed =
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
@@ -81,7 +72,7 @@ new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
SharedFile<E> *file = new SharedFile<E>(ctx, mf);
ctx.dso_pool.emplace_back(file);
file->priority = ctx.file_priority++;
file->is_reachable = !rctx.as_needed;
file->as_needed = rctx.as_needed;
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
@@ -256,7 +247,7 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
}
}
if (ctx.objs.empty())
if (ctx.objs.empty() && ctx.dsos.empty())
Fatal(ctx) << "no input files";
tg.wait();
@@ -306,8 +297,8 @@ int mold_main(int argc, char **argv) {
acquire_global_lock();
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
// Handle --wrap options if any.
for (std::string_view name : ctx.arg.wrap)
@@ -496,7 +487,8 @@ int mold_main(int argc, char **argv) {
// create as few segments as possible.
sort_output_sections(ctx);
if (!ctx.arg.separate_debug_file.empty())
// Handle --separate-debug-file.
if (ctx.gnu_debuglink)
separate_debug_sections(ctx);
// Compute sizes of output sections while assigning offsets
@@ -514,6 +506,10 @@ int mold_main(int argc, char **argv) {
// be added to .dynsym.
sort_dynsyms(ctx);
// Sort .debug_info contents so that DWARF32 debug info precedes that of
// DWARF64. This is to mitigate the possibility of a relocation overflow.
sort_debug_info_sections(ctx);
// Print reports about undefined symbols, if needed.
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
report_undef_errors(ctx);
@@ -536,13 +532,6 @@ int mold_main(int argc, char **argv) {
if (ctx.arg.emit_relocs)
create_reloc_sections(ctx);
// We've created range extension thunks with a pessimistive assumption
// that all out-of-section references are out of range. Now that we are
// able to assign addresses to all SHF_ALLOC output sections, we can
// eliminate excessive thunks.
if constexpr (needs_thunk<E>)
remove_redundant_thunks(ctx);
// Compute .symtab and .strtab sizes for each file.
if (!ctx.arg.strip_all)
create_output_symtab(ctx);
@@ -562,6 +551,14 @@ int mold_main(int argc, char **argv) {
filesize = set_osec_offsets(ctx);
}
// We've created range extension thunks with a pessimistive assumption
// that all out-of-section references are out of range. Now that we know
// the addresses of all sections,, we can eliminate excessive thunks.
if constexpr (needs_thunk<E>) {
remove_redundant_thunks(ctx);
filesize = set_osec_offsets(ctx);
}
if constexpr (is_arm32<E>) {
if (ctx.extra.exidx) {
ctx.extra.exidx->remove_duplicate_entries(ctx);
@@ -578,8 +575,8 @@ int mold_main(int argc, char **argv) {
// GOT or PLT addresses have a correct final value.
// If --compress-debug-sections is given, compress .debug_* sections
// using zlib.
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
// using zlib or zstd.
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE) {
compress_debug_sections(ctx);
filesize = set_osec_offsets(ctx);
}
@@ -601,6 +598,9 @@ int mold_main(int argc, char **argv) {
// Copy input sections to the output file and apply relocations.
copy_chunks(ctx);
if constexpr (is_arm32be<E>)
arm32be_swap_bytes(ctx);
if constexpr (is_x86_64<E>)
if (ctx.arg.z_rewrite_endbr)
rewrite_endbr(ctx);
@@ -612,7 +612,7 @@ int mold_main(int argc, char **argv) {
// .gdb_index's contents cannot be constructed before applying
// relocations to other debug sections. We have relocated debug
// sections now, so write the .gdb_index section.
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
if (ctx.gdb_index && !ctx.gnu_debuglink)
write_gdb_index(ctx);
// .note.gnu.build-id section contains a cryptographic hash of the
@@ -621,7 +621,7 @@ int mold_main(int argc, char **argv) {
if (ctx.buildid)
write_build_id(ctx);
if (!ctx.arg.separate_debug_file.empty())
if (ctx.gnu_debuglink)
write_gnu_debuglink(ctx);
t_copy.stop();
@@ -642,7 +642,7 @@ int mold_main(int argc, char **argv) {
if (ctx.arg.print_map)
print_map(ctx);
if (!ctx.arg.separate_debug_file.empty())
if (ctx.gnu_debuglink)
write_separate_debug_file(ctx);
// Show stats numbers
+6 -8
View File
@@ -19,14 +19,12 @@ static Map<E> get_map(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file != file || sym->get_type() == STT_SECTION)
continue;
if (InputSection<E> *isec = sym->get_input_section()) {
assert(file == &isec->file);
typename Map<E>::accessor acc;
map.insert(acc, {isec, {}});
acc->second.push_back(sym);
if (sym->file == file && sym->get_type() != STT_SECTION) {
if (InputSection<E> *isec = sym->get_input_section()) {
typename Map<E>::accessor acc;
map.insert(acc, {isec, {}});
acc->second.push_back(sym);
}
}
}
});
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
namespace mold {
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
namespace mold {
+456 -59
View File
@@ -1,6 +1,6 @@
#pragma once
#include "../lib/common.h"
#include "../lib/lib.h"
#include "elf.h"
#include <cassert>
@@ -16,6 +16,7 @@
#include <string_view>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/global_control.h>
#include <tbb/spin_mutex.h>
#include <tbb/task_group.h>
#include <type_traits>
@@ -49,7 +50,207 @@ struct ReaderContext;
template <typename E>
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
std::string get_mold_version();
extern std::string mold_version;
//
// error.cc
//
// Some C++ stdlibs don't support std::osyncstream even though
// it's is in the C++20 standard. So we implement it ourselves.
class SyncStream {
public:
SyncStream(std::ostream &out) : out(out) {}
~SyncStream() { emit(); }
void emit() {
if (!emitted) {
std::scoped_lock lock(mu);
out << ss.str() << '\n';
emitted = true;
}
}
template <typename T> SyncStream &operator<<(T &&val) {
ss << std::forward<T>(val);
return *this;
}
private:
std::ostream &out;
std::stringstream ss;
bool emitted = false;
static inline std::mutex mu;
};
template <typename E>
class Out {
public:
Out(Context<E> &ctx) {}
template <typename T> Out &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cout};
};
template <typename E>
class Fatal {
public:
Fatal(Context<E> &ctx);
[[noreturn]] ~Fatal();
template <typename T> Fatal &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename E>
class Error {
public:
Error(Context<E> &ctx);
template <typename T> Error &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename E>
class Warn {
public:
Warn(Context<E> &ctx);
template <typename T> Warn &operator<<(T &&val) {
if (out)
*out << std::forward<T>(val);
return *this;
}
private:
std::optional<SyncStream> out;
};
//
// signal-unix.cc
//
inline char *output_tmpfile = nullptr;
inline u8 *output_buffer_start = nullptr;
inline u8 *output_buffer_end = nullptr;
std::string errno_string();
void cleanup();
void install_signal_handler();
//
// mapped-file-unix.cc
//
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
class MappedFile {
public:
~MappedFile() { unmap(); }
void unmap();
void close_fd();
void reopen_fd(const std::string &path);
template <typename E>
MappedFile *slice(Context<E> &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile;
mf->name = name;
mf->data = data + start;
mf->size = size;
mf->parent = this;
ctx.mf_pool.emplace_back(mf);
return mf;
}
std::string_view get_contents() {
return std::string_view((char *)data, size);
}
i64 get_offset() const {
return parent ? (data - parent->data + parent->get_offset()) : 0;
}
// Returns a string that uniquely identify a file that is possibly
// in an archive.
std::string get_identifier() const {
if (parent) {
// We use the file offset within an archive as an identifier
// because archive members may have the same name.
return parent->name + ":" + std::to_string(get_offset());
}
if (thin_parent) {
// If this is a thin archive member, the filename part is
// guaranteed to be unique.
return thin_parent->name + ":" + name;
}
return name;
}
std::string name;
u8 *data = nullptr;
i64 size = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
// For --dependency-file
bool is_dependency = true;
#ifdef _WIN32
HANDLE fd = INVALID_HANDLE_VALUE;
#else
int fd = -1;
#endif
};
MappedFile *open_file_impl(const std::string &path, std::string &error);
template <typename E>
MappedFile *open_file(Context<E> &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
std::string error;
MappedFile *mf = open_file_impl(path, error);
if (!error.empty())
Fatal(ctx) << error;
if (mf)
ctx.mf_pool.emplace_back(mf);
return mf;
}
template <typename E>
MappedFile *must_open_file(Context<E> &ctx, std::string path) {
MappedFile *mf = open_file(ctx, path);
if (!mf)
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
return mf;
}
//
// jobs-unix.cc
//
void acquire_global_lock();
void release_global_lock();
//
// Mergeable section fragments
@@ -65,9 +266,13 @@ struct __attribute__((aligned(4))) SectionFragment {
}
MergedSection<E> &output_section;
u32 offset = -1;
i64 offset = -1;
Atomic<u8> p2align = 0;
Atomic<bool> is_alive = false;
// True if this fragment must be placed within 2^32 bytes from the
// start of the output section.
Atomic<bool> is_32bit = false;
};
// Additional class members for dynamic symbols. Because most symbols
@@ -103,23 +308,22 @@ public:
Thunk(OutputSection<E> &osec, i64 offset)
: output_section(osec), offset(offset) {}
i64 size() const { return E::thunk_hdr_size + symbols.size() * E::thunk_size; }
u64 get_addr() const { return output_section.shdr.sh_addr + offset; }
i64 size() { return offsets.back(); }
void compute_size();
void shrink_size(Context<E> &ctx) { compute_size(); }
void copy_buf(Context<E> &ctx);
u64 get_addr() const {
return output_section.shdr.sh_addr + offset;
}
u64 get_addr(i64 i) const {
return get_addr() + E::thunk_hdr_size + E::thunk_size * i;
}
OutputSection<E> &output_section;
i64 offset;
std::vector<Symbol<E> *> symbols;
std::vector<i64> offsets;
std::string name;
};
template <> void Thunk<ARM64LE>::shrink_size(Context<ARM64LE> &);
template <> void Thunk<ARM64BE>::shrink_size(Context<ARM64BE> &);
template <needs_thunk E>
static consteval i64 get_branch_distance() {
// ARM64's branch has 26 bits immediate. The immediate is padded with
@@ -262,9 +466,9 @@ struct FdeRecord {
template <typename E>
struct InputSectionExtras {};
template <>
struct InputSectionExtras<ARM32> {
InputSection<ARM32> *exidx = nullptr;
template <is_arm32 E>
struct InputSectionExtras<E> {
InputSection<E> *exidx = nullptr;
};
struct RelocDelta {
@@ -294,7 +498,7 @@ public:
InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx);
void uncompress(Context<E> &ctx);
void copy_contents(Context<E> &ctx, u8 *buf);
void copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz);
void scan_relocations(Context<E> &ctx);
void write_to(Context<E> &ctx, u8 *buf);
void apply_reloc_alloc(Context<E> &ctx, u8 *base);
@@ -449,14 +653,13 @@ public:
// For --gdb-index
bool is_compressed = false;
std::vector<u8> uncompressed_data;
// Some synethetic sections add local symbols to the output.
// For example, range extension thunks adds function_name@thunk
// symbol for each thunk entry. The following members are used
// for such synthesizing symbols.
virtual void compute_symtab_size(Context<E> &ctx) {};
virtual void populate_symtab(Context<E> &ctx) {};
virtual void compute_symtab_size(Context<E> &ctx) {}
virtual void populate_symtab(Context<E> &ctx) {}
i64 local_symtab_idx = 0;
i64 num_local_symtab = 0;
@@ -473,7 +676,7 @@ public:
std::vector<u64> relr;
};
// ELF header
// ELF header which is at the beginning of each ELF file.
template <typename E>
class OutputEhdr : public Chunk<E> {
public:
@@ -488,7 +691,11 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// Section header
// OutputShdr represents the section header. The section header is usually
// located at the end of an ELF file and is optional for executables.
// Executables work without it because the runtime only reads the program
// header. Section header is significant only in object files and not
// needed at runtime
template <typename E>
class OutputShdr : public Chunk<E> {
public:
@@ -502,7 +709,10 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// Program header
// Program header, a.k.a. segment header. Each entry in the program header
// represents a contiguous region of memory and has attributes such as
// page protection bits. On program startup, the kernel mmap's the file
// contents to memory based on the program header.
template <typename E>
class OutputPhdr : public Chunk<E> {
public:
@@ -519,6 +729,10 @@ public:
std::vector<ElfPhdr<E>> phdrs;
};
// .interp contains the pathname of a dynamic linker. Dynamically-linked
// executables have the section. If exists, the kernel runs the program at
// the specified path with the executable pathname as an argument,
// allowing the dynamic linker to run the program.
template <typename E>
class InterpSection : public Chunk<E> {
public:
@@ -550,7 +764,8 @@ struct AbsRel {
AbsRelKind kind = ABS_REL_NONE;
};
// Sections
// OutputSection represents the usual output section that contains input
// sections read from object files.
template <typename E>
class OutputSection : public Chunk<E> {
public:
@@ -570,7 +785,7 @@ public:
void populate_symtab(Context<E> &ctx) override;
void scan_abs_relocations(Context<E> &ctx);
void create_range_extension_thunks(Context<E> &ctx, bool first_pass);
void create_range_extension_thunks(Context<E> &ctx);
std::vector<InputSection<E> *> members;
std::vector<std::unique_ptr<Thunk<E>>> thunks;
@@ -582,6 +797,9 @@ public:
std::vector<std::vector<InputSection<E> *>> members_vec;
};
// .got is a linker-synthesized constant pool whose entry size is the same
// as the pointer size. It is used to store runtime addresses of global
// variables and TP-relative offsets of thread-local variables.
template <typename E>
class GotSection : public Chunk<E> {
public:
@@ -620,6 +838,8 @@ public:
i64 tlsld_idx = -1;
};
// .got.plt is similar to .got in the sense that it is a table containing
// pointers. The contents in .got.plt are function pointers used by .plt.
template <typename E>
class GotPltSection : public Chunk<E> {
public:
@@ -639,6 +859,9 @@ public:
static constexpr i64 ENTRY_SIZE = (is_ppc64v1<E> ? 3 : 1) * sizeof(Word<E>);
};
// .plt contains linker-synthesized stub code that acts as if they are
// functions. They are in fact immediately branches to real function entry
// points. .plt is used as a stub for runtime lazy symbol resolution.
template <typename E>
class PltSection : public Chunk<E> {
public:
@@ -665,6 +888,10 @@ public:
std::vector<Symbol<E> *> symbols;
};
// .plt.got is similar to .plt but doesn't support lazy symbol resolution.
// If we have the same symbol already in .got, resolving the same symbol
// lazily for .plt is just waste of time. Therefore, in such case, we use
// .plt.got for that symbol instead.
template <typename E>
class PltGotSection : public Chunk<E> {
public:
@@ -684,6 +911,7 @@ public:
std::vector<Symbol<E> *> symbols;
};
// .rel.plt contains relocation information for .plt.
template <typename E>
class RelPltSection : public Chunk<E> {
public:
@@ -699,6 +927,7 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .rel.dyn contains relocation infromation for other sections.
template <typename E>
class RelDynSection : public Chunk<E> {
public:
@@ -713,6 +942,39 @@ public:
void update_shdr(Context<E> &ctx) override;
};
// .relr.dyn is a relatively new section to contain base relocation
// information.
//
// A relocatable executable/DSO contains a lot of certain type of
// relocation entries, called "base relocations", to specify the locations
// of pointers in the FILE that need to be adjusted according to the
// desired load address and the actual load address. As an example,
// consider the following C code.
//
// extern int foo;
// int *bar = &foo;
//
// If an executable containing the above code is built as relocatable
// executable, meaning that the executable can be loaded not to a specific
// address in memory but anywhere in the virtual address space, then the
// pointer `bar`'s address is not known at link-time.
//
// The linker temporarily links the executable to a base address, record
// that information to the ELF header, and emits dynamic relocations to
// refer to the location of `bar`. At runtime, the loader adds the
// difference of the expected load address and the actual one to the
// pointer value to fix the pointer value.
//
// Relocatable executables/DSOs usually contain a fairly large number of
// base relocations. In particular, C++ virtual function table is an array
// of statically-initialized pointers which need base relocations.
//
// Notice that base relocations don't contain symbol information. They
// need only pointer locations in the ELF file that need fixing at
// load-time. Therefore, storing that information to the usual ELF
// relocation table is waste of space.
//
// .relr.dyn is designed to store base relocations in a space-efficient way.
template <typename E>
class RelrDynSection : public Chunk<E> {
public:
@@ -728,6 +990,10 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .strtab is referenced by .strtab and contains symbol names. Note that
// .strtab is not needed at runtime; one can remove the section from an
// ELF file without breaking it. Strings that runtime accesses are stored
// in .dynstr.
template <typename E>
class StrtabSection : public Chunk<E> {
public:
@@ -745,6 +1011,9 @@ public:
static constexpr i64 DATA = 7;
};
// .shstrtab contains section names, such as ".text" or ".data". Just like
// .strtab, .shstrtab is not needed at runtime. One can remove .shstrtab
// and section table from an executable without breaking it.
template <typename E>
class ShstrtabSection : public Chunk<E> {
public:
@@ -757,6 +1026,7 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .dynstr contains strings that the runtime uses.
template <typename E>
class DynstrSection : public Chunk<E> {
public:
@@ -774,6 +1044,9 @@ private:
std::unordered_map<std::string_view, i64> strings;
};
// .dynamic contains various information for dynamically-linked ELF files.
// At runtime, the dynamic linker reads the information to work
// appropriately.
template <typename E>
class DynamicSection : public Chunk<E> {
public:
@@ -797,9 +1070,12 @@ public:
};
template <typename E>
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
U32<E> *shndx);
std::optional<ElfSym<E>>
to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name, U32<E> *shndx);
// .symtab contains non-dynamic symbols. The section is not needed at
// runtime and can be stripped from an ELF file without affecting the
// behavior of the program. Symbols in .symtab are mainly for debugging.
template <typename E>
class SymtabSection : public Chunk<E> {
public:
@@ -814,6 +1090,15 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .symtab_shndx is a parallel table for .symtab to contain section
// indices for symbols.
//
// Symbol table entry contains a field for section index, but that's only
// 16 bit in size, so it cannot refer to a section whose section index is
// greater than 65535. We use .symtab_shndx for ELF files containing a lot
// of sections.
//
// Use of this section is exceptional. Most ELF files don't contain one.
template <typename E>
class SymtabShndxSection : public Chunk<E> {
public:
@@ -825,6 +1110,8 @@ public:
}
};
// .dynsym contains symbols for dynamic linking. This is similar to
// .symtab, but .dynsym contains data that the runtime uses.
template <typename E>
class DynsymSection : public Chunk<E> {
public:
@@ -844,6 +1131,15 @@ public:
i64 dynstr_offset = -1;
};
// .hash contains an on-disk hash table for .dynsym so that the runtime
// can look up a symbol name quickly without scannin all entries in
// .dynsym.
//
// Quickly identifying whether or not a .dynsym contains a given symbol is
// especially important for ELF because of the dynamic symbol lookup rule
// for ELF. In ELF, each dynamic symbol is not searched from a specific
// library but from all the ELF files loaded to memory. Therefore,
// minimizing the cost of each dynamic symbol lookup is important.
template <typename E>
class HashSection : public Chunk<E> {
public:
@@ -859,12 +1155,15 @@ public:
void copy_buf(Context<E> &ctx) override;
private:
// Even though u32 should suffice for all targets, s390x uses u64.
// It looks like a spec bug, but we need to follow suit for the
// sake of binary compatibility.
// Even though u32 should suffice as an etnry size for all targets,
// s390x uses u64. It looks like a spec bug, but we need to follow
// suit for the sake of binary compatibility.
using Entry = std::conditional_t<is_s390x<E>, U64<E>, U32<E>>;
};
// .gnu.hash is an alternative format for .hash. It contains not only an
// on-disk hash table but also contains a bloom filter to quickly identify
// whether or not a given symbol name exists in .dynsym.
template <typename E>
class GnuHashSection : public Chunk<E> {
public:
@@ -887,6 +1186,9 @@ public:
i64 num_exported = -1;
};
// MergedSection represents a section containing a constant pool such as
// string literals or floating-point constants. It is created from
// MergeableSection.
template <typename E>
class MergedSection : public Chunk<E> {
public:
@@ -915,6 +1217,10 @@ private:
std::vector<i64> shard_offsets;
};
// .eh_frame contains runtime information as to how to handle exceptions
// for each function. Each input object file contains one .eh_frame section.
// We parse input .eh_frame sections, merge their contents and emit the
// merged information to .eh_frame.
template <typename E>
class EhFrameSection : public Chunk<E> {
public:
@@ -930,6 +1236,11 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .eh_frame_hdr is a lookup table for .eh_frame. Entries in .eh_frame_hdr
// are sorted by their dcorresponding function addresses, so tha the
// runtime can quickly find an exception-handling record for the current
// function by binary search. Without .eh_frame_hdr, the runtime would
// have had to do linear search in .eh_frame.
template <typename E>
class EhFrameHdrSection : public Chunk<E> {
public:
@@ -949,6 +1260,9 @@ public:
i64 num_fdes = 0;
};
// EhFrameRelocSection contains relcoation records for .eh_frame. We use
// this class only for relocatable outputs (i.e. the output is an .o file
// as opposed to an executable or a .so file.)
template <typename E>
class EhFrameRelocSection : public Chunk<E> {
public:
@@ -964,6 +1278,8 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .copyrel and .copyrel.rel.ro represent memory regions to which the
// runtime copies symbols from other ELF files for copy relocations.
template <typename E>
class CopyrelSection : public Chunk<E> {
public:
@@ -981,6 +1297,10 @@ public:
std::vector<Symbol<E> *> symbols;
};
// .gnu.version contains a parallel table for .dynsym to specify symbol
// versions of undefined symbols. A symbol having an entry in .gnu.version
// must be resolved to a symbol with the exact same version string at
// runtime.
template <typename E>
class VersymSection : public Chunk<E> {
public:
@@ -998,6 +1318,8 @@ public:
std::vector<U16<E>> contents;
};
// .gnu.version_r contains information to refer to shared libraries and
// their symbol versions.
template <typename E>
class VerneedSection : public Chunk<E> {
public:
@@ -1015,6 +1337,9 @@ public:
std::vector<u8> contents;
};
// .gnu.version contains a parallel table for .dynsym to specify symbol
// versions of defined symbols. This section appears only in .so files,
// and it specifies the symbol version for each defined dynamic symbol.
template <typename E>
class VerdefSection : public Chunk<E> {
public:
@@ -1032,6 +1357,9 @@ public:
std::vector<u8> contents;
};
// .note.gnu.build-id contains an identifier for an output ELF file. The
// contents of the section is usually a cryptogrpahic hash of the output
// file itself to guarantee uniqueness of build-id.
template <typename E>
class BuildIdSection : public Chunk<E> {
public:
@@ -1049,6 +1377,10 @@ public:
std::vector<u8> contents;
};
// .note.package is an optional hint section that can contain arbitrary
// string. Package managers, such as dpkg or rpm, uses the section to
// embed package metadata into each ELF file so that it is easy to find
// the origin of an ELF file without any additional information.
template <typename E>
class NotePackageSection : public Chunk<E> {
public:
@@ -1063,6 +1395,8 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .note.gnu.property section contains an additional runtime information
// about ISA variant.
template <typename E>
class NotePropertySection : public Chunk<E> {
public:
@@ -1095,6 +1429,9 @@ private:
std::vector<Entry> contents;
};
// .gnu_debuglink section contains a pathname and its CRC32 checksum for a
// separate debug info file. gdb can read the section to read debug info
// from an external file.
template <typename E>
class GnuDebuglinkSection : public Chunk<E> {
public:
@@ -1111,6 +1448,7 @@ public:
u32 crc32 = 0;
};
// .gdb_index contains several tables to speed up gdb start-up.
template <typename E>
class GdbIndexSection : public Chunk<E> {
public:
@@ -1121,17 +1459,24 @@ public:
}
};
// Debug sections can be compressed with zlib or zstd to reduce the
// overall size of an ELF file. CompressedSection represents a compressed
// section.
template <typename E>
class CompressedSection : public Chunk<E> {
public:
CompressedSection(Context<E> &ctx, Chunk<E> &chunk);
void copy_buf(Context<E> &ctx) override;
private:
std::unique_ptr<u8[]> uncompressed_data;
ElfChdr<E> chdr = {};
private:
std::unique_ptr<Compressor> compressor;
};
// RelocSection represents a relocation table for an output file.
// This is used only for the reproducible output (i.e. the `-r` output).
template <typename E>
class RelocSection : public Chunk<E> {
public:
@@ -1160,6 +1505,8 @@ public:
}
};
// ComdatGroupSection represents a comdat group for an output file.
// This is used only for the reproducible output (i.e. the `-r` output).
template <typename E>
class ComdatGroupSection : public Chunk<E> {
public:
@@ -1301,11 +1648,11 @@ public:
std::string_view get_contents(i64 idx);
MergedSection<E> &parent;
std::vector<SectionFragment<E> *> fragments;
u8 p2align = 0;
std::unique_ptr<InputSection<E>> input_section;
std::vector<SectionFragment<E> *> fragments;
private:
std::unique_ptr<InputSection<E>> section;
std::vector<u32> frag_offsets;
std::vector<u32> hashes;
};
@@ -1337,6 +1684,7 @@ public:
mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) = 0;
std::span<Symbol<E> *> get_local_syms();
std::span<Symbol<E> *> get_global_syms();
std::string_view get_source_name() const;
@@ -1353,6 +1701,7 @@ public:
std::string_view shstrtab;
std::string_view symbol_strtab;
bool as_needed = false;
bool has_init_array = false;
bool has_ctors = false;
@@ -1429,6 +1778,7 @@ public:
bool is_lto_obj = false;
bool is_gcc_offload_obj = false;
bool is_rust_obj = false;
bool is_dwarf32 = false;
i64 fde_idx = 0;
i64 fde_offset = 0;
@@ -1487,6 +1837,7 @@ public:
std::string soname;
std::vector<std::string_view> version_strings;
std::vector<Symbol<E> *> symbols2;
std::vector<ElfSym<E>> elf_syms2;
private:
@@ -1567,6 +1918,22 @@ template <typename E>
std::vector<DynamicPattern>
parse_dynamic_list(Context<E> &ctx, std::string_view path);
//
// archive-file.cc
//
template <typename E>
std::vector<MappedFile *>
read_thin_archive_members(Context<E> &ctx, MappedFile *mf);
template <typename E>
std::vector<MappedFile *>
read_fat_archive_members(Context<E> &ctx, MappedFile *mf);
template <typename E>
std::vector<MappedFile *>
read_archive_members(Context<E> &ctx, MappedFile *mf);
//
// lto.cc
//
@@ -1687,6 +2054,7 @@ template <typename E> void scan_relocations(Context<E> &);
template <typename E> void compute_imported_symbol_weakness(Context<E> &);
template <typename E> void construct_relr(Context<E> &);
template <typename E> void sort_dynsyms(Context<E> &);
template <typename E> void sort_debug_info_sections(Context<E> &);
template <typename E> void create_output_symtab(Context<E> &);
template <typename E> void report_undef_errors(Context<E> &);
template <typename E> void create_reloc_sections(Context<E> &);
@@ -1717,28 +2085,34 @@ void rewrite_endbr(Context<X86_64> &ctx);
// arch-arm32.cc
//
class Arm32ExidxSection : public Chunk<ARM32> {
template <is_arm32 E>
class Arm32ExidxSection : public Chunk<E> {
public:
Arm32ExidxSection(OutputSection<ARM32> &osec) : output_section(osec) {
Arm32ExidxSection(OutputSection<E> &osec) : output_section(osec) {
this->name = ".ARM.exidx";
this->shdr.sh_type = SHT_ARM_EXIDX;
this->shdr.sh_flags = SHF_ALLOC;
this->shdr.sh_addralign = 4;
}
void compute_section_size(Context<ARM32> &ctx) override;
void update_shdr(Context<ARM32> &ctx) override;
void remove_duplicate_entries(Context<ARM32> &ctx);
void copy_buf(Context<ARM32> &ctx) override;
void compute_section_size(Context<E> &ctx) override;
void update_shdr(Context<E> &ctx) override;
void remove_duplicate_entries(Context<E> &ctx);
void copy_buf(Context<E> &ctx) override;
private:
std::vector<u8> get_contents(Context<ARM32> &ctx);
std::vector<u8> get_contents(Context<E> &ctx);
OutputSection<ARM32> &output_section;
OutputSection<E> &output_section;
};
template <> u64 get_eflags(Context<ARM32> &ctx);
void create_arm_exidx_section(Context<ARM32> &ctx);
template <is_arm32 E>
u64 get_eflags(Context<E> &ctx);
template <is_arm32 E>
void create_arm_exidx_section(Context<E> &ctx);
void arm32be_swap_bytes(Context<ARM32BE> &ctx);
//
// arch-riscv.cc
@@ -1831,8 +2205,6 @@ struct BuildId {
i64 hash_size = 0;
};
typedef enum { COMPRESS_NONE, COMPRESS_ZLIB, COMPRESS_ZSTD } CompressKind;
typedef enum {
UNRESOLVED_ERROR,
UNRESOLVED_WARN,
@@ -1888,9 +2260,9 @@ struct ContextExtras<E> {
NotePropertySection<E> *note_property = nullptr;
};
template <>
struct ContextExtras<ARM32> {
Arm32ExidxSection *exidx = nullptr;
template <is_arm32 E>
struct ContextExtras<E> {
Arm32ExidxSection<E> *exidx = nullptr;
};
template <is_riscv E>
@@ -1950,8 +2322,8 @@ struct Context {
BsymbolicKind Bsymbolic = BSYMBOLIC_NONE;
BuildId build_id;
CetReportKind z_cet_report = CET_REPORT_NONE;
CompressKind compress_debug_sections = COMPRESS_NONE;
MultiGlob undefined_glob;
Glob undefined_glob;
Glob unique;
SeparateCodeKind z_separate_code = NOSEPARATE_CODE;
ShuffleSectionsKind shuffle_sections = SHUFFLE_SECTIONS_NONE;
Symbol<E> *entry = nullptr;
@@ -1961,6 +2333,7 @@ struct Context {
bool allow_multiple_definition = false;
bool allow_shlib_undefined = true;
bool apply_dynamic_relocs = true;
bool be8 = false;
bool color_diagnostics = false;
bool default_symver = false;
bool demangle = true;
@@ -2034,12 +2407,12 @@ struct Context {
bool z_shstk = false;
bool z_start_stop_visibility_protected = false;
bool z_text = false;
i64 compress_debug_sections = ELFCOMPRESS_NONE;
i64 filler = -1;
i64 spare_dynamic_tags = 5;
i64 spare_program_headers = 0;
i64 thread_count = 0;
i64 z_stack_size = 0;
std::optional<Glob> unique;
std::optional<u64> physical_image_base;
std::optional<std::vector<Symbol<E> *>> retain_symbols_file;
std::string Map;
@@ -2060,6 +2433,7 @@ struct Context {
std::unordered_map<std::string_view, u64> section_align;
std::unordered_map<std::string_view, u64> section_start;
std::unordered_set<std::string_view> discard_section;
std::unordered_set<std::string_view> exclude_libs;
std::unordered_set<std::string_view> ignore_ir_file;
std::unordered_set<std::string_view> wrap;
std::vector<SectionOrder> section_order;
@@ -2070,7 +2444,6 @@ struct Context {
std::vector<std::string> plugin_opt;
std::vector<std::string> version_definitions;
std::vector<std::string_view> auxiliary;
std::vector<std::string_view> exclude_libs;
std::vector<std::string_view> filter;
std::vector<std::string_view> trace_symbol;
u32 z_x86_64_isa_level = 0;
@@ -2078,6 +2451,7 @@ struct Context {
u64 shuffle_sections_seed = 0;
} arg;
std::optional<tbb::global_control> global_limit;
std::vector<VersionPattern> version_patterns;
std::vector<DynamicPattern> dynamic_list_patterns;
i64 default_version = VER_NDX_UNSPECIFIED;
@@ -2303,6 +2677,7 @@ public:
u32 get_djb_hash(Context<E> &ctx) const;
void set_djb_hash(Context<E> &ctx, u32 hash);
void add_thunk_addr(Context<E> &ctx, u64 addr) requires needs_thunk<E>;
u64 get_thunk_addr(Context<E> &ctx, u64 P) const requires needs_thunk<E>;
bool is_absolute() const;
@@ -2386,6 +2761,9 @@ public:
bool is_traced : 1 = false; // for --trace-symbol
bool is_wrapped : 1 = false; // for --wrap
// For symbols with default symbol version, e.g. foo@@VERSION.
bool is_versioned_default : 1 = false;
// If a symbol can be resolved to a symbol in a different ELF file at
// runtime, `is_imported` is true. If a symbol is a dynamic symbol and
// can be used by other ELF file at runtime, `is_exported` is true.
@@ -2645,7 +3023,7 @@ InputSection<E>::get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag) {
return {};
std::string_view str = name();
if (!str.starts_with(".debug"))
if (!str.starts_with(".debug_"))
return {};
// If the section was dead due to ICF, we don't want to emit debug
@@ -2689,8 +3067,8 @@ template <typename E>
std::string_view MergeableSection<E>::get_contents(i64 i) {
i64 cur = frag_offsets[i];
if (i == frag_offsets.size() - 1)
return section->contents.substr(cur);
return section->contents.substr(cur, frag_offsets[i + 1] - cur);
return input_section->contents.substr(cur);
return input_section->contents.substr(cur, frag_offsets[i + 1] - cur);
}
template <typename E>
@@ -2728,6 +3106,11 @@ inline std::string_view InputFile<E>::get_string(Context<E> &ctx, i64 idx) {
return this->get_string(ctx, elf_sections[idx]);
}
template <typename E>
inline std::span<Symbol<E> *> InputFile<E>::get_local_syms() {
return std::span<Symbol<E> *>(this->symbols).subspan(0, this->first_global);
}
template <typename E>
inline std::span<Symbol<E> *> InputFile<E>::get_global_syms() {
return std::span<Symbol<E> *>(this->symbols).subspan(this->first_global);
@@ -3011,16 +3394,25 @@ inline void Symbol<E>::set_djb_hash(Context<E> &ctx, u32 hash) {
ctx.symbol_aux[aux_idx].djb_hash = hash;
}
template <typename E>
void Symbol<E>::add_thunk_addr(Context<E> &ctx, u64 addr) requires needs_thunk<E> {
add_aux(ctx);
std::vector<u64> &vec = ctx.symbol_aux[aux_idx].thunk_addrs;
assert(vec.empty() || vec.back() < addr);
vec.push_back(addr);
}
template <typename E>
u64
Symbol<E>::get_thunk_addr(Context<E> &ctx, u64 P) const requires needs_thunk<E> {
std::span<u64> vec = ctx.symbol_aux[aux_idx].thunk_addrs;
u64 lo = (P < branch_distance<E>) ? 0 : P - branch_distance<E>;
u64 val = *ranges::lower_bound(vec, lo);
i64 disp = val - P;
if (disp < -branch_distance<E> || branch_distance<E> <= disp)
Fatal(ctx) << "range extension thunk out of range: " << *this;
return val;
if (auto it = ranges::lower_bound(vec, lo);
it != vec.end())
if (i64 disp = *it - P;
-branch_distance<E> <= disp && disp < branch_distance<E>)
return *it;
Fatal(ctx) << "range extension thunk out of range: " << *this;
}
template <typename E>
@@ -3030,6 +3422,11 @@ inline bool Symbol<E>::has_plt(Context<E> &ctx) const {
template <typename E>
inline bool Symbol<E>::is_absolute() const {
// An unresolved weak symbol acts as if it were an absolute address
// at address 0
if (is_remaining_undef_weak())
return true;
return !is_imported && !get_frag() && !get_input_section() &&
!get_output_section();
}
+113 -79
View File
@@ -834,20 +834,6 @@ void DynamicSection<E>::copy_buf(Context<E> &ctx) {
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
template <typename T>
static std::vector<std::span<T>> split(std::vector<T> &input, i64 unit) {
std::span<T> span(input);
std::vector<std::span<T>> vec;
while (span.size() >= unit) {
vec.push_back(span.subspan(0, unit));
span = span.subspan(unit);
}
if (!span.empty())
vec.push_back(span);
return vec;
}
// Assign offsets to OutputSection members
template <typename E>
void OutputSection<E>::compute_section_size(Context<E> &ctx) {
@@ -967,8 +953,8 @@ void OutputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
InputSection<E> &isec = *members[i];
isec.write_to(ctx, buf + isec.offset);
// Clear trailing padding. We write trap or nop instructions for
// an executable segment so that a disassembler wouldn't try to
// Clear trailing padding. We write trap instructions for an
// executable segment so that a disassembler wouldn't try to
// disassemble garbage as instructions.
u64 this_end = isec.offset + isec.sh_size;
u64 next_start;
@@ -980,9 +966,18 @@ void OutputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
u8 *loc = buf + this_end;
i64 size = next_start - this_end;
auto fill = [&]<size_t N>(const u8 (&filler)[N]) {
for (i64 i = 0; i + N <= size; i += N)
memcpy(loc + i, filler, N);
};
if (this->shdr.sh_flags & SHF_EXECINSTR) {
for (i64 i = 0; i + sizeof(E::filler) <= size; i += sizeof(E::filler))
memcpy(loc + i, E::filler, sizeof(E::filler));
// s390x's old CRT files use NOP slides in .init and .fini.
// https://sourceware.org/bugzilla/show_bug.cgi?id=31042
if (is_s390x<E> && (this->name == ".init" || this->name == ".fini"))
fill({ 0x07, 0x00 }); // nopr
else
fill(E::trap);
} else {
memset(loc, 0, size);
}
@@ -1176,6 +1171,9 @@ void OutputSection<E>::populate_symtab(Context<E> &ctx) {
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
u8 *strtab = strtab_base + this->strtab_offset;
memset(esym, 0, this->num_local_symtab * sizeof(ElfSym<E>));
memset(strtab, 0, this->strtab_size);
auto write_esym = [&](u64 addr, i64 st_name) {
memset(esym, 0, sizeof(*esym));
esym->st_name = st_name;
@@ -1188,7 +1186,7 @@ void OutputSection<E>::populate_symtab(Context<E> &ctx) {
for (std::unique_ptr<Thunk<E>> &thunk : thunks) {
for (i64 i = 0; i < thunk->symbols.size(); i++) {
Symbol<E> &sym = *thunk->symbols[i];
u64 addr = thunk->get_addr(i);
u64 addr = thunk->get_addr() + thunk->offsets[i];
write_esym(addr, strtab - strtab_base);
@@ -1315,18 +1313,16 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
}
}
// If a symbol is imported, let the dynamic linker to resolve it.
if (sym->is_imported) {
// If a symbol is imported, let the dynamic linker to resolve it.
add({idx, 0, E::R_GLOB_DAT, sym});
continue;
}
// If we know an address at link-time, fill that GOT entry now.
// It may need a base relocation, though.
if (ctx.arg.pic && sym->is_relative())
} else if (ctx.arg.pic && sym->is_relative()) {
// We know the symbol's address, but it needs a base relocation.
add({idx, sym->get_addr(ctx, NO_PLT), E::R_RELATIVE});
else
} else {
// We know the symbol's exact run-time address at link-time.
add({idx, sym->get_addr(ctx, NO_PLT)});
}
}
// Create GOT entries for TLVs.
@@ -1738,8 +1734,8 @@ static u64 get_symbol_size(Symbol<E> &sym) {
}
template <typename E>
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
U32<E> *shn_xindex) {
std::optional<ElfSym<E>>
to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name, U32<E> *shn_xindex) {
ElfSym<E> esym;
memset(&esym, 0, sizeof(esym));
@@ -1848,7 +1844,8 @@ ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
if (0 <= shndx && shndx < SHN_LORESERVE) {
esym.st_shndx = shndx;
} else if (SHN_LORESERVE <= shndx) {
assert(shn_xindex);
if (!shn_xindex)
return {};
esym.st_shndx = SHN_XINDEX;
*shn_xindex = shndx;
}
@@ -1882,7 +1879,17 @@ void DynsymSection<E>::copy_buf(Context<E> &ctx) {
for (i64 i = 1; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
buf[sym.get_dynsym_idx(ctx)] = to_output_esym(ctx, sym, offset, nullptr);
std::optional<ElfSym<E>> esym = to_output_esym(ctx, sym, offset, nullptr);
if (!esym) {
Error(ctx) << ctx.arg.output
<< ": .dynsym: too many output sections: "
<< (ctx.shdr->shdr.sh_size / sizeof(ElfShdr<E>))
<< " requested, but ELF allows at most 65279";
return;
}
buf[sym.get_dynsym_idx(ctx)] = *esym;
offset += sym.name().size() + 1;
}
}
@@ -1994,7 +2001,7 @@ get_merged_output_name(Context<E> &ctx, std::string_view name, u64 flags,
i64 entsize, i64 addralign) {
if (ctx.arg.relocatable && !ctx.arg.relocatable_merge_sections)
return name;
if (ctx.arg.unique && ctx.arg.unique->match(name))
if (!ctx.arg.unique.empty() && ctx.arg.unique.find(name) != -1)
return name;
// GCC seems to create sections named ".rodata.strN.<mangled-symbol-name>.M"
@@ -2106,7 +2113,7 @@ static void add_comment_strings(Context<E> &ctx) {
};
// Add an identification string to .comment.
add(get_mold_version());
add(mold_version);
// Embed command line arguments for debugging.
char *env = getenv("MOLD_DEBUG");
@@ -2144,43 +2151,54 @@ void MergedSection<E>::compute_section_size(Context<E> &ctx) {
if (!resolved)
resolve(ctx);
std::vector<i64> sizes(map.NUM_SHARDS);
std::vector<i64> sizes(map.NUM_SHARDS * 2);
tbb::parallel_for((i64)0, map.NUM_SHARDS, [&](i64 i) {
using Entry = typename decltype(map)::Entry;
std::vector<Entry *> entries = map.get_sorted_entries(i);
i64 offset = 0;
i64 off1 = 0;
i64 off2 = 0;
for (Entry *ent : entries) {
SectionFragment<E> &frag = ent->value;
if (frag.is_alive) {
offset = align_to(offset, 1 << frag.p2align);
frag.offset = offset;
offset += ent->keylen;
if (frag.is_32bit) {
off1 = align_to(off1, 1 << frag.p2align);
frag.offset = off1;
off1 += ent->keylen;
} else {
off2 = align_to(off2, 1 << frag.p2align);
frag.offset = off2;
off2 += ent->keylen;
}
}
}
sizes[i] = offset;
sizes[i] = off1;
sizes[i + map.NUM_SHARDS] = off2;
});
i64 shard_size = map.nbuckets / map.NUM_SHARDS;
shard_offsets.resize(map.NUM_SHARDS + 1);
shard_offsets.resize(sizes.size() + 1);
for (i64 i = 1; i < map.NUM_SHARDS + 1; i++)
for (i64 i = 1; i < sizes.size() + 1; i++)
shard_offsets[i] =
align_to(shard_offsets[i - 1] + sizes[i - 1], this->shdr.sh_addralign);
this->shdr.sh_size = shard_offsets.back();
tbb::parallel_for((i64)1, map.NUM_SHARDS, [&](i64 i) {
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
SectionFragment<E> &frag = map.entries[j].value;
if (frag.is_alive)
frag.offset += shard_offsets[i];
if (frag.is_alive) {
if (frag.is_32bit)
frag.offset += shard_offsets[i];
else
frag.offset += shard_offsets[i + map.NUM_SHARDS];
}
}
});
this->shdr.sh_size = shard_offsets[map.NUM_SHARDS];
if (this->shdr.sh_size > UINT32_MAX)
Fatal(ctx) << this->name << ": output section too large";
}
template <typename E>
@@ -2196,8 +2214,11 @@ void MergedSection<E>::write_to(Context<E> &ctx, u8 *buf) {
// There might be gaps between strings to satisfy alignment requirements.
// If that's the case, we need to zero-clear them.
if (this->shdr.sh_addralign > 1 &&
this->shdr.sh_addralign != this->shdr.sh_entsize)
this->shdr.sh_addralign != this->shdr.sh_entsize) {
memset(buf + shard_offsets[i], 0, shard_offsets[i + 1] - shard_offsets[i]);
i64 j = map.NUM_SHARDS + i;
memset(buf + shard_offsets[j], 0, shard_offsets[j + 1] - shard_offsets[j]);
}
// Copy strings
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
@@ -2447,21 +2468,30 @@ void EhFrameRelocSection<E>::copy_buf(Context<E> &ctx) {
template <typename E>
void CopyrelSection<E>::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
assert(!ctx.arg.shared);
if (sym->has_copyrel)
return;
assert(!ctx.arg.shared);
assert(sym->file->is_dso);
if (!sym->file->is_dso) {
assert(sym->esym().is_undef_weak());
Error(ctx) << *sym->file << ": cannot create a copy relocation for "
<< *sym <<"; recompile with -fPIE or -fPIC";
return;
}
if (sym->esym().st_visibility == STV_PROTECTED)
if (sym->esym().st_visibility == STV_PROTECTED) {
Error(ctx) << *sym->file
<< ": cannot create a copy relocation for protected symbol '"
<< *sym << "'; recompile with -fPIC";
return;
}
if (!ctx.arg.z_copyreloc)
if (!ctx.arg.z_copyreloc) {
Error(ctx) << "-z nocopyreloc: " << *sym->file
<< ": cannot create a copy relocation for symbol '" << *sym
<< "'; recompile with -fPIC";
return;
}
symbols.push_back(sym);
@@ -2634,6 +2664,14 @@ void VerdefSection<E>::construct(Context<E> &ctx) {
if (ctx.arg.version_definitions.empty())
return;
// Handle --default-symver
if (ctx.arg.default_symver)
for (Symbol<E> *sym : ctx.dynsym->symbols)
if (sym && !sym->file->is_dso)
if (u16 ver = sym->ver_idx;
ver == VER_NDX_GLOBAL || ver == VER_NDX_UNSPECIFIED)
sym->ver_idx = VER_NDX_LAST_RESERVED + 1;
// Resize .gnu.version and write to it
ctx.versym->contents.resize(ctx.dynsym->symbols.size(), VER_NDX_GLOBAL);
ctx.versym->contents[0] = VER_NDX_LOCAL;
@@ -2809,42 +2847,36 @@ void NotePropertySection<E>::copy_buf(Context<E> &ctx) {
template <typename E>
CompressedSection<E>::CompressedSection(Context<E> &ctx, Chunk<E> &chunk) {
assert(chunk.name.starts_with(".debug"));
this->name = chunk.name;
this->is_compressed = true;
// Allocate a temporary buffer to write uncompressed contents. Note
// that we use u8[] instead of std::vector<u8> to avoid the cost of
// zero-initialization, as sh_size can be very large.
std::unique_ptr<u8[]> buf(new u8[chunk.shdr.sh_size]);
this->uncompressed_data.resize(chunk.shdr.sh_size);
u8 *buf = this->uncompressed_data.data();
// Write uncompressed contents and then compress them
chunk.write_to(ctx, buf.get());
chunk.write_to(ctx, buf);
switch (ctx.arg.compress_debug_sections) {
case COMPRESS_ZLIB:
chdr.ch_type = ELFCOMPRESS_ZLIB;
compressor.reset(new ZlibCompressor(buf, chunk.shdr.sh_size));
break;
case COMPRESS_ZSTD:
chdr.ch_type = ELFCOMPRESS_ZSTD;
compressor.reset(new ZstdCompressor(buf, chunk.shdr.sh_size));
break;
default:
unreachable();
}
if (ctx.arg.compress_debug_sections == ELFCOMPRESS_ZLIB)
compressor.reset(new ZlibCompressor(buf.get(), chunk.shdr.sh_size));
else
compressor.reset(new ZstdCompressor(buf.get(), chunk.shdr.sh_size));
// Compute header field values
chdr.ch_type = ctx.arg.compress_debug_sections;
chdr.ch_size = chunk.shdr.sh_size;
chdr.ch_addralign = chunk.shdr.sh_addralign;
this->name = chunk.name;
this->shndx = chunk.shndx;
this->is_compressed = true;
this->shdr = chunk.shdr;
this->shdr.sh_flags |= SHF_COMPRESSED;
this->shdr.sh_addralign = 1;
this->shdr.sh_size = sizeof(chdr) + compressor->compressed_size;
this->shndx = chunk.shndx;
// We don't need to keep the original data unless --gdb-index is given.
if (!ctx.arg.gdb_index) {
this->uncompressed_data.clear();
this->uncompressed_data.shrink_to_fit();
}
// We can discard the uncompressed contents unless --gdb-index is given
if (ctx.arg.gdb_index)
this->uncompressed_data = std::move(buf);
}
template <typename E>
@@ -3027,6 +3059,8 @@ template class GnuDebuglinkSection<E>;
template Chunk<E> *find_chunk(Context<E> &, u32);
template Chunk<E> *find_chunk(Context<E> &, std::string_view);
template i64 to_phdr_flags(Context<E> &ctx, Chunk<E> *chunk);
template ElfSym<E> to_output_esym(Context<E> &, Symbol<E> &, u32, U32<E> *);
template std::optional<ElfSym<E>>
to_output_esym(Context<E> &, Symbol<E> &, u32, U32<E> *);
} // namespace mold
+321 -168
View File
@@ -1,6 +1,7 @@
#include "config.h"
#include "mold.h"
#include "blake3.h"
#include <blake3.h>
#include <fstream>
#include <functional>
#include <optional>
@@ -26,8 +27,10 @@ int redo_main(Context<E> &ctx, int argc, char **argv) {
return mold_main<ARM64LE>(argc, argv);
if (target == ARM64BE::name)
return mold_main<ARM64BE>(argc, argv);
if (target == ARM32::name)
return mold_main<ARM32>(argc, argv);
if (target == ARM32LE::name)
return mold_main<ARM32LE>(argc, argv);
if (target == ARM32BE::name)
return mold_main<ARM32BE>(argc, argv);
if (target == RV64LE::name)
return mold_main<RV64LE>(argc, argv);
if (target == RV64BE::name)
@@ -63,9 +66,7 @@ template <typename E>
void apply_exclude_libs(Context<E> &ctx) {
Timer t(ctx, "apply_exclude_libs");
std::unordered_set<std::string_view> set(ctx.arg.exclude_libs.begin(),
ctx.arg.exclude_libs.end());
std::unordered_set<std::string_view> &set = ctx.arg.exclude_libs;
if (!set.empty())
for (ObjectFile<E> *file : ctx.objs)
if (!file->archive_name.empty())
@@ -204,7 +205,8 @@ static void mark_live_objects(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
if (!file->is_reachable) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file == file && ctx.arg.undefined_glob.find(sym->name())) {
if (sym->file == file &&
ctx.arg.undefined_glob.find(sym->name()) != -1) {
file->is_reachable = true;
sym->gc_root = true;
break;
@@ -215,14 +217,14 @@ static void mark_live_objects(Context<E> &ctx) {
}
std::vector<InputFile<E> *> roots;
append(roots, ctx.objs);
append(roots, ctx.dsos);
for (InputFile<E> *file : ctx.objs)
if (file->is_reachable)
roots.push_back(file);
for (InputFile<E> *file : roots)
if (!file->as_needed)
file->is_reachable = true;
for (InputFile<E> *file : ctx.dsos)
if (file->is_reachable)
roots.push_back(file);
std::erase_if(roots, [](InputFile<E> *file) { return !file->is_reachable; });
tbb::parallel_for_each(roots, [&](InputFile<E> *file,
tbb::feeder<InputFile<E> *> &feeder) {
@@ -247,25 +249,15 @@ static void mark_live_objects(Context<E> &ctx) {
// `VER1`. If it does, that's the symbol we are looking for.
template <typename E>
static void resolve_default_symver(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<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 && esym.is_undef() &&
file->has_symver[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;
}
}
tbb::parallel_for_each(files, [](InputFile<E> *file) {
std::span<Symbol<E> *> syms = file->get_global_syms();
for (i64 i = 0; i < syms.size(); i++)
if (syms[i]->is_versioned_default)
syms[i] = (Symbol<E> *)syms[i]->origin;
});
}
@@ -310,69 +302,68 @@ void resolve_symbols(Context<E> &ctx) {
resolve_default_symver(ctx);
mark_live_objects(ctx);
// Now that we know the exact set of input files that are to be
// included in the output file, we want to redo symbol resolution.
// This is because symbols defined by object files in archive files
// may have risen as a result of mark_live_objects().
//
// To redo symbol resolution, we want to clear the state first.
clear_symbols(ctx);
// COMDAT elimination needs to happen exactly here.
//
// It needs to be after archive extraction, otherwise we might
// assign COMDAT leader to an archive member that is not supposed to
// be extracted.
//
// It needs to happen before the final symbol resolution, otherwise
// we could eliminate a symbol that is already resolved to and cause
// dangling references.
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
if (file->is_reachable) {
for (ComdatGroupRef<E> &ref : file->comdat_groups)
update_minimum(ref.group->owner, file->priority);
for (ComdatGroup *g : file->lto_comdat_groups)
if (g)
update_minimum(g->owner, file->priority);
}
});
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
if (file->is_reachable)
for (ComdatGroupRef<E> &ref : file->comdat_groups)
if (ref.group->owner != file->priority)
for (u32 i : ref.members)
if (InputSection<E> *isec = file->sections[i].get())
isec->is_alive = false;
});
// Redo symbol resolution
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
if (file->is_reachable)
file->resolve_symbols(ctx);
});
// Symbols with hidden visibility need to be resolved within the
// output file. If a hidden symbol was resolved to a DSO, we'll redo
// symbol resolution from scratch with the flag to skip that symbol
// next time. This should be rare.
std::atomic_bool flag = false;
std::atomic_bool redo = false;
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
if (file->is_reachable) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file == file && sym->visibility == STV_HIDDEN) {
sym->skip_dso = true;
flag = true;
redo = true;
}
}
}
});
if (!flag)
return;
if (!redo)
break;
clear_symbols(ctx);
}
// Now that we know the exact set of input files that are to be
// included in the output file, we want to redo symbol resolution.
// This is because symbols defined by object files in archive files
// may have risen as a result of mark_live_objects().
//
// To redo symbol resolution, we want to clear the state first.
clear_symbols(ctx);
// COMDAT elimination needs to happen exactly here.
//
// It needs to be after archive extraction, otherwise we might
// assign COMDAT leader to an archive member that is not supposed to
// be extracted.
//
// It needs to happen before the final symbol resolution, otherwise
// we could eliminate a symbol that is already resolved to and cause
// dangling references.
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
if (file->is_reachable) {
for (ComdatGroupRef<E> &ref : file->comdat_groups)
update_minimum(ref.group->owner, file->priority);
for (ComdatGroup *g : file->lto_comdat_groups)
if (g)
update_minimum(g->owner, file->priority);
}
});
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
if (file->is_reachable)
for (ComdatGroupRef<E> &ref : file->comdat_groups)
if (ref.group->owner != file->priority)
for (u32 i : ref.members)
if (InputSection<E> *isec = file->sections[i].get())
isec->is_alive = false;
});
// Redo symbol resolution
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
if (file->is_reachable)
file->resolve_symbols(ctx);
});
}
// Do link-time optimization. We pass all IR object files to the compiler
@@ -496,7 +487,7 @@ static std::string_view
get_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
if (ctx.arg.relocatable && !ctx.arg.relocatable_merge_sections)
return name;
if (ctx.arg.unique && ctx.arg.unique->match(name))
if (!ctx.arg.unique.empty() && ctx.arg.unique.find(name) != -1)
return name;
if (flags & SHF_MERGE)
return name;
@@ -1041,15 +1032,16 @@ static std::string create_response_file(Context<E> &ctx) {
template <typename E>
void write_repro_file(Context<E> &ctx) {
std::string path = ctx.arg.output + ".repro.tar";
Timer t(ctx, "write_repro_file");
std::string path = ctx.arg.output + ".repro.tar";
std::unique_ptr<TarWriter> tar =
TarWriter::open(path, path_filename(ctx.arg.output) + ".repro");
if (!tar)
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
tar->append("response.txt", create_response_file(ctx));
tar->append("version.txt", get_mold_version() + "\n");
tar->append("version.txt", mold_version + "\n");
std::unordered_set<std::string_view> seen;
@@ -1125,35 +1117,41 @@ void check_shlib_undefined(Context<E> &ctx) {
return false;
};
// Obtain a list of known shared library names.
std::unordered_set<std::string_view> sonames;
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
sonames.insert(file->soname);
// Skip test if we don't have a complete set of shared object files
// for the program, because if there's a missing .so, an undefined
// symbol might be defined by that library.
auto do_test = [&] {
std::unordered_set<std::string_view> sonames;
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
sonames.insert(file->soname);
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
// Skip the file if it depends on a file that we know nothing about.
// This is because missing symbols may be provided by that unknown file.
for (std::string_view needed : file->get_dt_needed(ctx))
if (!sonames.contains(needed))
return;
for (SharedFile<E> *file : ctx.dsos)
for (std::string_view soname : file->get_dt_needed(ctx))
if (!sonames.contains(soname))
return false;
return true;
};
// Check if all undefined symbols have been resolved.
for (i64 i = 0; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
if (esym.is_undef() && !esym.is_weak() && !sym.file &&
!is_sparc_register(esym))
Error(ctx) << *file << ": --no-allow-shlib-undefined: undefined symbol: "
<< sym;
}
});
if (do_test()) {
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
// Check if all undefined symbols have been resolved.
for (i64 i = 0; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
if (esym.is_undef() && !esym.is_weak() && !sym.file &&
!is_sparc_register(esym))
Error(ctx) << *file << ": --no-allow-shlib-undefined: undefined symbol: "
<< sym;
}
});
}
// Beyond this point, DSOs that are not referenced directly by any
// object file are not needed. They were kept by
// SharedFile<E>::mark_live_objects just for this pass. Therefore,
// remove unneeded DSOs from the list now.
for (SharedFile<E> *file : ctx.dsos)
file->is_reachable = false;
file->is_reachable = !file->as_needed;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms())
@@ -1169,10 +1167,6 @@ template <typename E>
void check_symbol_types(Context<E> &ctx) {
Timer t(ctx, "check_symbol_types");
std::vector<InputFile<E> *> files;
append(files, ctx.objs);
append(files, ctx.dsos);
auto canonicalize = [](u32 ty) -> u32 {
if (ty == STT_GNU_IFUNC)
return STT_FUNC;
@@ -1181,23 +1175,35 @@ void check_symbol_types(Context<E> &ctx) {
return ty;
};
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<E> *file) {
auto check = [&](InputFile<E> &file, Symbol<E> &sym,
const ElfSym<E> &esym1, const ElfSym<E> &esym2) {
if (sym.file && sym.file != &file &&
esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
<< ">>> defined in " << *sym.file << " as "
<< stt_to_string<E>(esym1.st_type) << '\n'
<< ">>> defined in " << file << " as "
<< stt_to_string<E>(esym2.st_type);
}
};
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++)
if (Symbol<E> *sym = file->symbols[i];
sym->file && sym->file != file)
check(*file, *sym, sym->esym(), file->elf_syms[i]);
});
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<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;
if (Symbol<E> *sym = file->symbols[i];
sym->file && sym->file != file)
check(*file, *sym, sym->esym(), file->elf_syms[i]);
const ElfSym<E> &esym1 = sym.esym();
const ElfSym<E> &esym2 = file->elf_syms[i];
if (esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
<< ">>> defined in " << *sym.file << " as "
<< stt_to_string<E>(esym1.st_type) << '\n'
<< ">>> defined in " << *file << " as "
<< stt_to_string<E>(esym2.st_type);
}
if (Symbol<E> *sym = file->symbols2[i])
if (sym->file && sym->file != file)
check(*file, *sym, sym->esym(), file->elf_syms[i]);
}
});
}
@@ -1295,6 +1301,142 @@ void sort_ctor_dtor(Context<E> &ctx) {
}
}
// Returns true if a given section contains a DWARF32 debug record.
// `isec` must be a .debug_info section.
template <typename E>
static bool is_dwarf32(Context<E> &ctx, InputSection<E> *isec) {
if (!isec)
return true;
if (isec->sh_size < 12) {
// The section is too short. This is a user error, but instead of
// being nitpicky about it, we simply handle it on a garbage-in,
// garbage-out basis.
return true;
}
// A .debug_info section contains compilation units (CUs). A 32-bit CU
// starts with a 32-bit size field, while a 64-bit CU starts with a
// magic number 0xffff'ffff followed by a 64-bit size field.
//
// Note that size doesn't take the size field itself into account, so
// the actual size of a 64-bit CU including the size field is 12 bytes
// larger than the value in the size field.
u8 buf[12];
isec->copy_contents_to(ctx, buf, 12);
if (*(U32<E> *)buf != 0xffff'ffff)
return true;
if (*(U64<E> *)(buf + 4) + 12 == isec->sh_size)
return false;
// An input .debug_info section usually contains a single CU. However,
// if the linker combines multiple object files using `-r`, the
// resulting object file may have a .debug_info section with as many CUs
// as there are in the input files. Therefore, if the first CU doesn't
// cover the entire .debug_info section, we need to keep reading until
// the end of the section.
//
// An input .debug_info section may be compressed using zlib or zstd, so
// we need to uncompress it before accessing `isec->contents`.
isec->uncompress(ctx);
u8 *p = (u8 *)isec->contents.data() + *(U64<E> *)(buf + 4) + 12;
u8 *end = (u8 *)isec->contents.data() + isec->sh_size;
while (end - p >= 12) {
if (*(U32<E> *)p != 0xffff'ffff)
return true;
p += *(U64<E> *)(p + 4) + 12;
}
return false;
}
// Debug sections in an input object file refer to other debug sections in
// the same file using section offsets. The offsets are 64 bits in DWARF64
// and 32 bits in DWARF32.
//
// GCC and Clang emit DWARF32 debug info by default even for 64-bit code.
// That is, 64-bit values are used for addresses, and 32-bit values for
// references between debug sections. This makes sense for ordinary programs
// because it reduces the size of the debug info sections.
//
// You can change the format to DWARF64 by passing `-gdwarf64`. Therefore,
// the "right" approach to build an extremely large program in debug mode is
// to recompile everything with `-gdwarf64`. However, that’s often not
// feasiable for various reasons.
//
// If we don't do anything about it, a relocation overflow could occur if
// any output debug section exceeds 4 GiB in size, making it almost
// impossible for users to link an object file compiled without `-gdwarf64`
// to an extremely large program.
//
// This function works around the issue by sorting output debug section
// contents so that DWARF32 input sections are at the start of the output
// section followed by DWARF64 input sections. By doing this, we can avoid
// relocation overflow until the total size of DWARF32 input sections alone
// exceeds 4 GiB.
template <typename E>
void sort_debug_info_sections(Context<E> &ctx) {
Timer t(ctx, "sort_debug_info_sections");
// True if mold is running under ctest
bool is_in_test = false;
if (char *env = getenv("MOLD_DEBUG"); env && env[0])
is_in_test = true;
// Get lists of output debug sections that need sorting
std::vector<OutputSection<E> *> vec1;
std::vector<MergedSection<E> *> vec2;
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (!(osec->shdr.sh_flags & SHF_ALLOC) && osec->name.starts_with(".debug_"))
if (osec->shdr.sh_size >= UINT32_MAX || is_in_test)
vec1.push_back(osec);
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
if (!(osec->shdr.sh_flags & SHF_ALLOC) && osec->name.starts_with(".debug_"))
if (osec->shdr.sh_size >= UINT32_MAX || is_in_test)
vec2.push_back(osec.get());
if (vec1.empty() && vec2.empty())
return;
// Read each input file's .debug_info to record whether the file contains
// DWARF32 or DWARF64
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->is_dwarf32 = is_dwarf32(ctx, file->debug_info);
});
// Unless we have a mix of DWARF32 and DWARF64, it doesn't make sense to
// sort sections.
if (ranges::all_of(ctx.objs, &ObjectFile<E>::is_dwarf32) ||
ranges::none_of(ctx.objs, &ObjectFile<E>::is_dwarf32))
return;
// Reorder input sections in the output section so that DWARF32
// precededs DWARF64
tbb::parallel_for_each(vec1, [&](OutputSection<E> *osec) {
ranges::stable_partition(osec->members, [](InputSection<E> *isec) {
return isec->file.is_dwarf32;
});
osec->compute_section_size(ctx);
});
// Reorder strings in .debug_str and the like
tbb::parallel_for_each(vec2, [&](MergedSection<E> *osec) {
tbb::parallel_for_each(osec->members, [&](MergeableSection<E> *m) {
if (m->input_section->file.is_dwarf32)
for (SectionFragment<E> *frag : m->fragments)
frag->is_32bit = true;
});
});
tbb::parallel_for_each(vec2, [&](MergedSection<E> *osec) {
osec->compute_section_size(ctx);
});
}
// .ctors/.dtors serves the same purpose as .init_array/.fini_array,
// albeit with very subtly differences. Both contain pointers to
// initializer/finalizer functions. The runtime executes them one by one
@@ -1369,7 +1511,8 @@ void shuffle_sections(Context<E> &ctx) {
auto is_eligible = [](OutputSection<E> *osec) {
if (osec) {
std::string_view name = osec->name;
return name != ".init" && name != ".fini" &&
return (osec->shdr.sh_flags & SHF_ALLOC) &&
name != ".init" && name != ".fini" &&
name != ".ctors" && name != ".dtors" &&
name != ".init_array" && name != ".preinit_array" &&
name != ".fini_array";
@@ -1441,7 +1584,7 @@ void compute_section_sizes(Context<E> &ctx) {
// create_range_extension_thunks is not thread-safe
for (Chunk<E> *chunk : std::span(vec.begin(), tail.begin()))
chunk->to_osec()->create_range_extension_thunks(ctx, true);
chunk->to_osec()->create_range_extension_thunks(ctx);
tbb::parallel_for_each(tail, [&](Chunk<E> *chunk) {
chunk->compute_section_size(ctx);
@@ -1616,7 +1759,8 @@ void scan_relocations(Context<E> &ctx) {
ctx.got->add_tlsdesc_symbol(ctx, sym);
if (sym->flags & NEEDS_COPYREL) {
if (ctx.arg.z_relro && ((SharedFile<E> *)sym->file)->is_readonly(sym))
if (ctx.arg.z_relro && sym->file->is_dso &&
((SharedFile<E> *)sym->file)->is_readonly(sym))
ctx.copyrel_relro->add_symbol(ctx, sym);
else
ctx.copyrel->add_symbol(ctx, sym);
@@ -1861,8 +2005,8 @@ void apply_version_script(Context<E> &ctx) {
// Assign versions to symbols specified with `extern "C++"` or
// wildcard patterns first.
MultiGlob matcher;
MultiGlob cpp_matcher;
Glob matcher;
Glob cpp_matcher;
// The "local:" label has a special meaning in the version script.
// It can appear in any VERSION clause, and it hides matched symbols
@@ -1899,18 +2043,14 @@ void apply_version_script(Context<E> &ctx) {
continue;
std::string_view name = sym->name();
i64 match = -1;
if (std::optional<i64> idx = matcher.find(name))
match = std::max(match, *idx);
i64 match = matcher.find(name);
// Match non-mangled symbols against the C++ pattern as well.
// Weird, but required to match other linkers' behavior.
if (!cpp_matcher.empty()) {
if (std::optional<std::string_view> s = demangle_cpp(name))
name = *s;
if (std::optional<i64> idx = cpp_matcher.find(name))
match = std::max(match, *idx);
match = std::max(match, cpp_matcher.find(name));
}
if (match != -1)
@@ -2088,8 +2228,8 @@ void compute_import_export(Context<E> &ctx) {
// exported so that they are interposable. In other words, symbols
// that did not match will be bound locally within the output file,
// effectively turning them into protected symbols.
MultiGlob matcher;
MultiGlob cpp_matcher;
Glob matcher;
Glob cpp_matcher;
auto handle_match = [&](Symbol<E> *sym) {
if (ctx.arg.shared) {
@@ -2129,12 +2269,12 @@ void compute_import_export(Context<E> &ctx) {
std::string_view name = sym->name();
if (matcher.find(name)) {
if (matcher.find(name) != -1) {
handle_match(sym);
} else if (!cpp_matcher.empty()) {
if (std::optional<std::string_view> s = demangle_cpp(name))
name = *s;
if (cpp_matcher.find(name))
if (cpp_matcher.find(name) != -1)
handle_match(sym);
}
}
@@ -2412,6 +2552,15 @@ void sort_output_sections(Context<E> &ctx) {
sort_output_sections_by_order(ctx);
}
template <typename E>
static i64 get_tls_segment_alignment(Context<E> &ctx) {
i64 val = 1;
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->shdr.sh_flags & SHF_TLS)
val = std::max<i64>(val, chunk->shdr.sh_addralign);
return val;
}
// This function assigns virtual addresses to output sections. Assigning
// addresses is a bit tricky because we want to pack sections as tightly
// as possible while not violating the constraints imposed by the hardware
@@ -2456,8 +2605,12 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
std::vector<Chunk<E> *> &chunks = ctx.chunks;
u64 addr = ctx.arg.image_base;
auto is_tls = [](Chunk<E> *chunk) {
return chunk->shdr.sh_flags & SHF_TLS;
};
auto is_tbss = [](Chunk<E> *chunk) {
return (chunk->shdr.sh_type == SHT_NOBITS) && (chunk->shdr.sh_flags & SHF_TLS);
return (chunk->shdr.sh_flags & SHF_TLS) && (chunk->shdr.sh_type == SHT_NOBITS);
};
for (i64 i = 0; i < chunks.size(); i++) {
@@ -2514,6 +2667,12 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
}
}
// TLS sections are included only in PT_LOAD but also in PT_TLS.
// We align the first TLS section so that the PT_TLS segment starts
// at an address that meets the segment's alignment requirement.
if (is_tls(chunks[i]) && (i == 0 || !is_tls(chunks[i - 1])))
addr = align_to(addr, get_tls_segment_alignment(ctx));
// TLS BSS sections are laid out so that they overlap with the
// subsequent non-tbss sections. Overlapping is fine because a STT_TLS
// segment contains an initialization image for newly-created threads,
@@ -2764,7 +2923,7 @@ i64 set_osec_offsets(Context<E> &ctx) {
if (ctx.phdr) {
i64 sz = ctx.phdr->shdr.sh_size;
ctx.phdr->update_shdr(ctx);
if (sz != ctx.phdr->shdr.sh_size)
if (sz < ctx.phdr->shdr.sh_size)
continue;
}
@@ -2799,10 +2958,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
};
auto stop = [](Symbol<E> *sym, auto &chunk) {
auto stop = [](Symbol<E> *sym, auto &chunk, i64 bias = 0) {
if (sym && chunk) {
sym->set_output_section(chunk);
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size;
sym->value = chunk->shdr.sh_addr + chunk->shdr.sh_size + bias;
}
};
@@ -2839,10 +2998,9 @@ void fix_synthetic_symbols(Context<E> &ctx) {
// such executable lacks the .dynamic section and thus there's no way
// to find ifunc relocations other than these symbols.
if (ctx.reldyn && ctx.arg.static_ && !ctx.arg.pie) {
stop(ctx.__rel_iplt_start, ctx.reldyn);
stop(ctx.__rel_iplt_start, ctx.reldyn,
-get_num_irelative_relocs(ctx) * sizeof(ElfRel<E>));
stop(ctx.__rel_iplt_end, ctx.reldyn);
ctx.__rel_iplt_start->value -=
get_num_irelative_relocs(ctx) * sizeof(ElfRel<E>);
} else {
// If the symbols are not ncessary, we turn them to absolute
// symbols at address 0.
@@ -2944,17 +3102,13 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
// PPC64's _{save,rest}gpr{0,1}_{14,15,16,...,31} symbols
if constexpr (is_ppc64v2<E>) {
i64 offset = 0;
for (std::pair<std::string_view, u32> p : ppc64_save_restore_insns) {
std::string_view label = p.first;
if (!label.empty())
if constexpr (is_ppc64v2<E>)
for (i64 i = 0; i < ppc64_save_restore_insns.size(); i++)
if (std::string_view label = ppc64_save_restore_insns[i].first;
!label.empty())
if (Symbol<E> *sym = get_symbol(ctx, label);
sym->file == ctx.internal_obj)
start(sym, ctx.extra.save_restore, offset);
offset += 4;
}
}
start(sym, ctx.extra.save_restore, i * 4);
// __start_ and __stop_ symbols
for (Chunk<E> *chunk : sections) {
@@ -3002,25 +3156,22 @@ template <typename E>
void compress_debug_sections(Context<E> &ctx) {
Timer t(ctx, "compress_debug_sections");
// Since this pass is embarassingly parallel, we want to use all
// available cores.
ctx.global_limit.reset();
tbb::parallel_for((i64)0, (i64)ctx.chunks.size(), [&](i64 i) {
Chunk<E> &chunk = *ctx.chunks[i];
if ((chunk.shdr.sh_flags & SHF_ALLOC) || chunk.shdr.sh_size == 0 ||
!chunk.name.starts_with(".debug"))
return;
Chunk<E> *comp = new CompressedSection<E>(ctx, chunk);
ctx.chunk_pool.emplace_back(comp);
ctx.chunks[i] = comp;
if (!(chunk.shdr.sh_flags & SHF_ALLOC) && chunk.shdr.sh_size &&
chunk.name.starts_with(".debug_")) {
Chunk<E> *comp = new CompressedSection<E>(ctx, chunk);
ctx.chunk_pool.emplace_back(comp);
ctx.chunks[i] = comp;
}
});
if (ctx.shstrtab)
ctx.shstrtab->update_shdr(ctx);
if (ctx.ehdr)
ctx.ehdr->update_shdr(ctx);
if (ctx.shdr)
ctx.shdr->update_shdr(ctx);
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
}
// BLAKE3 is a cryptographic hash function just like SHA256.
@@ -3209,9 +3360,10 @@ void write_separate_debug_file(Context<E> &ctx) {
});
append(ctx.chunks, ctx.debug_chunks);
sort_debug_info_sections(ctx);
// Handle --compress-debug-info
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE)
compress_debug_sections(ctx);
// Write to the debug info file as if it were a regular output file.
@@ -3388,6 +3540,7 @@ template void create_reloc_sections(Context<E> &);
template void copy_chunks(Context<E> &);
template void construct_relr(Context<E> &);
template void sort_dynsyms(Context<E> &);
template void sort_debug_info_sections(Context<E> &);
template void create_output_symtab(Context<E> &);
template void apply_version_script(Context<E> &);
template void parse_symbol_version(Context<E> &);
-4
View File
@@ -122,10 +122,6 @@ i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
if (sym.is_absolute())
return INT64_MAX;
// Likewise, relocations against weak undefined symbols won't be relaxed.
if (sym.esym().is_undef_weak())
return INT64_MAX;
// Compute a distance between the relocated place and the symbol.
i64 S = sym.get_addr(ctx);
i64 A = rel.r_addend;
+3 -4
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <csignal>
#include <tbb/version.h>
@@ -28,8 +28,7 @@ void cleanup() {
// for the first time.
//
// If a disk becomes full as a result of a write to an mmap'ed memory
// region, the failure of the write is reported as a SIGBUS or structured
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
// region, the failure of the write is reported as a SIGBUS. This
// signal handler catches that signal and prints out a user-friendly
// error message. Without this, it is very hard to realize that the
// disk might be full.
@@ -74,7 +73,7 @@ void install_signal_handler() {
sigaction(SIGBUS, &action, NULL);
// OneTBB 2021.9.0 has the interface version 12090.
if (TBB_runtime_interface_version() < 12090) {
if (TBB_runtime_interface_version() <= 12090) {
sigabrt_msg = "mold: aborted\n"
"mold: mold with libtbb version 2021.9.0 or older is known to be unstable "
"under heavy load. Your libtbb version is " +
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <windows.h>
+2 -1
View File
@@ -1,3 +1,4 @@
#include "config.h"
#include "mold.h"
#include <csignal>
@@ -104,7 +105,7 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
setenv("LD_PRELOAD", dso_path.c_str(), 1);
setenv("MOLD_PATH", self.c_str(), 1);
// If ld, ld.lld or ld.gold is specified, run mold itself
// If ld, ld.lld or ld.gold is specified, run mold instead
if (std::string cmd = path_filename(argv[2]);
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
std::vector<char *> args;
+127 -70
View File
@@ -20,8 +20,8 @@
// we don't need to try too hard to reduce thunk size to the absolute
// minimum.
#if MOLD_ARM32 || MOLD_ARM64LE || MOLD_ARM64BE || MOLD_PPC32 || \
MOLD_PPC64V1 || MOLD_PPC64V2
#if MOLD_ARM32LE || MOLD_ARM32BE || MOLD_ARM64LE || MOLD_ARM64BE || \
MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
#include "mold.h"
@@ -45,68 +45,74 @@ static constexpr i64 max_thunk_size = 1024 * 1024;
static constexpr i64 thunk_align = 16;
template <typename E>
static bool is_reachable(Context<E> &ctx, bool first_pass, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel) {
// If the target section is in the same output section but
// hasn't got any address yet, that's unreacahble.
InputSection<E> *isec2 = sym.get_input_section();
if (isec2 && isec.output_section == isec2->output_section &&
isec2->offset == -1)
static bool
requires_thunk(Context<E> &ctx, InputSection<E> &isec, const ElfRel<E> &rel,
bool first_pass) {
if (!is_func_call_rel(rel))
return false;
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
// We don't know about the final file layout on the first pass, so
// we assume pessimistically that all out-of-section relocations are
// out-of-range. Excessive thunks will be removed on the second pass.
if (first_pass) {
// On the first pass, we pessimistically assume that all out-of-section
// relocations are out of range.
InputSection<E> *isec2 = sym.get_input_section();
if (!isec2 || isec.output_section != isec2->output_section)
return false;
return true;
// If the target section is in the same output section but
// hasn't got any address yet, that's unreacahble.
if (isec2->offset == -1)
return true;
// Even if the target is the same section, we branch to its PLT
// if it has one. So a symbol with a PLT is also considered an
// out-of-section reference.
if (sym.has_plt(ctx))
return false;
return true;
}
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are reachable.
if constexpr (is_arm32<E>)
if (bool is_thumb = sym.get_addr(ctx) & 1;
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
(rel.r_type == R_ARM_PLT32 && is_thumb) ||
(rel.r_type == R_ARM_THM_JUMP24 && !is_thumb))
return true;
// On PowerPC, all PLT calls go through range extension thunks.
if constexpr (is_ppc<E>)
if (sym.has_plt(ctx))
return true;
// 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 a thunk to recompute r2 for interworking.
if constexpr (is_ppc64v2<E>)
if ((rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc()))
return true;
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 S = sym.get_addr(ctx, NO_OPD);
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -branch_distance<E> <= val && val < branch_distance<E>;
}
template <typename E>
static bool needs_shim(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel) {
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are within their ranges.
if constexpr (is_arm32<E>) {
bool is_thumb = sym.get_addr(ctx) & 1;
return (rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
(rel.r_type == R_ARM_PLT32 && is_thumb);
}
// On PowerPC, all PLT calls go through range extension thunks.
if constexpr (is_ppc32<E> || is_ppc64v1<E>)
return sym.has_plt(ctx);
// 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>)
return sym.has_plt(ctx) ||
(rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc());
return false;
return val < -branch_distance<E> || branch_distance<E> <= val;
}
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
bool first_pass) {
void Thunk<E>::compute_size() {
offsets.clear();
for (i64 i = 0; i <= symbols.size(); i++)
offsets.push_back(E::thunk_hdr_size + i * E::thunk_size);
}
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
std::span<InputSection<E> *> m = members;
if (m.empty())
return;
@@ -122,7 +128,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
// 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.
// Input sections between B and C are the current batch.
//
// A is the input section with the smallest address than can reach
// from the current batch.
@@ -181,33 +187,21 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
// Create a new thunk and place it at D.
offset = align_to(offset, thunk_align);
thunks.emplace_back(std::make_unique<Thunk<E>>(*this, offset));
Thunk<E> &thunk = *thunks.back();
std::mutex mu;
// Scan relocations between B and C to collect symbols that need
// entries in the new thunk.
std::mutex mu;
tbb::parallel_for(b, c, [&](i64 i) {
InputSection<E> &isec = *m[i];
for (const ElfRel<E> &rel : isec.get_rels(ctx)) {
if (!is_func_call_rel(rel))
continue;
// Skip if the symbol is undefined. apply_reloc() will report an error.
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.file)
continue;
// Skip if we can directly branch to the destination.
if (is_reachable(ctx, first_pass, isec, sym, rel) &&
!needs_shim(ctx, sym, rel))
continue;
// Add the symbol to the current thunk if it's not added already.
if (!sym.flags.test_and_set()) {
std::scoped_lock lock(mu);
thunk.symbols.push_back(&sym);
if (requires_thunk(ctx, isec, rel, true)) {
if (Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
!sym.flags.test_and_set()) {
std::scoped_lock lock(mu);
thunk.symbols.push_back(&sym);
}
}
}
});
@@ -219,6 +213,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
// Now that we know the number of symbols in the thunk, we can compute
// the thunk's size.
thunk.compute_size();
assert(thunk.size() < max_thunk_size);
offset += thunk.size();
@@ -226,6 +221,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
b = c;
}
// Reset flags for future use
for (; t < thunks.size(); t++)
for (Symbol<E> *sym : thunks[t]->symbols)
sym->flags = 0;
@@ -233,15 +229,75 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
this->shdr.sh_size = offset;
}
// create_range_extension_thunks() creates thunks with a pessimistic
// assumption that all out-of-section references are out of range.
// After computing output section addresses, we revisit all thunks to
// remove unneeded entries from them.
//
// We create more thunks than necessary and then eliminate some of
// them later, instead of just creating thunks at this stage. This is
// because we can safely shrink sections after assigning addresses to
// them without worrying about making existing references to thunks go
// out of range. On the other hand, if we insert thunks after
// assigning addresses to sections, references to thunks could become
// out of range due to the new extra gaps for thunks. Thus, the
// creation of thunks is a two-pass process.
template <>
void remove_redundant_thunks(Context<E> &ctx) {
Timer t(ctx, "remove_redundant_thunks");
set_osec_offsets(ctx);
// Gather output executable sections
std::vector<OutputSection<E> *> sections;
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, false);
sections.push_back(osec);
// Mark all symbols that actually need range extension thunks
for (OutputSection<E> *osec : sections) {
tbb::parallel_for_each(osec->members, [&](InputSection<E> *isec) {
for (const ElfRel<E> &rel : isec->get_rels(ctx))
if (requires_thunk(ctx, *isec, rel, false))
isec->file.symbols[rel.r_sym]->flags.test_and_set();
});
}
// Remove symbols from thunks if they don't actually need range
// extension thunks
for (OutputSection<E> *osec : sections) {
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks) {
std::erase_if(thunk->symbols, [&](Symbol<E> *sym) { return !sym->flags; });
thunk->shrink_size(ctx);
}
}
// Recompute section sizes
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
std::span<InputSection<E> *> m = osec->members;
std::span<std::unique_ptr<Thunk<E>>> t = osec->thunks;
i64 offset = 0;
while (!m.empty() || !t.empty()) {
if (!m.empty() && (t.empty() || m[0]->offset < t[0]->offset)) {
offset = align_to(offset, 1 << m[0]->p2align);
m[0]->offset = offset;
offset += m[0]->sh_size;
m = m.subspan(1);
} else {
offset = align_to(offset, thunk_align);
t[0]->offset = offset;
offset += t[0]->size();
t = t.subspan(1);
}
}
osec->shdr.sh_size = offset;
});
// Reset flags for future use
for (OutputSection<E> *osec : sections)
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks)
for (Symbol<E> *sym : thunk->symbols)
sym->flags = 0;
}
// When applying relocations, we want to know the address in a reachable
@@ -259,7 +315,8 @@ void gather_thunk_addresses(Context<E> &ctx) {
std::vector<OutputSection<E> *> sections;
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
sections.push_back(osec);
if (osec->shdr.sh_flags & SHF_EXECINSTR)
sections.push_back(osec);
ranges::stable_sort(sections, {}, [](OutputSection<E> *x) {
return x->shdr.sh_addr;
@@ -269,8 +326,8 @@ void gather_thunk_addresses(Context<E> &ctx) {
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks) {
for (i64 i = 0; i < thunk->symbols.size(); i++) {
Symbol<E> &sym = *thunk->symbols[i];
sym.add_aux(ctx);
ctx.symbol_aux[sym.aux_idx].thunk_addrs.push_back(thunk->get_addr(i));
u64 addr = thunk->get_addr() + thunk->offsets[i];
sym.add_thunk_addr(ctx, addr);
}
}
}
+7 -2
View File
@@ -30,8 +30,8 @@ endif()
if(MOLD_ENABLE_QEMU_TESTS)
list(APPEND QEMU_ARCHS
x86_64 i386 arm aarch64 aarch64_be ppc ppc64 ppc64le sparc64 sh4 sh4eb
s390x riscv64 riscv32 m68k loongarch64)
x86_64 i386 arm armeb aarch64 aarch64_be ppc ppc64 ppc64le sparc64
sh4 sh4eb s390x riscv64 riscv32 m68k loongarch64)
LIST(APPEND TRIPLES
x86_64-linux-gnu
@@ -39,6 +39,7 @@ if(MOLD_ENABLE_QEMU_TESTS)
aarch64-linux-gnu
aarch64_be-linux-gnu
arm-linux-gnueabihf
armeb-linux-gnueabihf
riscv64-linux-gnu
powerpc-linux-gnu
powerpc64-linux-gnu
@@ -121,6 +122,10 @@ if(${MACHINE} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
add_target(arm arm-linux-gnueabihf)
endif()
if(${MACHINE} STREQUAL "armeb" OR (HAS_qemu-armeb AND HAS_armeb-linux-gnueabihf-gcc))
add_target(armeb armeb-linux-gnueabihf)
endif()
if(${MACHINE} STREQUAL "riscv64" OR (HAS_qemu-riscv64 AND HAS_riscv64-linux-gnu-gcc))
add_target(riscv64 riscv64-linux-gnu)
endif()
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF > $t/a.c
#include <stdio.h>
void fn3();
void fn4();
__attribute__((section(".low"))) void fn1() { printf(" fn1"); fn3(); }
__attribute__((section(".low"))) void fn2() { printf(" fn2"); fn4(); }
int main() {
printf(" main");
fn1();
printf("\n");
}
EOF
cat <<EOF > $t/b.c
#include <stdio.h>
void fn1();
void fn2();
__attribute__((section(".high"))) void fn3() { printf(" fn3"); fn2(); }
__attribute__((section(".high"))) void fn4() { printf(" fn4"); }
EOF
$CC -c -o $t/c.o $t/a.c
$CC -c -o $t/d.o $t/b.c
$CC -B. -o $t/exe1 $t/c.o $t/d.o \
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x100000000
$QEMU $t/exe1 | grep 'main fn1 fn3 fn2 fn4'
$CC -B. -o $t/exe2 $t/c.o $t/d.o \
-Wl,--section-start=.high=0x10000000,--section-start=.low=0x100000000
$QEMU $t/exe2 | grep 'main fn1 fn3 fn2 fn4'
$CC -c -o $t/e.o $t/a.c -fno-PIC -mcmodel=large
$CC -c -o $t/f.o $t/b.c -fno-PIC -mcmodel=large
$CC -B. -o $t/exe3 $t/e.o $t/f.o -pie \
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x400000000
$QEMU $t/exe3 | grep 'main fn1 fn3 fn2 fn4'
$CC -B. -o $t/exe4 $t/e.o $t/f.o -pie \
-Wl,--section-start=.high=0x10000000,--section-start=.low=0x400000000
$QEMU $t/exe4 | grep 'main fn1 fn3 fn2 fn4'
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() {}
EOF
not $CC -B. -o $t/exe $t/a.o -mbe32 |& grep 'be32 is not supported'
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -x assembler -
.section .foo, "ax"
foo:
call bar
EOF
cat <<EOF | $CC -o $t/b.o -c -x assembler -
.globl bar
.section .bar, "ax"
bar:
ret
EOF
cat <<EOF | $CC -o $t/c.o -c -xc -
int main() {}
EOF
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -Wl,--section-start=.foo=0x10000 \
-Wl,--section-start=.bar=0x8000f000
not $CC -B. -o $t/exe2 $t/a.o $t/b.o $t/c.o -Wl,--section-start=.foo=0x10000 \
-Wl,--section-start=.bar=0x8000f800 |&
grep -F 'relocation R_RISCV_CALL_PLT against bar out of range'
+18 -23
View File
@@ -1,33 +1,28 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
int foo() {
return 0;
}
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
int fn1() { return 42; }
EOF
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
__attribute__((weak)) int foo();
int bar() {
if (foo) return foo();
return 0;
}
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc - -L$t -lfoo
int fn1();
int fn2() { return fn1(); }
EOF
cat <<EOF | $CC -xc -c -o $t/c.o -
int bar();
int main() {
return bar();
}
cat <<EOF | $CC -o $t/libbaz.so -shared -fPIC -Wl,-soname,libbaz.so -xc - -L$t -lbar
int fn2();
int fn3() { return fn2(); }
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
cat <<EOF | $CC -o $t/a.o -c -xc -
int fn3();
int main() { fn3(); }
EOF
readelf --dynamic $t/exe > $t/log
not grep libfoo.so $t/log
grep libbar.so $t/log
$CC -B. -o $t/exe $t/a.o -L$t -Wl,--as-needed -lbaz -lbar
readelf -W --dynamic $t/exe > $t/log2
not grep libfoo $t/log2
not grep libbar $t/log2
grep libbaz $t/log2
+5
View File
@@ -13,3 +13,8 @@ EOF
readelf --dynamic $t/b.so > $t/log
grep -F 'Auxiliary library: [foo]' $t/log
grep -F 'Auxiliary library: [bar]' $t/log
not ./mold -o exe $t/a.o -f bar |& grep 'auxiliary may not be used without -shared'
# -fuse-ld is ignored
./mold -o exe $t/a.o -fuse-ld=mold
+3 -2
View File
@@ -7,6 +7,7 @@ canonical_name() {
case $1 in
i?86) echo i686 ;;
amd64) echo x86_64 ;;
armeb*) echo armeb ;;
arm*) echo arm ;;
powerpc) echo ppc ;;
powerpc64) echo ppc64 ;;
@@ -64,7 +65,7 @@ t=$TESTDIR/$testname
mkdir -p $t
case $MACHINE in
x86_64 | i686 | arm)
x86_64 | i686 | arm | armeb)
tlsdesc_opt=-mtls-dialect=gnu2 ;;
aarch64 | riscv* | loongarch*)
tlsdesc_opt=-mtls-dialect=desc ;;
@@ -101,7 +102,7 @@ supports_ifunc() {
supports_tlsdesc() {
# musl's tlsdesc on arm32 seems to be broken
[ $MACHINE = arm ] && is_musl && return 1
[[ $MACHINE = arm* ]] && is_musl && return 1
# FreeBSD's loader doesn't seem to support TLSDESC relocs in an executable
[ "$(uname)" = FreeBSD ] && return 1
+1 -1
View File
@@ -2,7 +2,7 @@
. $(dirname $0)/common.inc
# arm-linux-gnueabihf-objcopy crashes on x86-64
[ $MACHINE = arm ] && skip
[[ $MACHINE = arm* ]] && skip
[ $MACHINE = riscv32 ] && skip
command -v zstdcat >& /dev/null || skip
+1 -1
View File
@@ -2,7 +2,7 @@
. $(dirname $0)/common.inc
# Currently, CREL is not supported on REL-type targets
[ $MACHINE = arm ] && skip
[[ $MACHINE = arm* ]] && skip
[ $MACHINE = i686 ] && skip
[ "$CC" = cc ] || skip
+3 -3
View File
@@ -6,16 +6,16 @@ cat <<EOF > $t/a.h
#define B 99
EOF
cat <<EOF | $GCC -o $t/b.o -c -xc - -I$t -g3
cat <<EOF | $CC -o $t/b.o -c -xc - -I$t -g3
#include "a.h"
extern int z();
int main () { return z() - 122; }
EOF
cat <<EOF | $GCC -o $t/c.o -c -xc - -I$t -g3
cat <<EOF | $CC -o $t/c.o -c -xc - -I$t -g3
#include "a.h"
int z() { return A + B; }
EOF
$GCC -B. -o $t/exe $t/b.o $t/c.o
$CC -B. -o $t/exe $t/b.o $t/c.o
$OBJDUMP --dwarf=macro $t/exe | not grep 'DW_MACRO_import -.* 0x0$'
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
void foo() {}
EOF
echo '{ global: foo; local: *; };' > $t/b.ver
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--default-symver -Wl,--version-script=$t/b.ver
readelf --dyn-syms $t/c.so | grep -F ' foo@@c.so'
+2 -2
View File
@@ -22,8 +22,8 @@ void bar();
int main() { bar(); }
EOF
$CC -B. -o $t/exe1 $t/c.o $t/b.so
$CC -B. -o $t/exe1 $t/c.o $t/b.so -Wl,--allow-shlib-undefined
readelf --dynamic $t/exe1 | grep 'Dependency audit library:..*/a.so'
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo -Wl,--allow-shlib-undefined
readelf --dynamic $t/exe2 | grep 'Dependency audit library:..*foo:.*/a.so'
+1 -1
View File
@@ -6,7 +6,7 @@
# musl libc does not support init/fini on ARM
# https://github.com/rui314/mold/issues/951
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
[[ $MACHINE = arm* -o $MACHINE = aarch64 ]] && is_musl && skip
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
void keep();
+7 -4
View File
@@ -6,8 +6,11 @@ cat <<EOF | $CC -c -o $t/a.o -x assembler -
_start:
EOF
./mold -o $t/exe $t/a.o
readelf --sections $t/exe | not grep -F .interp
./mold -o $t/exe1 $t/a.o
readelf -WS $t/exe1 | not grep -F .interp
./mold -o $t/exe $t/a.o --dynamic-linker=/foo/bar
readelf --sections $t/exe | grep -F .interp
./mold -o $t/exe2 $t/a.o --dynamic-linker=/foo/bar
readelf -WS $t/exe2 | grep -F .interp
./mold -o $t/exe3 $t/a.o --dynamic-linker=/foo/bar -static
readelf -WS $t/exe3 | not grep -F .interp
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = arm ] && skip
[[ $MACHINE = arm* ]] && skip
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -
#include <cstdio>
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
void foo() {}
EOF
$CC -B. -shared -o $t/b.so $t/a.o
./mold -shared -o $t/c.so $t/b.so
readelf -W --dynamic $t/c.so | grep -F /b.so
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $GCC -c -o $t/a.o -xc -
cat <<EOF | $CC -c -o $t/a.o -xc -
#include <stdio.h>
__attribute__((section(".gnu.warning.foo")))
+1 -1
View File
@@ -2,7 +2,7 @@
. $(dirname $0)/common.inc
# ARM32's strip command crashes on the output of this test for some reason.
[ $MACHINE = arm ] && skip
[[ $MACHINE = arm* ]] && skip
strip=$STRIP
command -v $strip >& /dev/null || skip
+1 -1
View File
@@ -9,4 +9,4 @@ echo 'int main() {}' | clang -c -o $t/a.o -xc -
echo 'void foo() {}' | clang -c -o $t/b.o -xc - -flto
not ./mold -o /dev/null $t/a.o $t/b.o |&
grep "b.o: don't know how to handle this LTO object file"
grep "b.o: unable to handle this LTO object file"
@@ -1,7 +1,7 @@
#!/bin/bash
. $(dirname $0)/common.inc
seq 1 100000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' |
seq 1 100000 | sed 's/.*/.section .data.&,"aw"\n.word 0\n/g' |
$CC -c -xassembler -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o -
File renamed without changes.
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
. $(dirname $0)/common.inc
seq 1 100000 | sed 's/.*/.globl foo&\n.section .foo&,"aw"\nfoo&:.word 0\n/g' |
$CC -c -xassembler -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o - -fPIC
extern int foo100000;
int bar() { return foo100000; }
EOF
not $CC -B. -shared -o $t/c.so $t/a.o $t/b.o |& grep -F 'too many output sections'
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
void foo();
void bar() { foo(); }
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc - -L$t -lbar
void baz();
void foo() { baz(); }
EOF
cat <<EOF | $CC -c -o $t/a.o -c -xc -
int bar();
int main() { bar(); }
EOF
not $CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lfoo -lbar |&
grep 'libfoo.so: --no-allow-shlib-undefined: undefined symbol: baz'
+2
View File
@@ -1,6 +1,8 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
+2
View File
@@ -1,6 +1,8 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
void foo() {}
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
void foo();
void bar() { foo(); }
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbaz.so -xc - -L$t -lbar
void bar();
void baz() { bar(); }
EOF
cat <<EOF | $CC -c -o $t/a.o -c -xc -
int baz();
int main() { baz(); }
EOF
mv $t/libfoo.so $t/libfoo.so.bak
echo | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
$CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lbar -lbaz
not $CC -B. -o $t/exe2 $t/a.o -Wl,--no-allow-shlib-undefined \
-L$t -lfoo -lbar -lbaz |& grep 'no-allow-shlib-undefined: undefined symbol: foo'
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
void f1() {}
EOF
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
void f1();
void f2() { f1(); }
EOF
cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
void f1();
void f3() { f1(); }
EOF
$CC -B. -o $t/libfoo.so -shared $t/a.o
$CC -B. -o $t/libbar.so -shared $t/b.o -L$t -lfoo
$CC -B. -o $t/libbaz.so -shared $t/c.o
cat <<EOF | $CC -o $t/d.o -c -xc - -fPIC
void f2();
void f3();
int main() { f2(); f3(); }
EOF
$CC -B. -o $t/exe $t/d.o -L$t -lbar -lbaz
+1 -1
View File
@@ -3,10 +3,10 @@
[ $MACHINE = i686 ] && skip
[ $MACHINE = m68k ] && skip
[ $MACHINE = arm ] && skip
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = sh4 ] && skip
[[ $MACHINE = arm* ]] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -shared -o $t/a.so -xc -
Binary file not shown.
+2 -2
View File
@@ -6,12 +6,12 @@ nm mold | grep '__tsan_init' && skip
test_cflags -g3 -gz || skip
cat <<EOF | $GCC -c -o $t/a.o -xc - -g3 -gz
cat <<EOF | $CC -c -o $t/a.o -xc - -g3 -gz
#include <stdio.h>
void hello() { printf("Hello world\n"); }
EOF
cat <<EOF | $GCC -c -o $t/b.o -xc - -g3 -gz
cat <<EOF | $CC -c -o $t/b.o -xc - -g3 -gz
void hello();
int main() { hello(); }
EOF
+1 -1
View File
@@ -4,7 +4,7 @@
# OneTBB isn't tsan-clean
nm mold | grep '__tsan_init' && skip
seq 1 80000 | sed 's/.*/.section .data.\0,"aw"\n.word 0\n/g' |
seq 1 80000 | sed 's/.*/.section .data.&,"aw"\n.word 0\n/g' |
$CC -c -xassembler -o $t/a.o -
cat <<'EOF' | $CC -c -xc -o $t/b.o -
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -o $t/a.o -xc -
void foo();
int main() { foo(); }
EOF
cat <<EOF | $CC -c -o $t/b.o -xc -
void foo() {}
EOF
echo $t/a.o > $t/rsp1
echo $t'"\/b."\o' >> $t/rsp1
./mold -o $t/c.so -shared @$t/rsp1
echo $t/a.o > $t/rsp2
echo '\foo\bar' >> $t/rsp2
not ./mold -o $t/d.so -shared @$t/rsp2 |& grep 'cannot open foobar:'
+1 -2
View File
@@ -16,5 +16,4 @@ void bar() {}
EOF
echo "'$t/b.o' '$t/c.o'" > $t/rsp
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp
./mold -o $t/d.so -shared $t/a.o @$t/rsp
+1 -2
View File
@@ -12,5 +12,4 @@ EOF
echo "'$t/b.o'" > $t/rsp1
echo "@$t/rsp1" > $t/rsp2
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp2
./mold -o $t/c.so -shared $t/a.o @$t/rsp2
Loaded 100 of 456 files, more files were not shown because too many files have changed in this diff. Show more