Compare commits

...
225 Commits
Author SHA1 Message Date
Rui Ueyama edc2f852a1 Bump mold version to 2.3.2 2023-11-04 15:44:32 +09:00
Rui Ueyama 348e44a9c4 Reorganize tests 2023-11-04 14:43:47 +09:00
Rui Ueyama 42f663cac0 Silence TSAN errors that I believe false 2023-11-04 09:07:28 +09:00
Rui Ueyama 4dd5d2f8cb Propagate mergeable sections' sh_entsize and sh_addralign attributes
For --relocatable output, we need to pass through these attributes
for correctness. With this change, we'll have more .rodata sections
such as .rodata.str1.1, rodata.str1.4, etc. in the final executable,
but that should be fine because the runtime doesn't care about
section tables at all. The table isn't even loaded to memory at runtime.
2023-11-03 21:34:17 +09:00
Rui Ueyama 4cdfc7e72c Do not emit text relocations for IFUNC symbols in PDEs
IFUNC symbols are resolved at process startup by executing the function
that the symbol points to. This is used to select the "best" function at
runtime; for instance, the runtime may choose a faster version of memcpy
that uses SIMD instructions if they are available on the current system.

Thus, an IFUNC symbol has two addresses: the initial address (or the
resolver's address) and the resolved address, which is the return value of
the resolver.

In position-independent executables (PIEs), function pointers are loaded
from the GOT indirectly, and symbols are not directly referenced. In such
executables, the initial value of the GOT slot for an IFUNC symbol
contains the resolver address, and this is overwritten at runtime to the
resolved address upon process startup. When user code takes a pointer to
an IFUNC, it always reads the resolved address from GOT.

In contrast, position-dependent executables (PDEs) may have instructions
that directly refer to an IFUNC symbol, such as movabs on x86-64. The GOT
entry for an IFUNC holds the resolved address, so any direct reference
must also produce the resolved address to maintain pointer equality.
(C/C++ standards require that two pointers must be equal if and only if
they are taken for the same symbol.)

Previously, we emitted text relocations to modify instruction operands.
However, text relocations are undesirable and not always reliable. For
example, on ARM64, multiple instructions are used to materialize a
symbol's address, and it's not feasible to issue a dynamic relocation to
alter those instructions since the dynamic loader generally can only
modify 32-bit or 64-bit words.

In this commit, I have adopted a different strategy. An IFUNC symbol now
occupies two consecutive GOT slots in a PDE. The first slot holds the
symbol's PLT address, and the second slot holds the resolved address. The
PLT address is consistently used as the symbol's address throughout the
process, while the second slot is used only by the PLT entry to jump to
the resolved address.

This method ensures pointer equality without the need to emit text
relocations for IFUNC symbols in PDEs.
2023-11-02 12:27:55 +09:00
Rui Ueyama 9516da1751 Simplify 2023-10-31 21:14:48 +09:00
Rui Ueyama f3de82b2a2 Do not add the same set of tests twice 2023-10-31 19:32:46 +09:00
Rui Ueyama c4c506c1cb Make LoongArch range extension thunks shorter 2023-10-31 16:19:20 +09:00
Rui Ueyama 0c62a91ded Enable loongarch64 tests on loongarch64 hosts 2023-10-31 16:15:14 +09:00
Rui Ueyama 2fd0a4c60a Remove a warning because it's a bit too verbose 2023-10-31 16:10:42 +09:00
Rui Ueyama d3188e39df [LoongArch] Implement a workaround for R_LARCH_PCALA_LO12
It seems R_LARCH_PCALA_LO12 is sometimes used against JIRL even though
such use case is incorrect as per the psABI.

At least, `/usr/lib/loongarch64-linux-gnu/libc_nonshared.a(atexit.oS)`
on `cfarm400.cfarm.net` contains such relocation.

We need this patch to self-host mold on that cfarm machine.
2023-10-31 14:11:43 +09:00
Rui Ueyama 3229b5b7e3 Fix test 2023-10-31 12:22:39 +09:00
Rui Ueyama d9ede96aca Fix an incorrect assertion 2023-10-30 13:31:22 +09:00
Rui Ueyama 4b4219fc33 Fix tests for ARM32-on-ARM64 systems 2023-10-30 13:02:36 +09:00
Rui Ueyama 02ead29989 Fix build issue for ARM32-on-ARM64 systems 2023-10-30 13:02:35 +09:00
Rui Ueyama 37919009ab Do not report incompatibe relocation table as an error
Specifically, .llvm.call-graph-profile is always REL even on x86-64.
We can safely ignore the relocation table because it contains only
R_NONE relocations.

Fixes https://github.com/rui314/mold/issues/653
2023-10-28 19:12:20 +09:00
Rui Ueyama 782ff4f582 Fix test 2023-10-27 13:40:09 +09:00
Rui Ueyama 3c468c71a4 Merge pull request #1136 from sicherha/fix_aarch64_variant-pcs_test
Fix `aarch64_variant-pcs.sh` test on Linux
2023-10-27 11:38:54 +09:00
Christoph Erhardt a31679cf04 Fix aarch64_variant-pcs.sh test on Linux
The test expects the output format given by `llvm-readelf`:
```
5: 0000000000010698     0 FUNC    GLOBAL DEFAULT [VARIANT_PCS]   13 foo
```

However, in GNU's `readelf` implementation the symbol name comes first:
```
5: 0000000000010698     0 FUNC    GLOBAL DEFAULT   13 foo  [VARIANT_PCS]
```

Replace the regular expression with two `grep` calls piped together to
support both output formats.

Signed-off-by: Christoph Erhardt <[email protected]>
2023-10-26 23:59:24 +02:00
Rui Ueyama 9cd65ed152 Emit ARM32 mapping symbols for .plt and .plt.got 2023-10-26 13:20:24 +09:00
Rui Ueyama 79ad405319 Emit ARM32 mapping symbol strings only once 2023-10-26 13:20:24 +09:00
Rui Ueyama b60a164fcc Honor the --chroot option when handling --Map or --dependency-file 2023-10-24 12:34:24 +09:00
Rui Ueyama 2d4ba3ac74 Skip a test if musl
musl's tlsdesc implementation for arm32 is buggy.

Fixes https://github.com/rui314/mold/issues/1135
2023-10-23 23:47:13 +09:00
Rui Ueyama 42eabf9f71 Run a test for all targets 2023-10-23 18:39:53 +09:00
Rui Ueyama aa4b3f7d01 Attemp to fix a test
https://github.com/rui314/mold/issues/1134
2023-10-23 17:54:36 +09:00
Rui Ueyama 2d121b8d6d Inline larger relro segments 2023-10-23 16:33:10 +09:00
Rui Ueyama c86a59a511 Fill gaps in executable segments with trap or nop instructions 2023-10-23 12:26:56 +09:00
Rui Ueyama f7dea3a21b Revert "Do not create a PLT for an IFUNC unless necessary"
This reverts commit 651ae8a2a8 because
it broke CI.
2023-10-22 22:35:49 +09:00
Rui Ueyama 651ae8a2a8 Do not create a PLT for an IFUNC unless necessary 2023-10-22 20:31:36 +09:00
Rui Ueyama 5f7aca6276 Revert "Make container interruptible with Ctrl-C"
This reverts commit 5c4955763b because
it broke CI.
2023-10-22 15:53:50 +09:00
Rui Ueyama 39cdf61695 Create a lockfile in $XDG_RUNTIME_DIR instead of $HOME
https://github.com/rui314/mold/issues/117
2023-10-22 13:28:13 +09:00
Rui Ueyama 5c4955763b Make container interruptible with Ctrl-C 2023-10-22 13:04:56 +09:00
Rui Ueyama dae070283b Do not depend on the current path 2023-10-22 12:56:48 +09:00
Rui Ueyama 403655904b Merge two scripts into one 2023-10-21 23:30:12 +09:00
Rui Ueyama 44942b638b Do not copy mold and mold-wrapper.so to the top source directory 2023-10-21 20:49:43 +09:00
Rui Ueyama ceffd4679b Simplify 2023-10-21 17:50:36 +09:00
Rui Ueyama 6fa742be67 Improve data locality
It looks like this change makes the linker slightly faster.
2023-10-21 16:52:38 +09:00
Rui Ueyama 484e357971 Simplify 2023-10-21 13:13:56 +09:00
Rui Ueyama 53a5a37370 Refactor 2023-10-20 22:33:39 +09:00
Rui Ueyama 41579888b6 Add a timer prober 2023-10-20 20:25:07 +09:00
Rui Ueyama 1e2d84038e Simplify 2023-10-20 18:50:10 +09:00
Rui Ueyama 6c7700b051 Update the manual 2023-10-20 16:45:48 +09:00
Rui Ueyama 34c53777fb Bump mold version to 2.3.1 2023-10-20 13:15:31 +09:00
Rui Ueyama 7be1b66791 Fix range extension thunks
Fixes https://github.com/rui314/mold/issues/1132
2023-10-20 10:33:57 +09:00
Rui Ueyama 4bd80ec21c [LoongArch] Remove relocation overflow checks
These relocations may overflow with -mcmodel=extreme.
2023-10-19 20:44:23 +09:00
Rui Ueyama a9ab0ff452 [LoongArch] Fix -mcmodel=extreme
Based on MQ-mengqing's patch.

Fixes https://github.com/rui314/mold/issues/1131
2023-10-19 19:46:32 +09:00
Rui Ueyama 206bf65c50 Fix test
Fixes https://github.com/rui314/mold/issues/1130
2023-10-19 17:36:37 +09:00
Rui Ueyama 2b3e8eec05 Merge pull request #1129 from vihu/rg/artix 2023-10-19 15:08:51 +09:00
Rahul Garg 9eac818254 Add support for artix
Summary
----
This PR adds support for [Artix OS](https://artixlinux.org/).
2023-10-18 23:56:42 -06:00
Rui Ueyama 6fe0cadfa1 Enable building of RV64 distribution file 2023-10-19 14:12:54 +09:00
Rui Ueyama 1a2baee096 Fix test
`test_cflags` uses $CC to test if a given flag is supported, so
we should use $CC instead of $GCC for the rest of the file.

Fixes https://github.com/rui314/mold/issues/1128
2023-10-19 11:44:41 +09:00
Rui Ueyama 728b265cbd Fix -Wc++11-narrowing on 32-bit machines
Fixes https://github.com/rui314/mold/issues/1127
2023-10-19 11:33:54 +09:00
Rui Ueyama 9fa61b9298 Update third-party/rust-demangle
Updated to 4283d46e4064a7e1c54bc9918a07b066cb43fca3.

Fixed https://github.com/rui314/mold/issues/1127
2023-10-19 11:33:50 +09:00
Rui Ueyama 6ad942cb27 Bump mold version to 2.3.0 2023-10-18 19:24:17 +09:00
Rui Ueyama eed4496af2 Fix test
Older versions of GNU assembler don't know `.variant_pcs` directive.
2023-10-18 19:24:02 +09:00
Rui Ueyama 17c292c2b4 Fix CI
It looks like older versions of gdb falls into an infinite loop
if the gdb hash table is full.
2023-10-18 15:52:14 +09:00
Rui Ueyama ab1ab78f8a Fix gdb-index hash computation 2023-10-18 09:56:32 +09:00
Rui Ueyama 8506065fd2 Refactor 2023-10-17 20:19:07 +09:00
Rui Ueyama a291d0d448 Refactor 2023-10-17 18:41:10 +09:00
Rui Ueyama 690fefb97d Read .debug_gnu_{pubnames,pubtypes} correctly 2023-10-17 17:25:10 +09:00
Rui Ueyama c7c1779ad7 Fix CI 2023-10-17 17:04:53 +09:00
Rui Ueyama ec095e7513 Do not emit address areas for eliminated sections 2023-10-17 16:39:32 +09:00
Rui Ueyama 69a91e8aa0 --gdb-index: Fix address table 2023-10-17 15:53:55 +09:00
Rui Ueyama af46000645 Support DWARF64 for --gdb_index
Fixes https://github.com/rui314/mold/issues/1048
2023-10-17 15:19:35 +09:00
Rui Ueyama b1658c4702 Refactor 2023-10-17 15:19:35 +09:00
Rui Ueyama 9f85267b44 Optimize 2023-10-17 12:01:15 +09:00
Rui Ueyama f8d20cd3ce Fix CI 2023-10-17 10:40:21 +09:00
Rui Ueyama a396fa400f Re-implement --gdb-index
This change reduces the size of the .gdb_index section. It also fixes
an issue with handling of clang-generated object files.
2023-10-17 10:27:30 +09:00
Rui Ueyama d8a4f14fd0 Fix CI 2023-10-16 13:55:54 +09:00
Rui Ueyama c8b5fae2d0 Refactor 2023-10-16 13:15:00 +09:00
Rui Ueyama 0ce32d36d5 Allow <4GB input sections
Fixes https://github.com/rui314/mold/issues/1126
2023-10-16 12:40:52 +09:00
Rui Ueyama 01cd5d1a32 Revert "Do not write to an atomic variable too often"
This reverts commit 39a25e2394.
2023-10-13 15:08:27 +09:00
Rui Ueyama 39a25e2394 Do not write to an atomic variable too often 2023-10-13 14:28:56 +09:00
Rui Ueyama bfdd75ed38 Refactor 2023-10-13 14:24:35 +09:00
Rui Ueyama 5411f4216e Fix CI 2023-10-13 14:18:30 +09:00
Rui Ueyama 3cf00e30d5 Refactor 2023-10-13 13:14:01 +09:00
Rui Ueyama 0f714710f0 Fix relocations for POWER10
With this change, all tests now pass on an actual POWER10 machine
(gcc120.fsffrance.org).
2023-10-13 10:11:52 +09:00
Rui Ueyama 83ec409426 Support R_PPC64_TPREL34 2023-10-13 08:24:34 +09:00
Rui Ueyama e6ae83fbf9 Fix POWER10 issues 2023-10-12 21:04:27 +09:00
Rui Ueyama 00702773f4 Format 2023-10-12 12:04:08 +09:00
Rui Ueyama 1739cfbfb4 Refactor 2023-10-10 12:01:08 +09:00
Rui Ueyama ff689a1253 Allow nested response files
Fixes https://github.com/rui314/mold/issues/1124
2023-10-09 19:12:14 +09:00
Rui Ueyama ae427d133e Refactor 2023-10-07 16:50:00 +09:00
Rui Ueyama d92af2456d Refactor 2023-10-07 16:39:46 +09:00
Rui Ueyama b3e1092879 Refactor 2023-10-07 14:01:24 +09:00
Rui Ueyama 46ef71eb75 Fix CI 2023-10-05 20:00:27 +09:00
Rui Ueyama 871ef8cf14 Refactor 2023-10-05 19:06:33 +09:00
Rui Ueyama c1b1caa854 Fix address-taken bit 2023-10-05 14:10:38 +09:00
Rui Ueyama dcab76697e Refactor 2023-10-05 11:47:00 +09:00
Rui Ueyama cf11efb147 Revert "Fix TLSDESC on musl-libc ARM32 systems"
This reverts commit 967006479f because
it made the linker to crash. TLSDESC doesn't seem to work even with
GNU ld on ARM32 musl systems, so I'll disable the test.

Fixes https://github.com/rui314/mold/issues/1116
2023-10-05 11:45:15 +09:00
Rui Ueyama a688b73f8e Merge pull request #1123 from ishitatsuyuki/main-mono-reland
[ELF] Reland "Move main() and redo_main() into a separate compile unit."
2023-10-04 19:48:55 +09:00
Tatsuyuki Ishi 9bb50ae7a5 [ELF] Move main() and redo_main() into a separate compile unit.
Despite the extern template items Clang 14 seems to insist on instantiating
elf_main(), with the hilarious consequence of the function being duplicated
quadratically to the number of archs.

Move this to a different CU so that the compiler cannot possibly
instantiate it more than once. This significantly speeds up compilation
related to main.cc.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-10-04 19:09:26 +09:00
Rui Ueyama ead6235470 Emit an endbr64 only for a canonical PLT 2023-10-04 17:17:07 +09:00
Rui Ueyama 967006479f Fix TLSDESC on musl-libc ARM32 systems
Fixes https://github.com/rui314/mold/issues/1116
2023-10-04 14:54:55 +09:00
Rui Ueyama f843daee9b Revert "[ELF] Move main() and redo_main() into a separate compile unit."
This reverts commit 1d3706285e.

Fixes https://github.com/rui314/mold/issues/1122
2023-10-04 13:07:06 +09:00
Rui Ueyama c7129916ee Merge pull request #1121 from ishitatsuyuki/main-mono
[ELF] Move main() and redo_main() into a separate compile unit.
2023-10-03 19:01:43 +09:00
Rui Ueyama e234ce25b6 Fix CI 2023-10-03 18:58:01 +09:00
Rui Ueyama 02e1b9de10 Remove a useless variable 2023-10-03 18:53:35 +09:00
Rui Ueyama effde5ffa9 Merge pull request #1119 from sicherha/improvement_strict_compiler_warnings
Enable strict compiler warnings by default
2023-10-03 18:50:52 +09:00
Tatsuyuki Ishi 1d3706285e [ELF] Move main() and redo_main() into a separate compile unit.
Despite the extern template items Clang 14 seems to insist on instantiating
elf_main(), with the hilarious consequence of the function being duplicated
quadratically to the number of archs.

Move this to a different CU so that the compiler cannot possibly
instantiate it more than once. This significantly speeds up compilation
related to main.cc.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-10-03 17:48:57 +09:00
Rui Ueyama 085a1148fd Report a duplicate symbol error for archives with --whole-archive
Fixes https://github.com/rui314/mold/issues/1120
2023-10-03 17:36:45 +09:00
Christoph Erhardt 5cf3e53ab9 Enable strict compiler warnings
Compiler warnings have the potential to uncover real bugs before they
make it into a commit. For example, the accidental fallthrough
introduced by commit 713d81947a and fixed
by commit 49f38828d1 could have been
detected by GCC.

Remove `-Wunused-variable` since it is implied by `-Wall`.
Add `-Wno-unused-parameter` to suppress warning spam regarding unused
parameters.

Signed-off-by: Christoph Erhardt <[email protected]>
2023-10-03 08:50:44 +02:00
Christoph Erhardt a5aa387cb4 Add cast to ternary-operator expression
This fixes the following compiler warning:
> enumerated and non-enumerated type in conditional expression [-Wextra]

Signed-off-by: Christoph Erhardt <[email protected]>
2023-10-03 08:50:44 +02:00
Christoph Erhardt f99cb33c2d Remove unused parameters
Signed-off-by: Christoph Erhardt <[email protected]>
2023-10-03 08:50:44 +02:00
Rui Ueyama e55703405b Do not call compute_address_significance twice 2023-10-03 14:30:45 +09:00
Rui Ueyama 4c71174d5d Fix CI 2023-10-02 20:03:24 +09:00
Rui Ueyama 2ea08ac4b3 Revert "Enable to download build artifacts"
This reverts commit 82ace062db.
2023-10-02 19:58:37 +09:00
Rui Ueyama 82ace062db Enable to download build artifacts 2023-10-02 19:11:55 +09:00
Rui Ueyama 17f0d85efb Add an experimental -z rewrite-endbr option
endbr64 is a relatively new x86 instruction to mark a location to which an
indirect jump instruction can transfer control. If the control-flow
integrity is turned on (i.e., endbr64 is effective), an indirect jump can
only target an endbr64 or raise a runtime exception. This mechanism makes
some types of control hijacking attacks, such as ROP or JOP, very hard
because an attacker can no longer jump to an arbitrary location.

If `-fcf-protection` is given, GCC emits an endbr64 at the beginning of
each global function conservatively because the address of that function
might be used by other translation units.

In reality, most function addresses aren't taken, so the conservative
assumption ends up emitting lots of unnecessary endbr64 instructions.
This not only bloats the code but also weakens security because an
attacker would have more locations to attack.

The new linker option, `-z rewrite-endbr`, is designed to mitigate this
situation. The linker is in a position to conduct whole-program analysis
on the input files and determine functions whose addresses have never been
taken. If `-z rewrite-endbr` is given, mold performs that analysis and
rewrites the leading endbr64 with a nop if the function's address wasn't
taken.

For evaluation, I built the mold executable itself both with and without
this option. Before using the `-z rewrite-endbr` option, the mold
executable contained 25,994 endbr64 instructions. With the option, the
number becomes 18,035, a 30% reduction.

The new option is exprerimental; we may remove it in a future versions of
the mold linker.
2023-10-02 17:53:03 +09:00
Rui Ueyama a994d71fa1 Use relocations to identify address-taken functions 2023-10-02 17:05:38 +09:00
Rui Ueyama 0eea8f4015 Refactor 2023-09-30 12:04:20 +09:00
Rui Ueyama 2e3b56ee3f Emit DT_AARCH64_VARIANT_PCS if required
Fixes https://github.com/rui314/mold/issues/1115
2023-09-28 20:57:54 +09:00
Rui Ueyama b8bcb8a2a3 Refactor 2023-09-27 18:17:24 +09:00
Rui Ueyama ac3ee91cbd [RISC-V] Support GP-relative relocations
These relocations are use for embedded programming and are probably
not useful for mold, but we want them for the sake of completeness.
2023-09-26 11:30:02 +09:00
Rui Ueyama 18df821625 Install a required package 2023-09-24 17:26:11 +09:00
Rui Ueyama 79b3fc4b6a Bump mold version to 2.2.0 2023-09-24 12:28:19 +09:00
Rui Ueyama 9e09e74db3 Refactor 2023-09-20 16:58:37 +09:00
Rui Ueyama 9ee10ba4bd Fix test
Fixes https://github.com/rui314/mold/issues/1107
2023-09-18 15:03:41 +09:00
Rui Ueyama fe7c8c26b7 Fix for big-endian hosts 2023-09-17 15:28:35 +09:00
Rui Ueyama 23576b6e48 Refactor 2023-09-17 11:16:39 +09:00
Rui Ueyama cfa9e404d1 Keep the entry symbol when doing LTO
Fixes https://github.com/rui314/mold/issues/1105
2023-09-16 14:43:04 +09:00
Rui Ueyama 641360b0aa Update comments 2023-09-16 14:28:39 +09:00
Rui Ueyama 4ccc9eb89a Fix RISC-V's shrink_section()
r_delta is not available when we are shrinking sections.

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

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

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

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

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

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

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

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

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

```
cmake_minimum_required(VERSION 3.13)
```

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

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

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

Fixes https://github.com/rui314/mold/issues/1036
2023-08-17 11:03:12 +09:00
Rui Ueyama fbcee0b716 Refactor 2023-08-17 10:16:19 +09:00
Rui Ueyama c105cfd35a Fix CI 2023-08-16 23:13:07 +09:00
Rui Ueyama bb402480a5 Refactor 2023-08-16 22:57:16 +09:00
Rui Ueyama c983321fed Upgrade the bundled OneTBB to v2021.10.0 2023-08-16 21:06:41 +09:00
Rui Ueyama 43ebc96bfe Refactor 2023-08-16 20:46:25 +09:00
Rui Ueyama 8ae1ad57a4 Merge pull request #1090 from Romain-Geissler-1A/remove-openssl-mentions
Removing remaining openssl mentions following the move to Blake3.
2023-08-16 20:45:00 +09:00
Romain Geissler bd39bfdc5b Removing remaining openssl mentions following the move to Blake3. 2023-08-16 10:19:38 +00:00
Rui Ueyama a11566795d Update comments 2023-08-16 16:36:26 +09:00
Rui Ueyama 3676c596df Remove CI badge as well
We can see the CI status on the same GitHub homepage.
2023-08-16 14:27:21 +09:00
Rui Ueyama 0947836a44 Refactor 2023-08-16 14:27:09 +09:00
Rui Ueyama 2383e42a5c Merge pull request #1089 from marxin/drop-obs-badge
drop openSUSE OBS badge as the package is unmaintained
2023-08-16 14:26:58 +09:00
Martin Liska 049dbb73fe drop openSUSE OBS badge as the package is unmaintained 2023-08-16 07:19:27 +02:00
Rui Ueyama 95894ef2e2 Attempt to fix Windows CI 2023-08-16 13:36:30 +09:00
Rui Ueyama 7f7a744ebb Use BLAKE3 instead of SHA256 for cryptographic hash computation
Closes https://github.com/rui314/mold/issues/1088
2023-08-16 12:18:10 +09:00
Rui Ueyama 3833a4a457 Add BLAKE3 to third-party/
Since BLAKE3 is available under CC0, we don't need to add its license
terms (or the lack thereof) to our LICENSE.third-party file.
2023-08-16 12:13:53 +09:00
Rui Ueyama a0516a371f Refactor 2023-08-16 10:27:21 +09:00
Rui Ueyama 29491df6f2 Refactor 2023-08-15 18:19:39 +09:00
Rui Ueyama 599ed1dde0 Refactor 2023-08-15 16:56:59 +09:00
Rui Ueyama 143111ae5a Fix CI 2023-08-15 16:45:09 +09:00
Rui Ueyama d753ea5bdb Fix CI 2023-08-15 16:10:35 +09:00
Rui Ueyama b205eb7fa6 Refactor 2023-08-15 15:58:09 +09:00
Rui Ueyama d8baee9605 Refactor 2023-08-15 15:33:28 +09:00
Rui Ueyama 6dacc5192a Refactor 2023-08-15 15:08:34 +09:00
Rui Ueyama 4f5f88258c Fix TLSDESC relocation
Although we do not support TLSDESC on any target on which
ctx.tls_begin != ctx.dtp_addr, my understanding is that the origin
of the TLSDESC base relocation is tls_begin.
2023-08-14 19:55:39 +09:00
Rui Ueyama f5abedad01 Fix _TLS_MODULE_BASE_ address
Since the module base address obtained with `_TLS_MODULE_BASE_` will
be used with foo@dtpoff relocations, it should be the same as the DTP
address.
2023-08-14 19:39:50 +09:00
Rui Ueyama 02aee3b43e Do not put local symbols into .dynsym for TLSDESC
Placing local symbols into .dynsym is not technically incorrect, but
doing so is just a waste of space.
2023-08-14 19:06:35 +09:00
Rui Ueyama c1406860fa Fix -Wunused-variable
Fixes https://github.com/rui314/mold/issues/1086
2023-08-14 11:39:44 +09:00
Rui Ueyama 53759731cf Fix relocation overflow check 2023-08-14 08:39:13 +09:00
Rui Ueyama b018baec8a Refactor 2023-08-13 19:11:38 +09:00
Rui Ueyama 28ddea5371 Revert "Do not create copy relocations for PIE"
This reverts commit 82989a8b7c because
it broke our CI.
2023-08-13 17:55:44 +09:00
Rui Ueyama 82989a8b7c Do not create copy relocations for PIE
Object files have to be compiled with `-fPIE` or `-fPIC` for a
position-independent executable, and such object files shouldn't
contain relocations that directly refers to data objects without
GOT.
2023-08-13 17:02:45 +09:00
936 changed files with 76891 additions and 18913 deletions

No files matched your search

+2 -2
View File
@@ -51,9 +51,9 @@ jobs:
# Install cross toolchains
dpkg --add-architecture i386
./install-build-deps.sh update
apt-get install -y sudo qemu-user gdb zstd dwarfdump xz-utils {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha,mips,mipsel}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf {gcc,g++}-10-{mips64,mips64el}-linux-gnuabi64
apt-get install -y sudo qemu-user gdb zstd dwarfdump xz-utils {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
for i in {i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha,mips,mipsel}-linux-gnu arm-linux-gnueabihf {mips64,mips64el}-linux-gnuabi64; do
for i in {i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu arm-linux-gnueabihf; do
ln -sf /usr/bin/$i-gcc-10 /usr/bin/$i-gcc
ln -sf /usr/bin/$i-g++-10 /usr/bin/$i-g++
done
-8
View File
@@ -3,15 +3,7 @@
*~
**/.DS_Store
.cache
compile_commands.json
/mold
/test/tmp
options.inc
core
ld
ld64
ld64.mold
gentoo
out
/mold-*.tar.gz
/build*
+30 -22
View File
@@ -43,8 +43,8 @@
# of the mold linker of the same version will have the exactly same set of
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.13)
project(mold VERSION 2.1.0)
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.3.2)
include(CMakeDependentOption)
include(GNUInstallDirs)
@@ -64,8 +64,12 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
-fno-exceptions
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-Wall
-Wextra
-Wno-sign-compare
-Wno-unused-function
-Wno-unused-parameter
-Wno-missing-field-initializers
-ggnu-pubnames)
endif()
@@ -97,7 +101,7 @@ if(MOLD_USE_TSAN)
target_link_options(mold PRIVATE -fsanitize=thread)
endif()
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
#
# This option is intended to be used by `./dist.sh` script to create a
# mold binary that works on various Linux distros. You probably don't
@@ -105,13 +109,12 @@ endif()
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
if(MOLD_MOSTLY_STATIC)
target_link_options(mold PRIVATE -static-libstdc++)
target_link_libraries(mold PRIVATE libcrypto.a)
endif()
# Find zlib. If libz.so is not found, we compile a bundled one and
# statically-link it to mold.
find_package(ZLIB QUIET)
if(ZLIB_FOUND)
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
else()
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
@@ -120,12 +123,23 @@ else()
target_link_libraries(mold PRIVATE zlibstatic)
endif()
# Find zstd compression library. Just like zlib, if libzstd.so is not
# found, we compile a bundled one and statically-link it to mold.
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
# is not found, we compile a bundled one and statically-link it to mold.
find_package(BLAKE3 QUIET)
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE BLAKE3::blake3)
else()
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
target_link_libraries(mold PRIVATE blake3)
target_include_directories(mold PUBLIC third-party/blake3/c)
endif()
# Find zstd compression library. If libzstd.so is not found, we compile a
# bundled one and statically-link it to mold.
include(CheckIncludeFile)
check_include_file(zstd.h HAVE_ZSTD_H)
if(HAVE_ZSTD_H)
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE zstd)
else()
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
@@ -146,12 +160,10 @@ endif()
#
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
# be stable on 32-bit targets.
include(CheckCXXSourceCompiles)
check_cxx_source_compiles(
"static_assert(sizeof(void *) == 4); int main() {}" P32)
cmake_dependent_option(
MOLD_USE_MIMALLOC "Use mimalloc" ON
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD" OFF)
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
"NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT P32" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
MOLD_USE_MIMALLOC OFF)
@@ -236,11 +248,6 @@ if(NOT APPLE AND NOT WIN32)
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
endif()
if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
endif()
# If atomics doesn't work by default, add -latomic.
# We need the flag on riscv, armv6 and m68k.
include(CheckCXXSourceCompiles)
@@ -289,14 +296,14 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# on a multicore machine.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
S390X SPARC64 M68K SH4 ALPHA MIPS64LE MIPS64BE LOONGARCH32 LOONGARCH64)
S390X SPARC64 M68K SH4 ALPHA LOONGARCH32 LOONGARCH64)
list(APPEND MOLD_ELF_TEMPLATE_FILES
elf/arch-loongarch.cc
elf/arch-riscv.cc
elf/cmdline.cc
elf/dwarf.cc
elf/gc-sections.cc
elf/gdb-index.cc
elf/icf.cc
elf/input-files.cc
elf/input-sections.cc
@@ -329,10 +336,12 @@ list(APPEND MOLD_MACHO_TEMPLATE_FILES
function(mold_instantiate_templates SOURCE TARGET)
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
if(NOT EXISTS ${PATH})
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
#define MOLD_TARGET ${TARGET}
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
")
endif()
target_sources(mold PRIVATE ${PATH})
endfunction()
@@ -367,7 +376,6 @@ target_sources(mold PRIVATE
elf/arch-arm64.cc
elf/arch-i386.cc
elf/arch-m68k.cc
elf/arch-mips64.cc
elf/arch-ppc32.cc
elf/arch-ppc64v1.cc
elf/arch-ppc64v2.cc
+2 -5
View File
@@ -1,8 +1,5 @@
# mold: A Modern Linker
[![CI](https://github.com/rui314/mold/actions/workflows/ci.yml/badge.svg)](https://github.com/rui314/mold/actions/workflows/ci.yml)
[![build result](https://build.opensuse.org/projects/home:marxin:mold/packages/mold/badge.svg?type=default)](https://build.opensuse.org/package/show/home:marxin:mold/mold)
<i>This repository contains a free version of the mold linker.
If you are looking for a commercial version that supports macOS
please visit the
@@ -74,11 +71,11 @@ necessary packages. You may need to run it as root.
git clone https://github.com/rui314/mold.git
mkdir mold/build
cd mold/build
git checkout v2.1.0
git checkout v2.3.2
../install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
cmake --build . -j $(nproc)
sudo cmake --install .
sudo cmake --build . --target install
```
You might need to pass a C++20 compiler command name to `cmake`. In the
-23
View File
@@ -1,23 +0,0 @@
# This dockerfile creates a reproducible build environment for mold.
#
# $ docker buildx create --use
# $ docker buildx build --platform x86_64,aarch64,arm,ppc64le,s390x -t rui314/mold-builder:latest --push .
FROM ubuntu:18.04
ENV TZ=Europe/London
RUN apt-get update && \
apt-get install -y --no-install-recommends software-properties-common && \
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
\
mkdir /openssl && cd /openssl && \
wget -O- -q https://www.openssl.org/source/openssl-3.0.7.tar.gz | tar --strip-components=1 -xzf - && \
./Configure --prefix=/usr/local --libdir=lib && \
make -j$(nproc) && \
make -j$(nproc) install && \
ldconfig && \
mkdir /cmake && cd /cmake && \
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
rm -rf /var/lib/apt/lists/* /cmake /openssl
+57 -46
View File
@@ -6,58 +6,69 @@ namespace mold {
template <typename Context>
std::vector<std::string_view>
read_response_file(Context &ctx, std::string_view path) {
read_response_file(Context &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<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
u8 *data = mf->data;
std::string_view data((char *)mf->data, mf->size);
auto read_quoted = [&](i64 i, char quote) {
std::string buf;
while (i < mf->size && data[i] != quote) {
if (data[i] == '\\') {
buf.append(1, data[i + 1]);
i += 2;
} else {
buf.append(1, data[i++]);
}
}
if (i >= mf->size)
Fatal(ctx) << path << ": premature end of input";
vec.push_back(save_string(ctx, buf));
return i + 1;
};
auto read_unquoted = [&](i64 i) {
std::string buf;
while (i < mf->size) {
if (data[i] == '\\' && i + 1 < mf->size) {
buf.append(1, data[i + 1]);
i += 2;
continue;
}
if (!isspace(data[i])) {
buf.append(1, data[i++]);
continue;
}
break;
while (!data.empty()) {
if (isspace(data[0])) {
data = data.substr(1);
continue;
}
vec.push_back(save_string(ctx, buf));
return i;
};
auto read_quoted = [&]() {
char quote = data[0];
data = data.substr(1);
for (i64 i = 0; i < mf->size;) {
if (isspace(data[i]))
i++;
else if (data[i] == '\'')
i = read_quoted(i + 1, '\'');
else if (data[i] == '\"')
i = read_quoted(i + 1, '\"');
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);
};
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 (!isspace(data[0])) {
buf.append(1, data[0]);
data = data.substr(1);
continue;
}
break;
}
return save_string(ctx, buf);
};
std::string_view tok;
if (data[0] == '\'' || data[0] == '\"')
tok = read_quoted();
else
i = read_unquoted(i);
tok = read_unquoted();
if (tok.starts_with('@'))
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
else
vec.push_back(tok);
}
return vec;
}
@@ -68,7 +79,7 @@ std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
std::vector<std::string_view> vec;
for (i64 i = 0; argv[i]; i++) {
if (argv[i][0] == '@')
append(vec, read_response_file(ctx, argv[i] + 1));
append(vec, read_response_file(ctx, argv[i] + 1, 1));
else
vec.push_back(argv[i]);
}
+237 -125
View File
@@ -8,6 +8,7 @@
#include <bitset>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
@@ -22,6 +23,7 @@
#include <sys/types.h>
#include <tbb/concurrent_vector.h>
#include <tbb/enumerable_thread_specific.h>
#include <tbb/parallel_for.h>
#include <vector>
#ifdef _WIN32
@@ -224,8 +226,14 @@ struct Atomic : std::atomic<T> {
void operator=(T val) { store(val); }
operator T() const { return load(); }
void store(T val) { std::atomic<T>::store(val, relaxed); }
T load() const { return std::atomic<T>::load(relaxed); }
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; }
@@ -242,6 +250,85 @@ struct Atomic : std::atomic<T> {
}
};
//
// perf.cc
//
// Counter is used to collect statistics numbers.
class Counter {
public:
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
static std::mutex mu;
std::scoped_lock lock(mu);
instances.push_back(this);
}
Counter &operator++(int) {
if (enabled) [[unlikely]]
values.local()++;
return *this;
}
Counter &operator+=(int delta) {
if (enabled) [[unlikely]]
values.local() += delta;
return *this;
}
static void print();
static inline bool enabled = false;
private:
i64 get_value();
std::string_view name;
tbb::enumerable_thread_specific<i64> values;
static inline std::vector<Counter *> instances;
};
// Timer and TimeRecord records elapsed time (wall clock time)
// used by each pass of the linker.
struct TimerRecord {
TimerRecord(std::string name, TimerRecord *parent = nullptr);
void stop();
std::string name;
TimerRecord *parent;
tbb::concurrent_vector<TimerRecord *> children;
i64 start;
i64 end;
i64 user;
i64 sys;
bool stopped = false;
};
void
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
template <typename Context>
class Timer {
public:
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
record = new TimerRecord(name, parent ? parent->record : nullptr);
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
}
Timer(const Timer &) = delete;
~Timer() {
record->stop();
}
void stop() {
record->stop();
}
private:
TimerRecord *record;
};
//
// Bit vector
//
@@ -298,7 +385,7 @@ inline u64 bits(u64 val, u64 hi, u64 lo) {
inline i64 sign_extend(u64 val, i64 size) {
return (i64)(val << (63 - size)) >> (63 - size);
};
}
template <typename T, typename Compare = std::less<T>>
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
@@ -478,53 +565,72 @@ public:
}
~ConcurrentMap() {
if (keys) {
free((void *)keys);
free((void *)key_sizes);
free((void *)values);
}
free(entries);
}
// In order to avoid unnecessary cache-line false sharing, we want
// to make this object to be aligned to a reasonably large
// power-of-two address.
struct alignas(32) Entry {
Atomic<const char *> key;
T value;
u32 keylen;
};
void resize(i64 nbuckets) {
this->~ConcurrentMap();
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
i64 sz = sizeof(Entry) * this->nbuckets;
free(entries);
this->nbuckets = nbuckets;
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(char *));
key_sizes = (u32 *)malloc(nbuckets * sizeof(u32));
values = (T *)malloc(nbuckets * sizeof(T));
#if _WIN32
// Even though std::aligned_alloc is defined in C++17, MSVC doesn't
// seem to provide that function.
entries = (Entry *)_aligned_malloc(sz, alignof(Entry));
#else
entries = (Entry *)std::aligned_alloc(alignof(Entry), sz);
#endif
memset(entries, 0, sz);
}
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
if (!keys)
return {nullptr, false};
assert(has_single_bit(nbuckets));
i64 idx = hash & (nbuckets - 1);
i64 retry = 0;
while (retry < MAX_RETRY) {
const char *ptr = keys[idx].load(std::memory_order_acquire);
if (ptr == marker) {
pause();
Entry &ent = entries[idx];
const char *ptr = nullptr;
bool claimed = ent.key.compare_exchange_weak(ptr, (char *)-1,
std::memory_order_acquire);
// If we successfully claimed the ownership of an unused slot,
// copy values to it.
if (claimed) {
new (&ent.value) T(val);
ent.keylen = key.size();
ent.key.store(key.data(), std::memory_order_release);
return {&ent.value, true};
}
// Loop on a spurious failure.
if (ptr == nullptr)
continue;
// If someone is copying values to the slot, do busy wait.
while (ptr == (char *)-1) {
pause();
ptr = ent.key.load(std::memory_order_acquire);
}
if (ptr == nullptr) {
if (!keys[idx].compare_exchange_weak(ptr, marker,
std::memory_order_acquire))
continue;
new (values + idx) T(val);
key_sizes[idx] = key.size();
keys[idx].store(key.data(), std::memory_order_release);
return {values + idx, true};
}
if (key.size() == key_sizes[idx] &&
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
return {values + idx, false};
// If the same key is already present, this is the slot we are
// looking for.
if (key == std::string_view(ptr, ent.keylen))
return {&ent.value, false};
// Otherwise, move on to the next slot.
u64 mask = nbuckets / NUM_SHARDS - 1;
idx = (idx & ~mask) | ((idx + 1) & mask);
retry++;
@@ -534,17 +640,67 @@ public:
return {nullptr, false};
}
const char *get_key(i64 idx) {
return keys[idx].load(std::memory_order_relaxed);
i64 get_idx(T *value) const {
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
return (Entry *)addr - entries;
}
// Return a list of map entries sorted in a deterministic order.
std::vector<Entry *> get_sorted_entries(i64 shard_idx) {
if (nbuckets == 0)
return {};
i64 shard_size = nbuckets / NUM_SHARDS;
i64 begin = shard_idx * shard_size;
i64 end = begin + shard_size;
i64 sz = 0;
for (i64 i = begin; i < end; i++)
if (entries[i].key)
sz++;
std::vector<Entry *> vec;
vec.reserve(sz);
// 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)
vec.push_back(entries + --end);
// Find entries contiguous in the buckets and sort them.
i64 last = 0;
for (i64 i = begin; i < end;) {
while (i < end && entries[i].key)
vec.push_back(entries + i++);
std::sort(vec.begin() + last, vec.end(), [](Entry *a, Entry *b) {
if (a->keylen != b->keylen)
return a->keylen < b->keylen;
return memcmp(a->key, b->key, a->keylen) < 0;
});
last = vec.size();
while (i < end && !entries[i].key)
i++;
}
return vec;
}
std::vector<Entry *> get_sorted_entries_all() {
std::vector<std::vector<Entry *>> vec(NUM_SHARDS);
tbb::parallel_for((i64)0, NUM_SHARDS, [&](i64 i) {
vec[i] = get_sorted_entries(i);
});
return flatten(vec);
}
static constexpr i64 MIN_NBUCKETS = 2048;
static constexpr i64 NUM_SHARDS = 16;
static constexpr i64 MAX_RETRY = 128;
Entry *entries = nullptr;
i64 nbuckets = 0;
u32 *key_sizes = nullptr;
T *values = nullptr;
private:
static void pause() {
@@ -554,10 +710,6 @@ private:
asm volatile("yield");
#endif
}
private:
std::atomic<const char *> *keys = nullptr;
static constexpr const char *marker = "marker";
};
//
@@ -574,9 +726,11 @@ public:
virtual ~OutputFile() = default;
u8 *buf = nullptr;
std::vector<u8> buf2;
std::string path;
i64 filesize;
bool is_mmapped;
i64 fd = -1;
i64 filesize = 0;
bool is_mmapped = false;
bool is_unmapped = false;
protected:
@@ -584,6 +738,43 @@ protected:
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
};
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
public:
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), perm(perm) {
this->buf = (u8 *)malloc(filesize);
if (!this->buf)
Fatal(ctx) << "malloc failed";
}
~MallocOutputFile() {
free(this->buf);
}
void close(Context &ctx) override {
Timer t(ctx, "close_file");
FILE *fp;
if (this->path == "-") {
fp = stdout;
} else {
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
fp = fdopen(fd, "w");
}
fwrite(this->buf, this->filesize, 1, fp);
if (!this->buf2.empty())
fwrite(this->buf2.data(), this->buf2.size(), 1, fp);
fclose(fp);
}
private:
i64 perm;
};
//
// hyperloglog.cc
//
@@ -719,85 +910,6 @@ private:
std::vector<std::vector<u8>> shards;
};
//
// perf.cc
//
// Counter is used to collect statistics numbers.
class Counter {
public:
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
static std::mutex mu;
std::scoped_lock lock(mu);
instances.push_back(this);
}
Counter &operator++(int) {
if (enabled) [[unlikely]]
values.local()++;
return *this;
}
Counter &operator+=(int delta) {
if (enabled) [[unlikely]]
values.local() += delta;
return *this;
}
static void print();
static inline bool enabled = false;
private:
i64 get_value();
std::string_view name;
tbb::enumerable_thread_specific<i64> values;
static inline std::vector<Counter *> instances;
};
// Timer and TimeRecord records elapsed time (wall clock time)
// used by each pass of the linker.
struct TimerRecord {
TimerRecord(std::string name, TimerRecord *parent = nullptr);
void stop();
std::string name;
TimerRecord *parent;
tbb::concurrent_vector<TimerRecord *> children;
i64 start;
i64 end;
i64 user;
i64 sys;
bool stopped = false;
};
void
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
template <typename Context>
class Timer {
public:
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
record = new TimerRecord(name, parent ? parent->record : nullptr);
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
}
Timer(const Timer &) = delete;
~Timer() {
record->stop();
}
void stop() {
record->stop();
}
private:
TimerRecord *record;
};
//
// tar.cc
//
@@ -885,9 +997,9 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
i64 fd;
#ifdef _WIN32
fd = ::_open(path.c_str(), O_RDONLY);
fd = ::_open(path.c_str(), O_RDONLY);
#else
fd = ::open(path.c_str(), O_RDONLY);
fd = ::open(path.c_str(), O_RDONLY);
#endif
if (fd == -1) {
@@ -923,7 +1035,7 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
if (mf->data == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
#endif
}
}
close(fd);
return mf;
+12 -37
View File
@@ -52,14 +52,13 @@ class MemoryMappedOutputFile : public OutputFile<Context> {
public:
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, true) {
i64 fd;
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
std::tie(this->fd, output_tmpfile) =
open_or_create_file(ctx, path, filesize, perm);
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
::close(fd);
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + filesize;
@@ -76,6 +75,15 @@ public:
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
if (this->buf2.empty()) {
::close(this->fd);
} else {
FILE *out = fdopen(this->fd, "w");
fseek(out, 0, SEEK_END);
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
fclose(out);
}
// If an output file already exists, open a file and then remove it.
// This is the fastest way to unlink a file, as it does not make the
// system to immediately release disk blocks occupied by the file.
@@ -92,39 +100,6 @@ private:
int fd2 = -1;
};
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
public:
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), perm(perm) {
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << "mmap failed: " << errno_string();
}
void close(Context &ctx) override {
Timer t(ctx, "close_file");
if (this->path == "-") {
fwrite(this->buf, this->filesize, 1, stdout);
fclose(stdout);
return;
}
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
FILE *fp = fdopen(fd, "w");
fwrite(this->buf, this->filesize, 1, fp);
fclose(fp);
}
private:
i64 perm;
};
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
-33
View File
@@ -6,39 +6,6 @@
namespace mold {
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
public:
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), perm(perm) {
this->buf = (u8 *)malloc(filesize);
if (!this->buf)
Fatal(ctx) << "malloc failed";
}
void close(Context &ctx) override {
Timer t(ctx, "close_file");
if (this->path == "-") {
fwrite(this->buf, this->filesize, 1, stdout);
fclose(stdout);
return;
}
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
FILE *fp = fdopen(fd, "w");
fwrite(this->buf, this->filesize, 1, fp);
fclose(fp);
free(this->buf);
}
private:
i64 perm;
};
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
-81
View File
@@ -1,81 +0,0 @@
#pragma once
#include <cstdint>
typedef uint8_t u8;
static constexpr int64_t SHA256_SIZE = 32;
#ifdef _WIN32
// On Windows, we use Microsoft CNG.
#include <Windows.h>
#include <bcrypt.h>
#include <ntstatus.h>
inline static BCRYPT_ALG_HANDLE get_sha256_handle() {
static std::once_flag once;
static BCRYPT_ALG_HANDLE alg;
std::call_once(once, [&] {
BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
});
return alg;
}
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
BCryptHash(get_sha256_handle(), nullptr, 0, in, len, out, SHA256_SIZE);
}
class SHA256Hash {
public:
SHA256Hash() {
BCryptCreateHash(get_sha256_handle(), &handle, nullptr, 0, nullptr, 0, 0);
}
void update(u8 *data, size_t len) {
BCryptHashData(handle, data, len, 0);
}
void finish(u8 *out) {
BCryptFinishHash(handle, out, SHA256_SIZE, 0);
}
private:
BCRYPT_HASH_HANDLE handle;
};
#else
// On Unix, we use OpenSSL or the Apple's OpenSSL-compatible API.
#ifdef __APPLE__
# define COMMON_DIGEST_FOR_OPENSSL
# include <CommonCrypto/CommonDigest.h>
# define SHA256(data, len, md) CC_SHA256(data, len, md)
#else
# define OPENSSL_SUPPRESS_DEPRECATED 1
# include <openssl/sha.h>
#endif
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
SHA256(in, len, out);
}
class SHA256Hash {
public:
SHA256Hash() {
SHA256_Init(&ctx);
}
void update(u8 *data, size_t len) {
SHA256_Update(&ctx, data, len);
}
void finish(u8 *out) {
SHA256_Final(out, &ctx);
}
private:
SHA256_CTX ctx;
};
#endif
+48 -10
View File
@@ -1,4 +1,5 @@
#!/bin/bash
#
# This script creates a mold binary distribution. The output is
# written in this directory as `mold-$version-$arch-linux.tar.gz`
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
@@ -7,6 +8,8 @@
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
# and librt, as they almost always exist on any Linux systems.
set -e -x
case $# in
0)
arch=$(uname -m)
@@ -16,25 +19,60 @@ case $# in
arch="$1"
;;
*)
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
exit 1
esac
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || \
{ echo "Error: no docker image for $arch"; exit 1; }
image=mold-builder-$arch:1
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
dest=mold-$version-$arch-linux
set -e -x
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" \
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
bash -c "mkdir /tmp/build &&
cd /tmp/build &&
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
# Create a Docker image if not exists.
#
# We want to use a reasonably old Linux distro 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 a older version of glibc.
#
# We might want to instead statically-link everything, but that'll
# disable dlopen(), which means we can't use the linker plugin for LTO.
# So we don't want to do that.
if ! docker image ls $image | grep -q $image; then
if [ $arch = riscv64 ]; then
# Ubuntu 18 didn't support RISC-V, so use Ubuntu 20 instead
cat <<EOF | docker build --platform $arch -t $image -
FROM riscv64/ubuntu:20.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
rm -rf /var/lib/apt/lists
EOF
else
cat <<EOF | docker build --platform $arch -t $image -
FROM ubuntu:18.04
RUN apt-get update && \
apt-get install -y --no-install-recommends software-properties-common && \
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev gcc-10 g++-10 python3 libssl-dev && \
\
mkdir /cmake && cd /cmake && \
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
rm -rf /var/lib/apt/lists /cmake
EOF
fi
fi
# Build mold in a container.
docker run --platform linux/$arch -i --rm -v "$(realpath $(dirname $0)):/mold" \
-e "OWNER=$(id -u):$(id -g)" $image \
bash -c "mkdir /build &&
cd /build &&
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On /mold &&
cmake --build . -j\$(nproc) &&
[ $arch = arm ] || ctest -j\$(nproc) &&
cmake --install . --prefix $dest --strip &&
tar czf /mold/$dest.tar.gz $dest &&
cp mold mold-wrapper.so /mold &&
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
chown \$OWNER /mold/$dest.tar.gz"
+15 -13
View File
@@ -764,22 +764,24 @@ arguments.
## ENVIRONMENT VARIABLES
* `MOLD_JOBS`:
If this variable is set to `1`, only one process of `mold` runs actively. A
mold process invoked while another active mold process is running will wait
before doing anything until the active process exits.
If this variable is set to `1`, only one `mold` process will run at a time.
If a new mold process is initiated while another is already active, the new
process will wait until the active one completes before starting.
The purpose of this environment variable is to reduce peak memory usage.
Since mold is highly parallelized, there's no point in running it
simultaneously. If you run N instances of mold in parallel, it would take N
times more time and N times more memory. If you run them serially, it would
still take N times more to finish, but their peak memory usage is reduced to
normal.
The primary reason for this environment variable is to minimize peak memory
usage. Since mold is designed to operate with high parallelism, running
multiple mold instances simultaneously may not be beneficial. If you execute
N instances of mold concurrently, it could require N times the time and N
times the memory. On the other hand, running them one after the other might
still take N times longer, but the peak memory usage would be the same as
running just a single instance.
If your build system tends to invoke multiple linker processes
simultaneously, you may want to try to set this environment variable to
`1` to see if it could improve overall performance.
If your build system invokes multiple linker processes simultaneously and
some of them often get killed due to out-of-memory errors, you might
consider setting this environment variable to `1` to see if it addresses the
OOM issue.
Currently, any value other than 1 is silently ignored.
Currently, any value other than `1` is silently ignored.
* `MOLD_DEBUG`:
If this variable is set to a non-empty string, `mold` embeds its
+1 -1
View File
@@ -318,7 +318,7 @@ void AlphaGotSection::copy_buf(Context<E> &ctx) {
if (e.sym->is_imported) {
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, e.sym->get_dynsym_idx(ctx), e.addend);
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
} else {
*buf = e.sym->get_addr(ctx) + e.addend;
if (ctx.arg.pic && !e.sym->is_absolute())
+67 -36
View File
@@ -195,14 +195,14 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe08f'e00e, // 1: add lr, pc, lr
0xe5be'f008, // ldr pc, [lr, #8]!
0xe5be'f008, // ldr pc, [lr, #8]!
0x0000'0000, // 2: .word .got.plt - 1b - 8
0xe320'f000, // nop
0xe320'f000, // nop
0xe320'f000, // nop
0x0000'0000, // (padding)
0x0000'0000, // (padding)
0x0000'0000, // (padding)
};
memcpy(buf, insn, sizeof(insn));
@@ -211,8 +211,8 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 plt_entry[] = {
0xe59f'c004, // 1: ldr ip, 2f
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0x0000'0000, // 2: .word sym@GOT - 1b
};
@@ -225,7 +225,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 12;
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
// ARM does not use .eh_frame for exception handling. Instead, it uses
@@ -336,12 +336,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
// Just like THM_CALL, ARM_CALL relocation refers either BL or
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
u64 val = S + A - P;
if (is_jump_reachable(val)) {
@@ -364,7 +364,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
// These relocs refers a B (unconditional branch) instruction.
// These relocs refers to a B (unconditional branch) instruction.
// Unlike BL or BLX, we can't rewrite B to BX in place when the
// processor mode switch is required because BX doesn't takes an
// immediate; it takes only a register. So if mode switch is
@@ -467,12 +467,33 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
// ARM32 TLSDESC uses the following code sequence to materialize
// a TP-relative address in r0.
//
// ldr r0, .L2
// .L1: bl foo
// R_ARM_TLS_CALL
// .L2: .word foo + . - .L1
// R_ARM_TLS_GOTDESC
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// ldr r0, .L2
// .L1: ldr r0, [pc, r0]
// ...
// .L2: .word foo(gottpoff) + . - .L1
//
// or to the following for executable.
//
// ldr r0, .L2
// .L1: nop
// ...
// .L2: .word foo(tpoff)
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
if (A & 1)
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
else
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
} else {
*(ul32 *)loc = S - ctx.tp_addr;
}
@@ -481,9 +502,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (sym.has_tlsdesc(ctx)) {
// BL <tls_trampoline>
*(ul32 *)loc = 0xeb00'0000 | bits(get_tls_trampoline_addr(P + 8), 25, 2);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
// BL -> NOP
*(ul32 *)loc = 0xe320'f000;
*(ul32 *)loc = 0xe320'f000; // nop
}
break;
case R_ARM_THM_TLS_CALL:
@@ -491,9 +513,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 val = align_to(get_tls_trampoline_addr(P + 4), 4);
write_thm_b_imm(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else if (sym.has_gottp(ctx)) {
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
// we use two instructions instead.
*(ul16 *)loc = 0x4478; // add r0, pc
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
} else {
// BL -> NOP.W
*(ul32 *)loc = 0x8000'f3af;
*(ul32 *)loc = 0x8000'f3af; // nop.w
}
break;
default:
@@ -594,9 +620,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_ARM_TLS_IE32:
sym.flags |= NEEDS_GOTTP;
break;
case R_ARM_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
case R_ARM_TLS_CALL:
case R_ARM_THM_TLS_CALL:
scan_tlsdesc(ctx, sym);
break;
case R_ARM_TLS_LE32:
check_tlsle(ctx, sym, rel);
@@ -611,9 +637,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_CALL:
case R_ARM_THM_TLS_CALL:
case R_ARM_V4BX:
case R_ARM_TLS_GOTDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -623,8 +648,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// TLS trampoline code. ARM32's TLSDESC is designed so that this
// common piece of code is factored out from object files to reduce
// output size. Since no one provide, the linker has to synthesize it.
@@ -632,6 +655,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
0xe08e'0000, // add r0, lr, r0
0xe590'1004, // ldr r1, [r0, #4]
0xe12f'ff11, // bx r1
0xe320'f000, // nop
};
// This is a range extension and mode switch thunk.
@@ -641,27 +665,34 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
0x78, 0x47, // bx pc # jumps to 1f
0xc0, 0x46, // nop
// .arm
0x04, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
0x0f, 0xc0, 0x8c, 0xe0, // 2: add ip, ip, pc
0x1c, 0xff, 0x2f, 0xe1, // bx ip
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
};
static_assert(E::thunk_hdr_size == sizeof(hdr));
static_assert(E::thunk_size == sizeof(entry));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
memcpy(buf, hdr, sizeof(hdr));
buf += sizeof(hdr);
for (i64 i = 0; i < symbols.size(); i++) {
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
memcpy(loc, entry, sizeof(entry));
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
u64 S = symbols[i]->get_addr(ctx);
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr) + i * sizeof(entry);
*(ul32 *)(loc + 16) = S - P - 16;
for (Symbol<E> *sym : symbols) {
memcpy(buf, entry, sizeof(entry));
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
buf += sizeof(entry);
P += sizeof(entry);
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return EF_ARM_EABI_VER5;
}
// ARM executables use an .ARM.exidx section to look up an exception
// handling record for the current instruction pointer. The table needs
// to be sorted by their addresses.
+65 -35
View File
@@ -93,7 +93,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xd503'201f, // nop
};
u64 got = sym.get_got_addr(ctx);
u64 got = sym.get_got_pltgot_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, insn, sizeof(insn));
@@ -238,10 +238,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// :lo12: foo` instruction pair to materialize a PC-relative address
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
// if foo is in PC ± 1 MiB.
if (ctx.arg.relax && i + 1 < rels.size() &&
sign_extend(S + A - P - 4, 20) == S + A - P - 4) {
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
i + 1 < rels.size()) {
i64 val = S + A - P - 4;
const ElfRel<E> &rel2 = rels[i + 1];
if (rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
if (sign_extend(val, 20) == val &&
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
rel2.r_sym == rel.r_sym &&
rel2.r_offset == rel.r_offset + 4 &&
rel2.r_addend == rel.r_addend &&
@@ -252,7 +254,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (reg1 == reg2) {
*(ul32 *)loc = 0xd503'201f; // nop
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
write_adr(loc + 4, S + A - P - 4);
write_adr(loc + 4, val);
i++;
break;
}
@@ -369,38 +371,65 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
// ARM64 TLSDESC uses the following code sequence to materialize
// a TP-relative address in x0.
//
// adrp x0, 0
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
// ldr x1, [x0]
// R_AARCH64_TLSDESC_LD64_LO12 foo
// add x0, x0, #0
// R_AARCH64_TLSDESC_ADD_LO12 foo
// blr x1
// R_AARCH64_TLSDESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// nop
// nop
// adrp x0, :gottprel:foo
// ldr x0, [x0, :gottprel_lo12:foo]
//
// or to the following for executable.
//
// nop
// nop
// movz x0, :tls_offset_hi:foo, lsl #16
// movk x0, :tls_offset_lo:foo
if (sym.has_tlsdesc(ctx)) {
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
i64 val = (S + A - ctx.tp_addr);
check(val, -(1LL << 32), 1LL << 32);
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
*(ul32 *)loc = 0xd503'201f; // nop
}
break;
case R_AARCH64_TLSDESC_LD64_LO12:
if (sym.has_tlsdesc(ctx)) {
if (sym.has_tlsdesc(ctx))
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
} else {
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
}
else
*(ul32 *)loc = 0xd503'201f; // nop
break;
case R_AARCH64_TLSDESC_ADD_LO12:
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
} else {
// add x0, x0, #0 -> nop
*(ul32 *)loc = 0xd503'201f;
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
}
break;
case R_AARCH64_TLSDESC_CALL:
if (!sym.has_tlsdesc(ctx)) {
// blr x2 -> nop
*(ul32 *)loc = 0xd503'201f;
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
} else {
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
}
break;
default:
@@ -487,8 +516,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// symbol's address from GOT. If the GOT value is a link-time
// constant, we may be able to rewrite the ADRP+LDR instruction pair
// with an ADRP+ADD, eliminating a GOT memory load.
if (ctx.arg.relax && sym.is_relative() && !sym.is_imported &&
!sym.is_ifunc() && i + 1 < rels.size()) {
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
i + 1 < rels.size()) {
// ADRP+LDR must be consecutive and use the same register to relax.
const ElfRel<E> &rel2 = rels[i + 1];
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
@@ -531,8 +560,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_TLSGD;
break;
case R_AARCH64_TLSDESC_CALL:
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
scan_tlsdesc(ctx, sym);
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
@@ -575,24 +603,26 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const ul32 data[] = {
static const ul32 insn[] = {
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
0xd61f'0200, // br x16
0xd503'201f, // nop
};
static_assert(E::thunk_size == sizeof(data));
static_assert(E::thunk_size == sizeof(insn));
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *loc = buf + i * E::thunk_size;
memcpy(loc, data, sizeof(data));
write_adrp(loc, page(S) - page(P));
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
memcpy(buf, insn, E::thunk_size);
write_adrp(buf, page(S) - page(P));
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
buf += E::thunk_size;
P += E::thunk_size;
}
}
+51 -43
View File
@@ -164,7 +164,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
@@ -172,7 +172,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx);
}
}
@@ -338,20 +338,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
break;
case R_386_TLS_GD:
if (sym.has_tlsgd(ctx)) {
if (sym.has_tlsgd(ctx))
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
} else {
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
i++;
}
else
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
break;
case R_386_TLS_LDM:
if (ctx.got->has_tlsld(ctx)) {
if (ctx.got->has_tlsld(ctx))
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
} else {
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
i++;
}
else
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
break;
case R_386_TLS_LDO_32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
@@ -360,13 +356,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = sym.esym().st_size + A;
break;
case R_386_TLS_GOTDESC:
// i386 TLSDESC uses the following code sequence to materialize
// a TP-relative address in %eax.
//
// lea 0(%ebx), %eax
// R_386_TLS_GOTDESC foo
// call *(%eax)
// R_386_TLS_DESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// mov foo@GOTTPOFF(%ebx), %eax
// nop
//
// or to the following for executable.
//
// mov $foo@TPOFF, %eax
// nop
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
} else if (sym.has_gottp(ctx)) {
loc[-2] = 0x8b;
loc[-1] = 0x83;
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
} else {
static const u8 insn[] = {
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
loc[-2] = 0x90;
loc[-1] = 0xb8;
*(ul32 *)loc = S + A - ctx.tp_addr;
}
break;
@@ -476,6 +491,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
}
switch (rel.r_type) {
case R_386_8:
case R_386_16:
@@ -493,15 +518,15 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_386_GOTPC:
sym.flags |= NEEDS_GOT;
break;
case R_386_GOT32X: {
// We always want to relax GOT32X because static PIE doesn't
// work without it.
bool do_relax = !sym.is_imported && sym.is_relative() &&
relax_got32x(loc - 2);
if (!do_relax)
case R_386_GOT32X:
// We always want to relax GOT32X even if --no-relax is given
// because static PIE doesn't work without it.
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
// Do nothing
} else {
sym.flags |= NEEDS_GOT;
}
break;
}
case R_386_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
@@ -511,31 +536,15 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOTTP;
break;
case R_386_TLS_GD:
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static ||
(ctx.arg.relax && !ctx.arg.shared && !sym.is_imported))
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static)
i++;
else
sym.flags |= NEEDS_TLSGD;
break;
case R_386_TLS_LDM:
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
@@ -544,8 +553,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
ctx.needs_tlsld = true;
break;
case R_386_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
scan_tlsdesc(ctx, sym);
break;
case R_386_TLS_LE:
check_tlsle(ctx, sym, rel);
+86 -82
View File
@@ -47,36 +47,38 @@ static u64 hi20(u64 val, u64 pc) {
//
// This is similar but different from RISC-V because RISC-V's AUIPC
// doesn't zero-clear [11:0].
return page(val + 0x800) - page(pc);
return bits(page(val + 0x800) - page(pc), 31, 12);
}
static u64 hi64(u64 val, u64 pc) {
// A PC-relative 64-bit address is materialized with the following
// instructions for the large code model:
//
// pcalau12i $rX, %pc_hi20(sym)
// addi.d $rY, $zero, %lo12(sym)
// lu32i.d $rY, %pc64_lo20(sym)
// lu52i.d $rY, $r12, %pc64_hi12(sym)
// add.d $rX, $rX, $rY
// pcalau12i $rN, %pc_hi20(sym)
// addi.d $rM, $zero, %lo12(sym)
// lu32i.d $rM, %pc64_lo20(sym)
// lu52i.d $rM, $r12, %pc64_hi12(sym)
// add.d $rN, $rN, $rM
//
// PCALAU12I computes (pc + imm << 12) to materialize a 64-bit value.
// ADDI.D adds a sign-extended 12 bit value to a register. LU32I.D and
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
//
// Compensating all the sign-extensions is a bit complicated.
u64 x = hi20(val, pc);
if ((val & 0x800) && !(x & 0x8000'0000))
return x - 0x1'0000'0000;
if (!(val & 0x800) && (x & 0x8000'0000))
return x + 0x1'0000'0000;
u64 x = page(val) - page(pc);
if (val & 0x800)
x += 0x1000 - 0x1'0000'0000;
if (x & 0x8000'0000)
x += 0x1'0000'0000;
return x;
}
static void write_j20(u8 *loc, u32 val) {
// opcode, [19:0], rd
*(ul32 *)loc &= 0b1111111'00000000000000000000'11111;
*(ul32 *)loc |= bits(val, 19, 0) << 5;
static u64 higher20(u64 val, u64 pc) {
return bits(hi64(val, pc), 51, 32);
}
static u64 highest12(u64 val, u64 pc) {
return bits(hi64(val, pc), 63, 52);
}
static void write_k12(u8 *loc, u32 val) {
@@ -85,6 +87,18 @@ static void write_k12(u8 *loc, u32 val) {
*(ul32 *)loc |= bits(val, 11, 0) << 10;
}
static void write_k16(u8 *loc, u32 val) {
// opcode, [15:0], rj, rd
*(ul32 *)loc &= 0b111111'0000000000000000'11111'11111;
*(ul32 *)loc |= bits(val, 15, 0) << 10;
}
static void write_j20(u8 *loc, u32 val) {
// opcode, [19:0], rd
*(ul32 *)loc &= 0b1111111'00000000000000000000'11111;
*(ul32 *)loc |= bits(val, 19, 0) << 5;
}
static void write_d5k16(u8 *loc, u32 val) {
// opcode, [15:0], rj, [20:16]
*(ul32 *)loc &= 0b111111'0000000000000000'11111'00000;
@@ -99,12 +113,6 @@ static void write_d10k16(u8 *loc, u32 val) {
*(ul32 *)loc |= bits(val, 25, 16);
}
static void write_k16(u8 *loc, u32 val) {
// opcode, [15:0], rj, rd
*(ul32 *)loc &= 0b111111'0000000000000000'11111'11111;
*(ul32 *)loc |= bits(val, 15, 0) << 10;
}
template <>
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
static const ul32 insn_64[] = {
@@ -133,7 +141,7 @@ void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
u64 plt = ctx.plt->shdr.sh_addr;
memcpy(buf, E::is_64 ? insn_64 : insn_32, E::plt_hdr_size);
write_j20(buf, hi20(gotplt, plt) >> 12);
write_j20(buf, hi20(gotplt, plt));
write_k12(buf + 8, gotplt);
write_k12(buf + 16, gotplt);
}
@@ -158,17 +166,17 @@ void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
write_j20(buf, hi20(gotplt, plt) >> 12);
write_j20(buf, hi20(gotplt, plt));
write_k12(buf + 4, gotplt);
}
template <>
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u64 got = sym.get_got_addr(ctx);
u64 got = sym.get_got_pltgot_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
write_j20(buf, hi20(got, plt) >> 12);
write_j20(buf, hi20(got, plt));
write_k12(buf + 4, got);
}
@@ -264,7 +272,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// as if they were TLSGD relocs for LoongArch, which is a clear bug.
// We need to handle TLSLD relocs as synonyms for TLSGD relocs for the
// sake of bug compatibility.
auto get_tls_idx = [&] {
auto get_got_idx = [&] {
if (sym.has_tlsgd(ctx))
return sym.get_tlsgd_idx(ctx);
return sym.get_got_idx(ctx);
@@ -273,7 +281,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = get_tls_idx() * sizeof(Word<E>);
u64 G = get_got_idx() * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
@@ -302,93 +310,91 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_d10k16(loc, val >> 2);
break;
}
case R_LARCH_ABS_HI20:
write_j20(loc, (S + A) >> 12);
break;
case R_LARCH_ABS_LO12:
write_k12(loc, S + A);
break;
case R_LARCH_ABS_HI20:
write_j20(loc, (S + A) >> 12);
break;
case R_LARCH_ABS64_LO20:
write_j20(loc, (S + A) >> 32);
break;
case R_LARCH_ABS64_HI12:
write_k12(loc, (S + A) >> 52);
break;
case R_LARCH_PCALA_HI20: {
i64 val = hi20(S + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
break;
}
case R_LARCH_PCALA_LO12:
write_k12(loc, S + A);
// It looks like R_LARCH_PCALA_LO12 is sometimes used for JIRL even
// though the instruction takes a 16 bit immediate rather than 12 bits.
// It is contrary to the psABI document, but GNU ld has special
// code to handle it, so we accept it too.
if ((*(ul32 *)loc & 0xfc00'0000) == 0x4c00'0000)
write_k16(loc, sign_extend(S + A, 11) >> 2);
else
write_k12(loc, S + A);
break;
case R_LARCH_PCALA_HI20:
write_j20(loc, hi20(S + A, P));
break;
case R_LARCH_PCALA64_LO20:
write_j20(loc, hi64(S + A, P) >> 32);
write_j20(loc, higher20(S + A, P));
break;
case R_LARCH_PCALA64_HI12:
write_k12(loc, hi64(S + A, P) >> 52);
write_k12(loc, highest12(S + A, P));
break;
case R_LARCH_GOT_PC_HI20: {
i64 val = hi20(GOT + G + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
break;
}
case R_LARCH_GOT_PC_LO12:
write_k12(loc, GOT + G + A);
break;
case R_LARCH_GOT_PC_HI20:
write_j20(loc, hi20(GOT + G + A, P));
break;
case R_LARCH_GOT64_PC_LO20:
write_j20(loc, hi64(GOT + G + A, P) >> 32);
write_j20(loc, higher20(GOT + G + A, P));
break;
case R_LARCH_GOT64_PC_HI12:
write_k12(loc, hi64(GOT + G + A, P) >> 52);
break;
case R_LARCH_GOT_HI20:
write_j20(loc, (GOT + G + A) >> 12);
write_k12(loc, highest12(GOT + G + A, P));
break;
case R_LARCH_GOT_LO12:
write_k12(loc, GOT + G + A);
break;
case R_LARCH_GOT_HI20:
write_j20(loc, (GOT + G + A) >> 12);
break;
case R_LARCH_GOT64_LO20:
write_j20(loc, (GOT + G + A) >> 32);
break;
case R_LARCH_GOT64_HI12:
write_k12(loc, (GOT + G + A) >> 52);
break;
case R_LARCH_TLS_LE_HI20:
write_j20(loc, (S + A - ctx.tp_addr) >> 12);
break;
case R_LARCH_TLS_LE_LO12:
write_k12(loc, S + A - ctx.tp_addr);
break;
case R_LARCH_TLS_LE_HI20:
write_j20(loc, (S + A - ctx.tp_addr) >> 12);
break;
case R_LARCH_TLS_LE64_LO20:
write_j20(loc, (S + A - ctx.tp_addr) >> 32);
break;
case R_LARCH_TLS_LE64_HI12:
write_k12(loc, (S + A - ctx.tp_addr) >> 52);
break;
case R_LARCH_TLS_IE_PC_HI20: {
i64 val = hi20(sym.get_gottp_addr(ctx) + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
break;
}
case R_LARCH_TLS_IE_PC_LO12:
write_k12(loc, sym.get_gottp_addr(ctx) + A);
break;
case R_LARCH_TLS_IE_PC_HI20:
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
break;
case R_LARCH_TLS_IE64_PC_LO20:
write_j20(loc, hi64(sym.get_gottp_addr(ctx) + A, P) >> 32);
write_j20(loc, higher20(sym.get_gottp_addr(ctx) + A, P));
break;
case R_LARCH_TLS_IE64_PC_HI12:
write_k12(loc, hi64(sym.get_gottp_addr(ctx) + A, P) >> 52);
break;
case R_LARCH_TLS_IE_HI20:
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 12);
write_k12(loc, highest12(sym.get_gottp_addr(ctx) + A, P));
break;
case R_LARCH_TLS_IE_LO12:
write_k12(loc, sym.get_gottp_addr(ctx) + A);
break;
case R_LARCH_TLS_IE_HI20:
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 12);
break;
case R_LARCH_TLS_IE64_LO20:
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 32);
break;
@@ -396,12 +402,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_k12(loc, (sym.get_gottp_addr(ctx) + A) >> 52);
break;
case R_LARCH_TLS_LD_PC_HI20:
case R_LARCH_TLS_GD_PC_HI20: {
i64 val = hi20(sym.get_tlsgd_addr(ctx) + A, P);
check(val, -(1LL << 31), 1LL << 31);
write_j20(loc, val >> 12);
case R_LARCH_TLS_GD_PC_HI20:
check(sym.get_tlsgd_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
write_j20(loc, hi20(sym.get_tlsgd_addr(ctx) + A, P));
break;
}
case R_LARCH_TLS_LD_HI20:
case R_LARCH_TLS_GD_HI20:
write_j20(loc, (sym.get_tlsgd_addr(ctx) + A) >> 12);
@@ -585,6 +589,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_dyn_absrel(ctx, sym, rel);
break;
case R_LARCH_B26:
case R_LARCH_PCALA_HI20:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
@@ -618,7 +623,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_LARCH_ABS_LO12:
case R_LARCH_ABS64_LO20:
case R_LARCH_ABS64_HI12:
case R_LARCH_PCALA_HI20:
case R_LARCH_PCALA_LO12:
case R_LARCH_PCALA64_LO20:
case R_LARCH_PCALA64_HI12:
@@ -655,25 +659,25 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const ul32 insn[] = {
0x1a00'000c, // pcalau12i $t0, 0
0x02c0'018c, // addi.d $t0, $t0, 0
0x1e00'000c, // pcaddu18i $t0, 0
0x4c00'0180, // jirl $zero, $t0, 0
0x0340'0000, // nop
};
static_assert(E::thunk_size == sizeof(insn));
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *loc = buf + i * E::thunk_size;
memcpy(loc, insn, sizeof(insn));
write_j20(loc, hi20(S, P) >> 12);
write_k12(loc + 4, S);
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
memcpy(buf, insn, sizeof(insn));
write_j20(buf, (S - P + 0x20000) >> 18);
write_k16(buf + 4, (S - P) >> 2);
buf += sizeof(insn);
P += sizeof(insn);
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 4) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 2;
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
}
template <>
-673
View File
@@ -1,673 +0,0 @@
// MIPS is a RISC ISA developed in the '80s. The processor was once fairly
// popular; for examples, Silicon Graphics workstations and Nintendo 64
// game consoles are based on the processor. Even though it's no longer a
// popular choice when creating a new system, there are still many uses of
// the ISA especially in the network router segment.
//
// The MIPS psABIs are in a sad state due to the lack of ownership of the
// ABI. The last major Unix vendor in the MIPS market was Silicon
// Graphics, which effectively ceased its MIPS-based Unix workstation
// business in the '90s. Even at the time the MIPS ABIs looked peculiar.
// After that, various small vendors used MIPS to create appliances and
// notably routers, but no one tried to modernize or improve the ABIs. As
// a result, the MIPS ABIs left as probably the most diverged ABI compared
// to the other psABIs.
//
// Specifically, the MIPS ABIs has the following issues:
//
// 1. Since the ISA does not support PC-relative addressing, each function
// first materializes the address of GOT + 0x7ff0 in the GP register
// and access GOT entries relative to the GP's value. This GP-relative
// access is usually done with a single load instruction with a 16-bit
// offset. That means only GP ± 32 KiB is addressable. If GOT is larger
// than that, the linker is expected to create a GOT section for each
// input file and associate a different GP value for each GOT. This
// method is called "multi-GOT". Multi-GOT is not necessary for other
// ABIs because other processors either simply support PC-relative
// addressing or use two instructions to access GOT entries.
//
// 2. The MIPS ABIs require .dynsym entries to be sorted in a very
// specific manner to represent some dynamic relocations implicitly
// rather than explicitly in the .rela.dyn section. This feature is
// called "Quickstart" in the MIPS documentation.
//
// 3. Unlike other psABIs, a MIPS relocation record can have up to three
// types -- that is, each record has not only r_type but also r_type2
// and r_type3. A relocated value is computed as the combination of all
// the relocation types.
//
// In our MIPS support, we prioritize simplicity of implementation over
// marginal runtime efficiency. Specifically, we made the following
// decisions for simplification:
//
// 1. We do not sort .dynsym entries. Quickstart still kicks in at the
// load-time (there's no way to tell the loader to disable Quickstart),
// and the loader writes resolved addresses to our placeholder section
// `.mips_quickstart`. We just ignore these relocated values.
//
// 2. Instead of supporting arbitrary combinations of relocation types, we
// support only a limited set of them. This works because, in practice,
// the compiler emits only a limted set of relocation types.
#include "mold.h"
namespace mold::elf {
static constexpr i64 BIAS = 0x8000;
// We don't support lazy symbol resolution for MIPS. All dynamic symbols
// are resolved eagerly on process startup.
template <typename E>
void write_plt_header(Context<E> &ctx, u8 *buf) {}
template <typename E>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <typename E>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <typename E>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_MIPS_64:
// We relocate R_MIPS_64 in .eh_frame as a relative relocation.
// See the comment for mips_rewrite_cie() below.
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <typename E>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
// 0x7ff0 is added to maximize the GP-relative addressable range
// for load/store instructions with a signed 16-bit displacement.
u64 GP = file.extra.got->shdr.sh_addr + 0x7ff0;
u64 GP0 = file.extra.gp0;
MipsGotSection<E> *got = file.extra.got;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto write_hi16 = [&](u64 val) {
check(val, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc |= ((val + BIAS) >> 16) & 0xffff;
};
auto write_lo16 = [&](u64 val) {
check(val, -(1 << 15), 1 << 15);
*(U32<E> *)loc |= val & 0xffff;
};
auto write_lo16_nc = [&](u64 val) {
*(U32<E> *)loc |= val & 0xffff;
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
switch (rel.r_type) {
case R_MIPS_64:
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_HI16 << 16): {
u64 val = sym.is_local(ctx) ? (S + A + GP0 - GP) : (S + A - GP);
write_hi16(-val);
break;
}
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_LO16 << 16): {
u64 val = sym.is_local(ctx) ? (S + A + GP0 - GP) : (S + A - GP);
write_lo16_nc(-val);
break;
}
case R_MIPS_GPREL32 | (R_MIPS_64 << 8):
*(U64<E> *)loc = S + A + GP0 - GP;
break;
case R_MIPS_GOT_DISP:
case R_MIPS_CALL16:
case R_MIPS_CALL_LO16:
case R_MIPS_GOT_LO16:
write_lo16(got->get_got_addr(ctx, sym, A) - GP);
break;
case R_MIPS_CALL_HI16:
case R_MIPS_GOT_HI16:
write_hi16(got->get_got_addr(ctx, sym, A) - GP);
break;
case R_MIPS_GOT_PAGE:
write_lo16(got->get_gotpage_addr(ctx, sym, A) - GP);
break;
case R_MIPS_GOT_OFST:
write_lo16(0);
break;
case R_MIPS_JALR:
break;
case R_MIPS_TLS_TPREL_HI16:
write_hi16(S + A - ctx.tp_addr);
break;
case R_MIPS_TLS_TPREL_LO16:
write_lo16_nc(S + A - ctx.tp_addr);
break;
case R_MIPS_TLS_GOTTPREL:
write_lo16(got->get_gottp_addr(ctx, sym) - GP);
break;
case R_MIPS_TLS_DTPREL_HI16:
write_hi16(S + A - ctx.dtp_addr);
break;
case R_MIPS_TLS_DTPREL_LO16:
write_lo16_nc(S + A - ctx.dtp_addr);
break;
case R_MIPS_TLS_GD:
write_lo16(got->get_tlsgd_addr(ctx, sym) - GP);
break;
case R_MIPS_TLS_LDM:
write_lo16(got->get_tlsld_addr(ctx) - GP);
break;
default:
unreachable();
}
}
}
template <typename E>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : get_addend(loc, rel);
switch (rel.r_type) {
case R_MIPS_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U64<E> *)loc = *val;
else
*(U64<E> *)loc = S + A;
break;
case R_MIPS_32:
*(U32<E> *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <typename E>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
MipsGotSection<E> *got = file.extra.got;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
switch (rel.r_type) {
case R_MIPS_64:
scan_toc_rel(ctx, sym, rel);
break;
case R_MIPS_GOT_DISP:
case R_MIPS_CALL16:
case R_MIPS_CALL_HI16:
case R_MIPS_CALL_LO16:
case R_MIPS_GOT_HI16:
case R_MIPS_GOT_LO16:
got->got_syms.push_back({&sym, rel.r_addend});
break;
case R_MIPS_GOT_PAGE:
case R_MIPS_GOT_OFST:
got->gotpage_syms.push_back({&sym, rel.r_addend});
break;
case R_MIPS_TLS_GOTTPREL:
assert(rel.r_addend == 0);
got->gottp_syms.push_back(&sym);
break;
case R_MIPS_TLS_TPREL_HI16:
case R_MIPS_TLS_TPREL_LO16:
check_tlsle(ctx, sym, rel);
break;
case R_MIPS_TLS_GD:
assert(rel.r_addend == 0);
got->tlsgd_syms.push_back(&sym);
break;
case R_MIPS_TLS_LDM:
assert(rel.r_addend == 0);
got->has_tlsld = true;
break;
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_HI16 << 16):
case R_MIPS_GPREL16 | (R_MIPS_SUB << 8) | (R_MIPS_LO16 << 16):
case R_MIPS_GPREL32 | (R_MIPS_64 << 8):
case R_MIPS_JALR:
case R_MIPS_TLS_DTPREL_HI16:
case R_MIPS_TLS_DTPREL_LO16:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <typename E>
bool MipsGotSection<E>::SymbolAddend::operator<(const SymbolAddend &other) const {
return std::tuple(sym->file->priority, sym->sym_idx, addend) <
std::tuple(other.sym->file->priority, other.sym->sym_idx, other.addend);
};
template <typename E>
static bool compare(const Symbol<E> *a, const Symbol<E> *b) {
return std::tuple(a->file->priority, a->sym_idx) <
std::tuple(b->file->priority, b->sym_idx);
};
template <typename E>
u64 MipsGotSection<E>::SymbolAddend::get_addr(Context<E> &ctx, i64 flags) const {
return sym->get_addr(ctx, flags) + addend;
}
template <typename E>
static inline i64 get_gotpage_offset(const MipsGotSection<E> &got) {
return got.got_syms.size();
}
template <typename E>
static inline i64 get_tlsgd_offset(const MipsGotSection<E> &got) {
return get_gotpage_offset(got) + got.gotpage_syms.size();
}
template <typename E>
static inline i64 get_gottp_offset(const MipsGotSection<E> &got) {
return get_tlsgd_offset(got) + got.tlsgd_syms.size() * 2;
}
template <typename E>
static inline i64 get_tlsld_offset(const MipsGotSection<E> &got) {
return get_gottp_offset(got) + got.gottp_syms.size();
}
template <typename E>
static inline i64 get_num_got_entries(const MipsGotSection<E> &got) {
return get_tlsld_offset(got) + got.has_tlsld * 2;
}
template <typename E>
u64 MipsGotSection<E>::get_got_addr(Context<E> &ctx, Symbol<E> &sym,
i64 addend) const {
auto it = std::lower_bound(got_syms.begin(), got_syms.end(),
SymbolAddend{&sym, addend});
i64 idx = it - got_syms.begin();
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_gotpage_addr(Context<E> &ctx, Symbol<E> &sym,
i64 addend) const {
auto it = std::lower_bound(gotpage_syms.begin(), gotpage_syms.end(),
SymbolAddend{&sym, addend});
i64 idx = get_gotpage_offset(*this) + (it - gotpage_syms.begin());
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const {
auto it = std::lower_bound(tlsgd_syms.begin(), tlsgd_syms.end(),
&sym, compare<E>);
i64 idx = get_tlsgd_offset(*this) + (it - tlsgd_syms.begin()) * 2;
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const {
auto it = std::lower_bound(gottp_syms.begin(), gottp_syms.end(),
&sym, compare<E>);
i64 idx = get_gottp_offset(*this) + (it - gottp_syms.begin());
return this->shdr.sh_addr + idx * sizeof(Word<E>);
}
template <typename E>
u64 MipsGotSection<E>::get_tlsld_addr(Context<E> &ctx) const {
assert(has_tlsld);
return this->shdr.sh_addr + get_tlsld_offset(*this) * sizeof(Word<E>);
}
namespace {
template <typename E>
struct GotEntry {
u64 val = 0;
i64 r_type = R_NONE;
Symbol<E> *sym = nullptr;
};
}
template <typename E>
std::vector<GotEntry<E>>
get_got_entries(Context<E> &ctx, const MipsGotSection<E> &got) {
using SymbolAddend = typename MipsGotSection<E>::SymbolAddend;
std::vector<GotEntry<E>> entries;
auto add = [&](GotEntry<E> ent) { entries.push_back(ent); };
// Create GOT entries for ordinary symbols
for (const SymbolAddend &ent : got.got_syms) {
if (ent.sym->is_imported) {
// If a symbol is imported, let the dynamic linker to resolve it.
add({0, E::R_DYNAMIC, ent.sym});
} else if (ctx.arg.pic && ent.sym->is_relative()) {
// If we know an address at link-time, fill that GOT entry now.
// It may need a base relocation, though.
add({ent.get_addr(ctx, NO_PLT), E::R_RELATIVE});
} else {
add({ent.get_addr(ctx, NO_PLT)});
}
}
// Create GOT entries for GOT_PAGE and GOT_OFST relocs
for (const SymbolAddend &ent : got.gotpage_syms) {
if (ctx.arg.pic && ent.sym->is_relative())
add({ent.get_addr(ctx), E::R_RELATIVE});
else
add({ent.get_addr(ctx)});
}
// Create GOT entries for TLVs.
for (Symbol<E> *sym : got.tlsgd_syms) {
if (sym->is_imported) {
// If a symbol is imported, let the dynamic linker to resolve it.
add({0, E::R_DTPMOD, sym});
add({0, E::R_DTPOFF, sym});
} else if (ctx.arg.shared) {
// If we are creating a shared library, we know the TLV's offset
// within the current TLS block. We don't know the module ID though.
add({0, E::R_DTPMOD});
add({sym->get_addr(ctx) - ctx.dtp_addr});
} else {
// If we are creating an executable, we know both the module ID and
// the offset. Module ID 1 indicates the main executable.
add({1});
add({sym->get_addr(ctx) - ctx.dtp_addr});
}
}
for (Symbol<E> *sym : got.gottp_syms) {
if (sym->is_imported) {
// If we know nothing about the symbol, let the dynamic linker
// to fill the GOT entry.
add({0, E::R_TPOFF, sym});
} else if (ctx.arg.shared) {
// If we know the offset within the current thread vector,
// let the dynamic linker to adjust it.
add({sym->get_addr(ctx) - ctx.tls_begin, E::R_TPOFF});
} else {
// Otherwise, we know the offset from the thread pointer (TP) at
// link-time, so we can fill the GOT entry directly.
add({sym->get_addr(ctx) - ctx.tp_addr});
}
}
if (got.has_tlsld) {
if (ctx.arg.shared)
add({0, E::R_DTPMOD});
else
add({1}); // 1 means the main executable
add({0});
}
return entries;
}
template <typename E>
void MipsGotSection<E>::update_shdr(Context<E> &ctx) {
// Finalize got_syms
sort(got_syms);
remove_duplicates(got_syms);
// Finalize gotpage_syms
sort(gotpage_syms);
remove_duplicates(gotpage_syms);
// Finalize tlsgd_syms
sort(tlsgd_syms, compare<E>);
remove_duplicates(tlsgd_syms);
// Finalize gottp_syms
sort(gottp_syms, compare<E>);
remove_duplicates(gottp_syms);
this->shdr.sh_size = get_num_got_entries(*this) * sizeof(Word<E>);
}
template <typename E>
i64 MipsGotSection<E>::get_reldyn_size(Context<E> &ctx) const {
i64 n = 0;
for (GotEntry<E> &ent : get_got_entries(ctx, *this))
if (ent.r_type != R_NONE)
n++;
return n;
}
template <typename E>
void MipsGotSection<E>::copy_buf(Context<E> &ctx) {
U64<E> *buf = (U64<E> *)(ctx.buf + this->shdr.sh_offset);
memset(buf, 0, this->shdr.sh_size);
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
this->reldyn_offset);
for (i64 i = 0; GotEntry<E> &ent : get_got_entries(ctx, *this)) {
if (ent.r_type != R_NONE)
*dynrel++ = ElfRel<E>(this->shdr.sh_addr + i * sizeof(Word<E>),
ent.r_type,
ent.sym ? ent.sym->get_dynsym_idx(ctx) : 0,
ent.val);
buf[i++] = ent.val;
}
}
template <typename E>
void MipsQuickstartSection<E>::update_shdr(Context<E> &ctx) {
this->shdr.sh_size = (NUM_RESERVED + ctx.dynsym->symbols.size()) *
sizeof(Word<E>);
}
template <typename E>
void MipsQuickstartSection<E>::copy_buf(Context<E> &ctx) {
U64<E> *buf = (U64<E> *)(ctx.buf + this->shdr.sh_offset);
memset(buf, 0, this->shdr.sh_size);
// It is not clear how the runtime uses it, but all MIPS binaries
// have this value in GOT[1].
buf[1] = E::is_64 ? 0x8000'0000'0000'0000 : 0x8000'0000;
for (i64 i = 0; i < ctx.dynsym->symbols.size(); i++)
if (Symbol<E> *sym = ctx.dynsym->symbols[i])
if (!sym->file->is_dso && !sym->esym().is_undef())
buf[i + NUM_RESERVED] = sym->get_addr(ctx, NO_PLT);
}
template <typename E>
void MipsABIFlagsSection<E>::update_shdr(Context<E> &ctx) {
for (ObjectFile<E> *file : ctx.objs) {
if (file->extra.abi_flags) {
contents = file->extra.abi_flags->contents;
break;
}
}
this->shdr.sh_size = contents.size();
}
// .MIPS.abiflags section contains ABI info such as ISA level.
// We need to merge input .MIPS.abiflags sections into a single
// .MIPS.abiflags section. But for now, we just pick the first one.
template <typename E>
void MipsABIFlagsSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
memcpy(buf, contents.data(), contents.size());
}
// We merge consective .mips_got sections to reduce the total number of
// .mips_got entries. Note that each .mips_got should be equal or smaller
// than 64 KiB so that all of its entries are within its GP ± 32 KiB.
template <typename E>
void mips_merge_got_sections(Context<E> &ctx) {
for (ObjectFile<E> *file : ctx.objs)
file->extra.got->update_shdr(ctx);
for (i64 i = 0; i < ctx.objs.size(); i++) {
MipsGotSection<E> &got = *ctx.objs[i]->extra.got;
for (i++; i < ctx.objs.size(); i++) {
MipsGotSection<E> &got2 = *ctx.objs[i]->extra.got;
if (got.shdr.sh_size + got2.shdr.sh_size >= 65536)
break;
append(got.got_syms, got2.got_syms);
append(got.gotpage_syms, got2.gotpage_syms);
append(got.tlsgd_syms, got2.tlsgd_syms);
append(got.gottp_syms, got2.gottp_syms);
got.has_tlsld = got.has_tlsld || got2.has_tlsld;
got2.got_syms.clear();
got2.gotpage_syms.clear();
got2.tlsgd_syms.clear();
got2.gottp_syms.clear();
got2.has_tlsld = false;
got.update_shdr(ctx);
ctx.objs[i]->extra.got = &got;
}
}
}
// MIPS .eh_frame contains absolute addresses (i.e. R_MIPS_64 relocations)
// even if compiled with -fPIC. Instead of emitting base relocations, we
// rewrite CIEs so that we can write relative addresse instead of absolute
// ones to .eh_frame.
template <typename E>
void mips_rewrite_cie(Context<E> &ctx, u8 *buf, CieRecord<E> &cie) {
u8 *aug = buf + 9; // Skip Length, CIE ID and Version fields
if (*aug != 'z')
return;
aug++;
// Skip Augmentation String
u8 *p = aug + strlen((char *)aug) + 1;
read_uleb(&p); // Skip Code Alignment Factor
read_uleb(&p); // Skip Data Alignment Factor
p++; // Skip Return Address Register
read_uleb(&p); // Skip Augmentation Data Length
auto rewrite = [&](u8 *ptr) {
i64 sz;
switch (*ptr & 0xf) {
case DW_EH_PE_absptr:
sz = sizeof(Word<E>);
break;
case DW_EH_PE_udata4:
case DW_EH_PE_sdata4:
sz = 4;
break;
case DW_EH_PE_udata8:
case DW_EH_PE_sdata8:
sz = 8;
break;
default:
Fatal(ctx) << cie.input_section << ": unknown pointer size";
}
if ((*ptr & 0x70) == DW_EH_PE_absptr) {
if (sz == 4)
*ptr = (*ptr & 0x80) | DW_EH_PE_pcrel | DW_EH_PE_sdata4;
else
*ptr = (*ptr & 0x80) | DW_EH_PE_pcrel | DW_EH_PE_sdata8;
}
return sz;
};
// Now p points to the beginning of Augmentation Data
for (; *aug; aug++) {
switch (*aug) {
case 'L':
case 'R':
rewrite(p);
p++;
break;
case 'P':
p += rewrite(p) + 1;
break;
case 'S':
case 'B':
break;
default:
Error(ctx) << cie.input_section
<< ": unknown argumentation string in CIE: '"
<< (char)*aug << "'";
}
}
}
#define INSTANTIATE(E) \
template void write_plt_header(Context<E> &, u8 *); \
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
template void EhFrameSection<E>:: \
apply_eh_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
template void InputSection<E>::scan_relocations(Context<E> &); \
template class MipsGotSection<E>; \
template class MipsQuickstartSection<E>; \
template class MipsABIFlagsSection<E>; \
template void mips_merge_got_sections(Context<E> &); \
template void mips_rewrite_cie(Context<E> &, u8 *, CieRecord<E> &);
INSTANTIATE(MIPS64LE);
INSTANTIATE(MIPS64BE);
} // namespace mold::elf
+18 -16
View File
@@ -120,7 +120,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
ub32 *loc = (ub32 *)buf;
i64 offset = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 8;
i64 offset = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 8;
loc[4] |= higha(offset);
loc[5] |= lo(offset);
}
@@ -409,8 +409,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const ub32 local_thunk[] = {
// Get this thunk's address
0x7c08'02a6, // mflr r0
@@ -429,22 +427,26 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_entry));
static_assert(E::thunk_size == sizeof(local_thunk));
for (i64 i = 0; i < symbols.size(); i++) {
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
Symbol<E> &sym = *symbols[i];
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
if (sym.has_plt(ctx)) {
memcpy(loc, plt_entry, sizeof(plt_entry));
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
i64 val = got - get_addr(i) - 8;
loc[4] |= higha(val);
loc[5] |= lo(val);
for (Symbol<E> *sym : symbols) {
if (sym->has_plt(ctx)) {
u64 got =
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
i64 val = got - P - 8;
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx) - get_addr(i) - 8;
loc[4] |= higha(val);
loc[5] |= lo(val);
i64 val = sym->get_addr(ctx) - P - 8;
memcpy(buf, local_thunk, sizeof(local_thunk));
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
+26 -27
View File
@@ -72,22 +72,22 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
0x7d88'02a6, // mflr r12
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
0xe84b'0024, // ld r2,36(r11)
0x7d88'03a6, // mtlr r12
0x7d62'5a14, // add r11,r2,r11
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
0xe98b'0000, // ld r12,0(r11)
0xe84b'0008, // ld r2,8(r11)
0x7d89'03a6, // mtctr r12
0xe96b'0010, // ld r11,16(r11)
0x4e80'0420, // bctr
// .quad .got.plt - .plt - 8
0x0000'0000,
0x0000'0000,
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
}
template <>
@@ -410,8 +410,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// If the destination is .plt.got, we save the current r2, read an
// address of a function descriptor from .got, restore %r2 and jump
// to the function.
@@ -466,26 +464,27 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(local_thunk));
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
if (sym.has_got(ctx)) {
memcpy(loc, pltgot_thunk, sizeof(pltgot_thunk));
i64 val = sym.get_got_addr(ctx) - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
} else if(sym.has_plt(ctx)) {
memcpy(loc, plt_thunk, sizeof(plt_thunk));
i64 val = sym.get_gotplt_addr(ctx) - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
for (Symbol<E> *sym : symbols) {
if (sym->has_got(ctx)) {
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else if(sym->has_plt(ctx)) {
i64 val = sym->get_gotplt_addr(ctx) - ctx.extra.TOC->value;
memcpy(buf, plt_thunk, sizeof(plt_thunk));
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
loc[0] |= higha(val);
loc[1] |= lo(val);
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
memcpy(buf, local_thunk, sizeof(local_thunk));
*(ub32 *)buf |= higha(val);
*(ub32 *)(buf + 4) |= lo(val);
}
buf += E::thunk_size;
}
}
@@ -520,7 +519,7 @@ struct OpdSymbol {
};
static Symbol<E> *
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
if (it == syms.end())
return nullptr;
@@ -615,7 +614,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
if (sym.get_input_section() != opd)
continue;
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
Symbol<E> *real_sym = get_opd_sym_at(opd_syms, r.r_addend);
if (!real_sym)
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
<< " at offset 0x" << std::hex << (u64)r.r_addend;
+55 -37
View File
@@ -92,8 +92,10 @@ static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 prefix34(u64 x) {
return bits(x, 33, 16) | (bits(x, 15, 0) << 32);
static void write34(u8 *loc, u64 x) {
ul32 *buf = (ul32 *)loc;
buf[0] = (buf[0] & 0xfffc'0000) | bits(x, 33, 16);
buf[1] = (buf[1] & 0xffff'0000) | bits(x, 15, 0);
}
// .plt is used only for lazy symbol resolution on PPC64. All PLT
@@ -112,25 +114,26 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
0x7c08'03a6, // mtlr r0
// Compute the PLT entry index
0xe80b'002c, // ld r0, 44(r11)
0x7d8b'6050, // subf r12, r11, r12
0x7d60'5a14, // add r11, r0, r11
0x380c'ffcc, // addi r0, r12, -52
0x398c'ffd4, // addi r12, r12, -44
0x7c0b'6050, // subf r0, r11, r12
0x7800'f082, // rldicl r0, r0, 62, 2
// Compute the address of .got.plt
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
0xe98b'0000, // ld r12, 0(r11)
0x7d89'03a6, // mtctr r12
0xe96b'0008, // ld r11, 8(r11)
0x4e80'0420, // bctr
// .quad .got.plt - .plt - 8
0x0000'0000,
0x0000'0000,
};
memcpy(buf, insn, sizeof(insn));
*(ul64 *)(buf + 52) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
*(ul32 *)(buf + 28) |= higha(val);
*(ul32 *)(buf + 32) |= lo(val);
}
template <>
@@ -204,7 +207,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 TOC = ctx.extra.TOC->value;
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
switch (rel.r_type) {
case R_PPC64_ADDR64:
@@ -278,10 +281,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
case R_PPC64_GOT_PCREL34:
*(ul64 *)loc |= prefix34(G + GOT - P);
write34(loc, G + GOT - P);
break;
case R_PPC64_PCREL34:
*(ul64 *)loc |= prefix34(S + A - P);
write34(loc, S + A - P);
break;
case R_PPC64_GOT_TPREL16_HA:
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
@@ -290,7 +293,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_GOT_TPREL_PCREL34:
*(ul64 *)loc |= prefix34(sym.get_gottp_addr(ctx) - P);
write34(loc, sym.get_gottp_addr(ctx) - P);
break;
case R_PPC64_GOT_TLSGD16_HA:
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
@@ -299,7 +302,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD_PCREL34:
*(ul64 *)loc |= prefix34(sym.get_tlsgd_addr(ctx) - P);
write34(loc, sym.get_tlsgd_addr(ctx) - P);
break;
case R_PPC64_GOT_TLSLD16_HA:
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
@@ -308,7 +311,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD_PCREL34:
*(ul64 *)loc |= prefix34(ctx.got->get_tlsld_addr(ctx) - P);
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
break;
case R_PPC64_DTPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
@@ -317,7 +320,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_DTPREL34:
*(ul64 *)loc |= prefix34(S + A - ctx.dtp_addr);
write34(loc, S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
@@ -325,6 +328,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_TPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC64_TPREL34:
write34(loc, S + A - ctx.tp_addr);
break;
case R_PPC64_PLTSEQ:
case R_PPC64_PLTSEQ_NOTOC:
case R_PPC64_PLTCALL:
@@ -442,6 +448,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL34:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_REL32:
@@ -478,12 +485,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// If the destination is PLT, we read an address from .got.plt or .got
// and jump there.
static const ul32 plt_thunk[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
0x7d89'03a6, // mtctr r12
@@ -492,6 +498,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static const ul32 plt_thunk_power10[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x0410'0000, // pld r12, foo@gotplt@pcrel
0xe580'0000,
0x7d89'03a6, // mtctr r12
@@ -502,6 +509,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
// to its local entry point.
static const ul32 local_thunk[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
@@ -510,6 +518,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static const ul32 local_thunk_power10[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x0610'0000, // pla r12, foo@pcrel
0x3980'0000,
0x7d89'03a6, // mtctr r12
@@ -521,34 +530,43 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(local_thunk));
static_assert(E::thunk_size == sizeof(local_thunk_power10));
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u64 TOC = ctx.extra.TOC->value;
if (sym.has_plt(ctx)) {
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
for (Symbol<E> *sym : symbols) {
if (sym->has_plt(ctx)) {
u64 got =
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
if (ctx.extra.is_power10) {
memcpy(loc, plt_thunk_power10, E::thunk_size);
*(ul64 *)(loc + 1) |= prefix34(got - get_addr(i) - 4);
memcpy(buf, plt_thunk_power10, E::thunk_size);
write34(buf + 8, got - P - 8);
} else {
i64 val = got - ctx.extra.TOC->value;
memcpy(loc, plt_thunk, E::thunk_size);
loc[1] |= higha(val);
loc[2] |= lo(val);
memcpy(buf, plt_thunk, E::thunk_size);
*(ul32 *)(buf + 8) |= higha(got - TOC);
*(ul32 *)(buf + 12) |= lo(got - TOC);
}
} else {
u64 S = sym->get_addr(ctx);
if (ctx.extra.is_power10) {
memcpy(loc, local_thunk_power10, E::thunk_size);
*(ul64 *)(loc + 1) |= prefix34(sym.get_addr(ctx) - get_addr(i) - 4);
memcpy(buf, local_thunk_power10, E::thunk_size);
write34(buf + 8, S - P - 8);
} else {
i64 val = sym.get_addr(ctx) - ctx.extra.TOC->value;
memcpy(loc, local_thunk, E::thunk_size);
loc[1] |= higha(val);
loc[2] |= lo(val);
memcpy(buf, local_thunk, E::thunk_size);
*(ul32 *)(buf + 8) |= higha(S - TOC);
*(ul32 *)(buf + 12) |= lo(S - TOC);
}
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return 2;
}
} // namespace mold::elf
+220 -72
View File
@@ -69,8 +69,10 @@
#if MOLD_RV64LE || MOLD_RV64BE || MOLD_RV32LE || MOLD_RV32BE
#include "elf.h"
#include "mold.h"
#include <regex>
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
@@ -111,6 +113,11 @@ static void write_jtype(u8 *loc, u32 val) {
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
}
static void write_citype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'0'11111'00000'11;
*(ul16 *)loc |= bit(val, 5) << 12 | bits(val, 4, 0) << 2;
}
static void write_cbtype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'000'111'00000'11;
*(ul16 *)loc |= bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
@@ -126,11 +133,6 @@ static void write_cjtype(u8 *loc, u32 val) {
bit(val, 1) << 3 | bit(val, 5) << 2;
}
// Returns the rd register of an R/I/U/J-type instruction.
static u32 get_rd(u32 val) {
return bits(val, 11, 7);
}
static void set_rs1(u8 *loc, u32 rs1) {
assert(rs1 < 32);
*(ul32 *)loc &= 0b111111'11111'00000'111'11111'1111111;
@@ -196,7 +198,7 @@ void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
template <>
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u64 got = sym.get_got_addr(ctx);
u64 got = sym.get_got_pltgot_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
@@ -247,9 +249,17 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
}
}
static inline bool is_hi20(const ElfRel<E> &rel) {
u32 ty = rel.r_type;
return ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20 ||
ty == R_RISCV_TLSDESC_HI20;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
u64 GP = ctx.__global_pointer ? ctx.__global_pointer->get_addr(ctx) : 0;
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
@@ -277,16 +287,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
<< lo << ", " << hi << ")";
};
auto is_hi20 = [](const ElfRel<E> &r) {
u32 ty = r.r_type;
return ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20;
};
auto find_paired_reloc = [&] {
assert(sym.get_input_section() == this);
if (sym.value < r_offset) {
if (sym.value <= rels[i].r_offset - get_r_delta(i)) {
for (i64 j = i - 1; j >= 0; j--)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset - get_r_delta(j))
return j;
@@ -299,6 +301,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Fatal(ctx) << *this << ": paired relocation is missing: " << i;
};
auto get_rd = [&](i64 offset) {
// Returns the rd register of an R/I/U/J-type instruction.
return bits(*(ul32 *)(contents.data() + offset), 11, 7);
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + r_offset;
@@ -326,27 +333,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
i64 val = S + A - P;
i64 rd = get_rd(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;
write_jtype(loc, S + A - P);
write_jtype(loc, val);
} else if (removed_bytes == 6 && rd == 0) {
// auipc + jalr -> c.j
*(ul16 *)loc = 0b101'00000000000'01;
write_cjtype(loc, S + A - P);
write_cjtype(loc, val);
} else if (removed_bytes == 6 && rd == 1) {
// auipc + jalr -> c.jal
assert(!E::is_64);
*(ul16 *)loc = 0b001'00000000000'01;
write_cjtype(loc, S + A - P);
write_cjtype(loc, val);
} else {
assert(removed_bytes == 0);
// Calling an undefined weak symbol does not make sense.
// We make such call into an infinite loop. This should
// help debugging of a faulty program.
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
check(val, -(1LL << 31), 1LL << 31);
write_utype(loc, val);
write_itype(loc + 4, val);
@@ -366,7 +376,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_utype(loc, S + A - P);
break;
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S: {
case R_RISCV_PCREL_LO12_S:
case R_RISCV_GPREL_LO12_I:
case R_RISCV_GPREL_LO12_S: {
i64 idx2 = find_paired_reloc();
const ElfRel<E> &rel2 = rels[idx2];
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
@@ -390,19 +402,27 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_RISCV_PCREL_HI20:
val = S + A - P;
break;
case R_RISCV_GPREL_HI20:
val = S + A - GP;
break;
default:
unreachable();
}
if (rel.r_type == R_RISCV_PCREL_LO12_I)
if (rel.r_type == R_RISCV_PCREL_LO12_I ||
rel.r_type == R_RISCV_GPREL_LO12_I)
write_itype(loc, val);
else
write_stype(loc, val);
break;
}
case R_RISCV_HI20:
assert(removed_bytes == 0 || removed_bytes == 4);
if (removed_bytes == 0) {
if (removed_bytes == 2) {
// Rewrite LUI with C.LUI
i64 rd = get_rd(rel.r_offset);
*(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);
}
@@ -415,9 +435,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_stype(loc, S + A);
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
// accessible relative to the zero register. If the upper 20 bits
// are all zero, the corresponding LUI might have been removed.
if (bits(S + A, 31, 12) == 0)
// accessible relative to the zero register because if that's the
// case, corresponding LUI might have been removed by relaxation.
if (sign_extend(S + A, 11) == S + A)
set_rs1(loc, 0);
break;
case R_RISCV_TPREL_HI20:
@@ -445,6 +465,58 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
set_rs1(loc, 4);
break;
}
case R_RISCV_TLSDESC_HI20:
if (removed_bytes == 0)
write_utype(loc, sym.get_tlsdesc_addr(ctx) + A - P);
break;
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL: {
i64 idx2 = find_paired_reloc();
const ElfRel<E> &rel2 = rels[idx2];
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
u64 S = sym2.get_addr(ctx);
u64 A = rel2.r_addend;
u64 P = get_addr() + rel2.r_offset - get_r_delta(idx2);
switch (rel.r_type) {
case R_RISCV_TLSDESC_LOAD_LO12:
if (sym2.has_tlsdesc(ctx))
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
break;
case R_RISCV_TLSDESC_ADD_LO12:
if (sym2.has_tlsdesc(ctx)) {
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);
} else {
if (removed_bytes == 0) {
*(ul32 *)loc = 0x537; // lui a0,<hi20>
write_utype(loc, S + A - ctx.tp_addr);
}
}
break;
case R_RISCV_TLSDESC_CALL:
if (sym2.has_tlsdesc(ctx)) {
// Do nothing
} else if (sym2.has_gottp(ctx)) {
// {ld,lw} a0, <lo12>(a0)
*(ul32 *)loc = E::is_64 ? 0x53503 : 0x52503;
write_itype(loc, sym2.get_gottp_addr(ctx) + A - P);
} else {
i64 val = S + A - ctx.tp_addr;
if (sign_extend(val, 11) == val)
*(ul32 *)loc = 0x513; // addi a0,zero,<lo12>
else
*(ul32 *)loc = 0x50513; // addi a0,a0,<lo12>
write_itype(loc, val);
}
break;
}
break;
}
case R_RISCV_ADD8:
loc += S + A;
break;
@@ -495,6 +567,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check(S + A - P, -(1 << 11), 1 << 11);
write_cjtype(loc, S + A - P);
break;
case R_RISCV_GPREL_HI20:
check(S + A - GP, -(1LL << 31), 1LL << 31);
write_utype(loc, S + A - GP);
break;
case R_RISCV_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
break;
@@ -683,6 +759,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_TLS_GD_HI20:
sym.flags |= NEEDS_TLSGD;
break;
case R_RISCV_TLSDESC_HI20:
scan_tlsdesc(ctx, sym);
break;
case R_RISCV_32_PCREL:
case R_RISCV_PCREL_HI20:
scan_pcrel(ctx, sym, rel);
@@ -693,12 +772,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_TPREL_ADD:
check_tlsle(ctx, sym, rel);
break;
case R_RISCV_GPREL_HI20:
if (ctx.arg.shared)
Fatal(ctx) << *this << ": R_RISCV_GPREL_HI20 may not be used with -shared";
break;
case R_RISCV_BRANCH:
case R_RISCV_JAL:
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL:
case R_RISCV_ADD8:
case R_RISCV_ADD16:
case R_RISCV_ADD32:
@@ -710,6 +796,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_ALIGN:
case R_RISCV_RVC_BRANCH:
case R_RISCV_RVC_JUMP:
case R_RISCV_GPREL_LO12_I:
case R_RISCV_GPREL_LO12_S:
case R_RISCV_RELAX:
case R_RISCV_SUB6:
case R_RISCV_SET6:
@@ -725,14 +813,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
template <typename E>
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
template <>
u64 get_eflags(Context<E> &ctx) {
std::vector<ObjectFile<E> *> objs = ctx.objs;
std::erase(objs, ctx.internal_obj);
if (objs.empty())
return 0;
u32 ret = objs[0]->get_ehdr().e_flags;
for (i64 i = 1; i < objs.size(); i++) {
u32 flags = objs[i]->get_ehdr().e_flags;
if (flags & EF_RISCV_RVC)
ret |= EF_RISCV_RVC;
if ((flags & EF_RISCV_FLOAT_ABI) != (ret & EF_RISCV_FLOAT_ABI))
Error(ctx) << *objs[i] << ": cannot link object files with different"
<< " floating-point ABI from " << *objs[0];
if ((flags & EF_RISCV_RVE) != (ret & EF_RISCV_RVE))
Error(ctx) << *objs[i] << ": cannot link object files with different"
<< " EF_RISCV_RVE from " << *objs[0];
}
return ret;
}
static bool is_resizable(InputSection<E> *isec) {
return isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
(isec->shdr().sh_flags & SHF_EXECINSTR);
}
// Returns the distance between a relocated place and a symbol.
template <typename E>
static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
InputSection<E> &isec, const ElfRel<E> &rel) {
// We handle absolute symbols as if they were infinitely far away
@@ -754,11 +865,14 @@ static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
}
// Scan relocations to shrink sections.
template <typename E>
static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
isec.extra.r_deltas.resize(rels.size() + 1);
auto get_rd = [&](i64 offset) {
return bits(*(ul32 *)(isec.contents.data() + offset), 11, 7);
};
i64 delta = 0;
for (i64 i = 0; i < rels.size(); i++) {
@@ -795,6 +909,20 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
if (sym.file == ctx.internal_obj)
continue;
auto find_paired_reloc = [&] {
if (sym.value <= rels[i].r_offset) {
for (i64 j = i - 1; j >= 0; j--)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset)
return j;
} else {
for (i64 j = i + 1; j < rels.size(); j++)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset)
return j;
}
Fatal(ctx) << isec << ": paired relocation is missing: " << i;
};
switch (r.r_type) {
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
@@ -805,13 +933,13 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
if (dist & 1)
break;
i64 rd = get_rd(*(ul32 *)(isec.contents.data() + r.r_offset + 4));
i64 rd = get_rd(r.r_offset + 4);
if (rd == 0 && sign_extend(dist, 11) == dist && use_rvc) {
if (use_rvc && rd == 0 && sign_extend(dist, 11) == dist) {
// If rd is x0 and the jump target is within ±2 KiB, we can use
// C.J, saving 6 bytes.
delta += 6;
} else if (rd == 1 && sign_extend(dist, 11) == dist && use_rvc && !E::is_64) {
} else if (use_rvc && !E::is_64 && rd == 1 && sign_extend(dist, 11) == dist) {
// If rd is x1 and the jump target is within ±2 KiB, we can use
// C.JAL. This is RV32 only because C.JAL is RV32-only instruction.
delta += 6;
@@ -821,38 +949,68 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
}
break;
}
case R_RISCV_HI20:
// If the upper 20 bits are all zero, we can remove LUI.
// The corresponding instructions referred to by LO12_I/LO12_S
// relocations will use the zero register instead.
if (bits(sym.get_addr(ctx), 31, 12) == 0)
case R_RISCV_HI20: {
u64 val = sym.get_addr(ctx) + r.r_addend;
i64 rd = get_rd(r.r_offset);
if (sign_extend(val, 11) == val) {
// We can replace `lui t0, %hi(foo)` and `add t0, t0, %lo(foo)`
// instruction pair with `add t0, x0, %lo(foo)` if foo's bits
// [32:11] are all one or all zero.
delta += 4;
} else if (use_rvc && rd != 0 && rd != 2 && sign_extend(val, 17) == val) {
// If the upper 20 bits can actually be represented in 6 bits,
// we can use C.LUI instead of LUI.
delta += 2;
}
break;
}
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_ADD:
// These relocations are used to add a high 20-bit value to the
// thread pointer. The following two instructions materializes
// TP + HI20(foo) in %r5, for example.
// TP + %tprel_hi20(foo) in %t0, for example.
//
// lui a5,%tprel_hi(foo) # R_RISCV_TPREL_HI20 (symbol)
// add a5,a5,tp,%tprel_add(foo) # R_RISCV_TPREL_ADD (symbol)
// lui t0, %tprel_hi(foo) # R_RISCV_TPREL_HI20
// add t0, t0, tp # R_RISCV_TPREL_ADD
//
// Then thread-local variable `foo` is accessed with a low 12-bit
// offset like this:
// Then thread-local variable `foo` is accessed with the low
// 12-bit offset like this:
//
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
// sw t0, %tprel_lo(foo)(t0) # R_RISCV_TPREL_LO12_S
//
// However, if the variable is at TP ±2 KiB, TP + HI20(foo) is the
// same as TP, so we can instead access the thread-local variable
// directly using TP like this:
// However, if the variable is at TP ± 2 KiB, TP + %tprel_hi20(foo)
// is the same as TP, so we can instead access the thread-local
// variable directly using TP like this:
//
// sw t0,%tprel_lo(foo)(tp)
// sw t0, %tprel_lo(foo)(tp)
//
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
sign_extend(val, 11) == val)
delta += 4;
break;
case R_RISCV_TLSDESC_HI20:
if (!sym.has_tlsdesc(ctx))
delta += 4;
break;
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12: {
const ElfRel<E> &rel2 = rels[find_paired_reloc()];
Symbol<E> &sym2 = *isec.file.symbols[rel2.r_sym];
if (r.r_type == R_RISCV_TLSDESC_LOAD_LO12) {
if (!sym2.has_tlsdesc(ctx))
delta += 4;
} else {
assert(r.r_type == R_RISCV_TLSDESC_ADD_LO12);
if (!sym2.has_tlsdesc(ctx) && !sym2.has_gottp(ctx))
if (i64 val = sym2.get_addr(ctx) + rel2.r_addend - ctx.tp_addr;
sign_extend(val, 11) == val)
delta += 4;
}
break;
}
}
}
@@ -880,7 +1038,7 @@ i64 riscv_resize_sections<E>(Context<E> &ctx) {
// This step should only shrink sections.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (is_resizable(ctx, isec.get()))
if (is_resizable(isec.get()))
shrink_section(ctx, *isec, use_rvc);
});
@@ -936,11 +1094,13 @@ i64 riscv_resize_sections<E>(Context<E> &ctx) {
//
// The following functions takes care of ISA strings.
namespace {
struct Extn {
std::string name;
i64 major;
i64 minor;
};
}
// As per the RISC-V spec, the extension names must be sorted in a very
// specific way, and unfortunately that's not just an alphabetical order.
@@ -981,26 +1141,14 @@ static bool extn_version_less(const Extn &e1, const Extn &e2) {
}
static std::optional<Extn> read_extn_string(std::string_view &str) {
Extn extn;
auto flags = std::regex_constants::optimize | std::regex_constants::ECMAScript;
static std::regex re(R"(^([a-z]+)(\d+)p(\d+))", flags);
size_t pos = str.find_first_of("0123456789");
if (pos == str.npos)
return {};
extn.name = str.substr(0, pos);
str = str.substr(pos);
size_t nread;
extn.major = std::stoul(std::string(str), &nread, 10);
str = str.substr(nread);
if (str.size() < 2 || str[0] != 'p')
return {};
str = str.substr(1);
extn.minor = std::stoul(std::string(str), &nread, 10);
str = str.substr(nread);
if (str.empty() || str[0] == '_')
return extn;
std::cmatch m;
if (std::regex_search(str.data(), str.data() + str.size(), m, re)) {
str = str.substr(m.length());
return Extn{m[1], (i64)std::stoul(m[2]), (i64)std::stoul(m[3])};
}
return {};
}
+24 -22
View File
@@ -48,16 +48,23 @@ static void write_mid20(u8 *loc, u64 val) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static u8 insn[] = {
// Compute PLT_INDEX
0xb9, 0x09, 0x00, 0x01, // sgr %r0, %r1
0xa7, 0x0b, 0xff, 0xc2, // aghi %r0, -62
0xeb, 0x10, 0x00, 0x01, 0x00, 0x0c, // srlg %r1, %r0, 1
0xb9, 0x08, 0x00, 0x01, // agr %r0, %r1
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
// Branch to _dl_runtime_resolve
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x07, 0x00, 0x07, 0x00, // nopr; nopr
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 8) = (ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 6) >> 1;
*(ub32 *)(buf + 26) =
(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 24) >> 1;
}
template <>
@@ -65,15 +72,12 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static u8 insn[] = {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0xc0, 0x01, 0, 0, 0, 0, // lgfi %r0, PLT_INDEX
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x0d, 0x01, // basr %r0, %r1
0x07, 0x00, // nopr
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 2) = (sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
*(ub32 *)(buf + 14) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
template <>
@@ -86,7 +90,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 2) = (sym.get_got_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
*(ub32 *)(buf + 2) = (sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
}
template <>
@@ -356,12 +360,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_390_32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_390_32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_390_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub64 *)loc = *val;
@@ -455,23 +457,23 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// We always want to relax calls to __tls_get_offset() in statically-
// linked executables because __tls_get_offset() in libc.a just calls
// abort().
if (ctx.arg.is_static ||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
// do nothing
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
!ctx.arg.z_dlopen) {
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static) {
// Do nothing
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
} else {
sym.flags |= NEEDS_TLSGD;
}
break;
case R_390_TLS_LDM32:
case R_390_TLS_LDM64: {
bool do_relax = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
if (!do_relax)
case R_390_TLS_LDM64:
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared)) {
// Do nothing
} else {
ctx.needs_tlsld = true;
}
break;
}
case R_390_TLS_LE32:
case R_390_TLS_LE64:
check_tlsle(ctx, sym, rel);
+2 -2
View File
@@ -169,7 +169,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static_assert(sizeof(insn) == E::pltgot_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
@@ -181,7 +181,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static_assert(sizeof(insn) == E::pltgot_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx);
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
}
}
+20 -23
View File
@@ -115,7 +115,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
};
memcpy(buf, entry, sizeof(entry));
*(ub64 *)(buf + 24) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 4;
*(ub64 *)(buf + 24) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 4;
}
template <>
@@ -288,36 +288,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// We always have to relax a GOT load to a load immediate if a
// symbol is local, because R_SPARC_GOTDATA_OP cannot represent
// an addend for a local symbol.
if (sym.is_imported || sym.is_ifunc()) {
if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
} else if (sym.is_pcrel_linktime_const(ctx)) {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
} else {
*(ub32 *)loc |= bits(G, 31, 10);
} else if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
} else {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
}
break;
case R_SPARC_GOTDATA_OP_LOX10: {
if (sym.is_imported || sym.is_ifunc()) {
case R_SPARC_GOTDATA_OP_LOX10:
if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
} else if (sym.is_pcrel_linktime_const(ctx)) {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
} else {
*(ub32 *)loc |= bits(G, 9, 0);
} else if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
} else {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
}
break;
}
case R_SPARC_GOTDATA_OP:
if (sym.is_imported || sym.is_ifunc())
break;
if (sym.is_absolute()) {
// ldx [ %g2 + %g1 ], %g1 → nop
*(ub32 *)loc = 0x0100'0000;
} else {
} else if (sym.is_pcrel_linktime_const(ctx)) {
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
@@ -568,8 +564,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (!ctx.arg.is_static && ctx.extra.tls_get_addr_sym->is_imported)
ctx.extra.tls_get_addr_sym->flags |= NEEDS_PLT;
if (!ctx.arg.is_static)
if (Symbol<E> &sym = *ctx.extra.tls_get_addr_sym; sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
+116 -126
View File
@@ -64,28 +64,41 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
};
// Only a canonical PLT can be address-taken; there's no way to take
// an address of a non-canonical PLT. Therefore, a non-canonical PLT
// doesn't have to start with an endbr64.
if (sym.is_canonical) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
} else {
static const u8 insn[] = {
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 2) = sym.get_plt_idx(ctx);
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 6;
}
template <>
@@ -165,28 +178,6 @@ static u32 relax_gottpoff(u8 *loc) {
return 0;
}
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
}
return 0;
}
// Rewrite a function call to __tls_get_addr to a cheaper instruction
// sequence. We can do this when we know the thread-local variable's TP-
// relative address at link-time.
@@ -267,7 +258,7 @@ static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
// sequence. The difference from relax_gd_to_le is that we are
// materializing a Dynamic Thread Pointer for the current ELF module
// instead of an address for a particular thread-local variable.
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32: {
@@ -275,13 +266,18 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// e8 00 00 00 00 call __tls_get_addr
//
// The instructions are so short that we cannot rewrite them with
// "mov %fs:0, %rax" which is 9 bytes long. We use a shorter code
// sequence instead. Since "xor %eax, %eax" zero-clears %rax, the
// meaning is equivalent.
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
*(ul32 *)(loc + 5) = tls_size;
break;
}
case R_X86_64_GOTPCREL:
@@ -297,7 +293,7 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
0x90, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
*(ul32 *)(loc + 5) = tls_size;
break;
}
case R_X86_64_PLTOFF64: {
@@ -308,14 +304,12 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
*(ul32 *)(loc + 8) = tls_size;
break;
}
default:
@@ -364,7 +358,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
u64 GOT = ctx.gotplt->shdr.sh_addr;
switch (rel.r_type) {
case R_X86_64_8:
@@ -400,32 +394,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc = S + A - P;
break;
case R_X86_64_GOT32:
write32s(G + A);
write32(G + A);
break;
case R_X86_64_GOT64:
*(ul64 *)loc = G + A;
break;
case R_X86_64_GOTOFF64:
case R_X86_64_PLTOFF64:
*(ul64 *)loc = S + A - GOTPLT;
*(ul64 *)loc = S + A - GOT;
break;
case R_X86_64_GOTPC32:
write32s(GOTPLT + A - P);
write32s(GOT + A - P);
break;
case R_X86_64_GOTPC64:
*(ul64 *)loc = GOTPLT + A - P;
*(ul64 *)loc = GOT + A - P;
break;
case R_X86_64_GOTPCREL:
write32s(G + GOTPLT + A - P);
write32s(G + GOT + A - P);
break;
case R_X86_64_GOTPCREL64:
*(ul64 *)loc = G + GOTPLT + A - P;
*(ul64 *)loc = G + GOT + A - P;
break;
case R_X86_64_GOTPCRELX:
// We always want to relax GOTPCRELX relocs even if --no-relax
// was given because some static PIE runtime code depends on these
// relaxations.
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
if (sym.is_pcrel_linktime_const(ctx)) {
u32 insn = relax_gotpcrelx(loc - 2);
i64 val = S + A - P;
if (insn && (i32)val == val) {
@@ -435,10 +429,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
}
write32s(G + GOTPLT + A - P);
write32s(G + GOT + A - P);
break;
case R_X86_64_REX_GOTPCRELX:
if (!sym.is_imported && !sym.is_ifunc() && sym.is_relative()) {
if (sym.is_pcrel_linktime_const(ctx)) {
u32 insn = relax_rex_gotpcrelx(loc - 3);
i64 val = S + A - P;
if (insn && (i32)val == val) {
@@ -449,26 +443,21 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
}
write32s(G + GOTPLT + A - P);
write32s(G + GOT + A - P);
break;
case R_X86_64_TLSGD:
if (sym.has_tlsgd(ctx)) {
if (sym.has_tlsgd(ctx))
write32s(sym.get_tlsgd_addr(ctx) + A - P);
} else if (sym.has_gottp(ctx)) {
relax_gd_to_ie(loc, rels[i + 1], sym.get_gottp_addr(ctx) - P);
i++;
} else {
relax_gd_to_le(loc, rels[i + 1], S - ctx.tp_addr);
i++;
}
else if (sym.has_gottp(ctx))
relax_gd_to_ie(loc, rels[++i], sym.get_gottp_addr(ctx) - P);
else
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
break;
case R_X86_64_TLSLD:
if (ctx.got->has_tlsld(ctx)) {
if (ctx.got->has_tlsld(ctx))
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
} else {
relax_ld_to_le(loc, rels[i + 1], ctx.tp_addr - ctx.tls_begin);
i++;
}
else
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
break;
case R_X86_64_DTPOFF32:
write32s(S + A - ctx.dtp_addr);
@@ -495,15 +484,46 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
break;
case R_X86_64_GOTPC32_TLSDESC:
// x86-64 TLSDESC uses the following code sequence to materialize
// a TP-relative address in %rax.
//
// lea 0(%rip), %rax
// R_X86_64_GOTPC32_TLSDESC foo
// call *(%rax)
// R_X86_64_TLSDESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// mov foo@GOTTPOFF(%rip), %rax
// nop
//
// or to the following for executable.
//
// mov $foo@TPOFF, %rax
// nop
if (sym.has_tlsdesc(ctx)) {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
} else if (sym.has_gottp(ctx)) {
// mov foo@gottpoff(%rip), %rax
loc[-3] = 0x48;
loc[-2] = 0x8b;
loc[-1] = 0x05;
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
// mov $foo@tpoff, %rax
loc[-3] = 0x48;
loc[-2] = 0xc7;
loc[-1] = 0xc0;
write32s(S - ctx.tp_addr);
assert(A == -4);
}
break;
case R_X86_64_TLSDESC_CALL:
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
break;
case R_X86_64_SIZE32:
@@ -512,13 +532,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_X86_64_SIZE64:
*(ul64 *)loc = sym.esym().st_size + A;
break;
case R_X86_64_TLSDESC_CALL:
if (!sym.has_tlsdesc(ctx)) {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
break;
default:
unreachable();
}
@@ -653,6 +666,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
if (rel.r_type == R_X86_64_TLSGD || rel.r_type == R_X86_64_TLSLD) {
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": " << rel
<< " must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": " << rel
<< " must be followed by PLT or GOTPCREL";
}
switch (rel.r_type) {
case R_X86_64_8:
case R_X86_64_16:
@@ -685,25 +711,12 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_PLT;
break;
case R_X86_64_TLSGD:
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (ctx.arg.is_static ||
(ctx.arg.relax && !sym.is_imported && !ctx.arg.shared)) {
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
i++;
} else if (ctx.arg.relax && !sym.is_imported && ctx.arg.shared &&
!ctx.arg.z_dlopen) {
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
i++;
} else {
@@ -711,18 +724,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
break;
case R_X86_64_TLSLD:
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
@@ -730,28 +731,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
else
ctx.needs_tlsld = true;
break;
case R_X86_64_GOTTPOFF: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
!sym.is_imported && relax_gottpoff(loc - 3);
if (!do_relax)
case R_X86_64_GOTTPOFF:
if (ctx.arg.relax && relax_gottpoff(loc - 3) &&
sym.is_tprel_linktime_const(ctx)) {
// do nothing
} else {
sym.flags |= NEEDS_GOTTP;
}
break;
}
case R_X86_64_GOTPC32_TLSDESC: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
<< " against an invalid code sequence";
if (!relax_tlsdesc(ctx, sym))
sym.flags |= NEEDS_TLSDESC;
case R_X86_64_TLSDESC_CALL:
scan_tlsdesc(ctx, sym);
break;
}
case R_X86_64_TPOFF32:
case R_X86_64_TPOFF64:
check_tlsle(ctx, sym, rel);
@@ -761,7 +751,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_DTPOFF64:
case R_X86_64_SIZE32:
case R_X86_64_SIZE64:
case R_X86_64_TLSDESC_CALL:
case R_X86_64_GOTPC32_TLSDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
+12 -15
View File
@@ -402,7 +402,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
std::vector<std::string> remaining;
std::string_view arg;
ctx.page_size = E::page_size;
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
bool version_shown = false;
@@ -526,16 +525,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
<< " elf64alpha\n elf64ltsmip\n elf64btsmip\n"
<< " elf64loongarch\n elf32loongarch";
<< " elf64alpha\n elf64loongarch\n elf32loongarch";
version_shown = true;
} else if (read_flag("mips32") || read_flag("mips32r2") ||
read_flag("mips32r3") || read_flag("mips32r4") ||
read_flag("mips32r5") || read_flag("mips32r6") ||
read_flag("mips64") || read_flag("mips64r2") ||
read_flag("mips64r3") || read_flag("mips64r4") ||
read_flag("mips64r5") || read_flag("mips64r6")) {
// Ignore useless MIPS-specific flags
} else if (read_arg("m")) {
if (arg == "elf_x86_64") {
ctx.arg.emulation = X86_64::target_name;
@@ -569,10 +560,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.emulation = SH4::target_name;
} else if (arg == "elf64alpha") {
ctx.arg.emulation = ALPHA::target_name;
} else if (arg == "elf64ltsmip") {
ctx.arg.emulation = MIPS64LE::target_name;
} else if (arg == "elf64btsmip") {
ctx.arg.emulation = MIPS64BE::target_name;
} else if (arg == "elf64loongarch") {
ctx.arg.emulation = LOONGARCH64::target_name;
} else if (arg == "elf32loongarch") {
@@ -896,6 +883,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.z_sectionheader = true;
} else if (read_z_flag("nosectionheader")) {
ctx.arg.z_sectionheader = false;
} else if (read_z_flag("rewrite-endbr")) {
ctx.arg.z_rewrite_endbr = true;
} else if (read_flag("no-undefined")) {
ctx.arg.z_defs = true;
} else if (read_flag("fatal-warnings")) {
@@ -1218,7 +1207,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Fatal(ctx) << "-auxiliary may not be used without -shared";
}
if constexpr (!E::is_rela || is_mips<E>)
if constexpr (!E::is_rela)
if (!ctx.arg.apply_dynamic_relocs)
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
<< E::target_name;
@@ -1275,6 +1264,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.shared)
ctx.overwrite_output_file = false;
if (!ctx.arg.chroot.empty()) {
if (!ctx.arg.Map.empty())
ctx.arg.Map = ctx.arg.chroot + "/" + ctx.arg.Map;
if (!ctx.arg.dependency_file.empty())
ctx.arg.dependency_file = ctx.arg.chroot + "/" + ctx.arg.dependency_file;
}
if (version_shown && remaining.empty())
exit(0);
return remaining;
-554
View File
@@ -1,554 +0,0 @@
// This file contains code to read DWARF debug info to create .gdb_index.
//
// .gdb_index is an optional section to speed up GNU debugger. It contains
// two maps: 1) a map from function/variable/type names to compunits, and
// 2) a map from function address ranges to compunits. gdb uses these
// maps to quickly find a compunit given a name or an instruction pointer.
//
// (Terminology: a compilation unit, which often abbreviated as compunit
// or cu, is a unit of debug info. An input .debug_info section usually
// contains one compunit, and thus an output .debug_info contains as
// many compunits as the number of input files.)
//
// .gdb_index is not mandatory. All the information in .gdb_index is
// also in other debug info sections. You can actually create an
// executable without .gdb_index and later add it using `gdb-add-index`
// post-processing tool that comes with gdb.
//
// The mapping from names to compunits is 1:n while the mapping from
// address ranges to compunits is 1:1. That is, two object files may
// define the same type name (with the same definition), while there
// should be no two functions that overlap with each other in memory.
//
// .gdb_index contains an on-disk hash table for names, so gdb can
// lookup names without loading all strings into memory and construct an
// in-memory hash table.
//
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
// sections. These sections are created if `-ggnu-pubnames` is given.
// Besides names, these sections contains attributes for each name so
// that gdb can distinguish type names from function names, for example.
//
// A compunit contains one or more function address ranges. If an
// object file is compiled without -ffunction-sections, it contains
// only one .text section and therefore contains a single address range.
// Such range is typically stored directly to the compunit.
//
// If an object file is compiled with -fucntion-sections, it contains
// more than one .text section, and it has as many address ranges as
// the number of .text sections. Such discontiguous address ranges are
// stored to .debug_ranges in DWARF 2/3/4/5 and
// .debug_rnglists/.debug_addr in DWARF 5.
//
// .debug_info section contains DWARF debug info. Although we don't need
// to parse the whole .debug_info section to read address ranges, we
// have to do a little bit. DWARF is complicated and often handled using
// a library such as libdwarf. But we don't use any library because we
// don't want to add an extra run-time dependency just for --gdb-index.
//
// This page explains the format of .gdb_index:
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
#include "mold.h"
namespace mold::elf {
// The hash function for .gdb_index.
static u32 gdb_hash(std::string_view name) {
u32 h = 0;
for (u8 c : name) {
if ('A' <= c && c <= 'Z')
c = 'a' + c - 'A';
h = h * 67 + c - 113;
}
return h;
}
// Split .debug_info into so-called "compilation units". A .debug_info
// section usually contains one compunit unless it was created by `ld -r`.
// This is for --gdb-index.
template <typename E>
std::vector<std::string_view>
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
file.debug_info->uncompress(ctx);
std::string_view data = file.debug_info->contents;
std::vector<std::string_view> vec;
while (!data.empty()) {
if (data.size() < 4)
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
if (*(U32<E> *)data.data() == 0xffff'ffff)
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
i64 len = *(U32<E> *)data.data() + 4;
vec.push_back(data.substr(0, len));
data = data.substr(len);
}
return vec;
}
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
// null-terminated string) tuples.
//
// The 4-byte offset is an offset into .debug_info that contains details
// about the name. The 1-byte type is a type of the corresponding name
// (e.g. function, variable or datatype). The string is a name of a
// function, a variable or a type.
template <typename E>
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
std::vector<GdbIndexName> vec;
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
i64 off = 0;
for (i64 i = 0; i < file.compunits.size(); i++) {
if (offset == off)
return file.compunits_idx + i;
off += file.compunits[i].size();
}
Fatal(ctx) << isec << ": corrupted debug_info_offset";
};
auto read = [&](InputSection<E> &isec) {
isec.uncompress(ctx);
std::string_view contents = isec.contents;
while (!contents.empty()) {
if (contents.size() < 14)
Fatal(ctx) << isec << ": corrupted header";
u32 len = *(U32<E> *)contents.data() + 4;
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
std::string_view data = contents.substr(14, len - 14);
contents = contents.substr(len);
while (!data.empty()) {
u32 offset = *(U32<E> *)data.data();
data = data.substr(4);
if (offset == 0)
break;
u8 type = data[0];
data = data.substr(1);
std::string_view name = data.data();
data = data.substr(name.size() + 1);
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
}
}
};
if (file.debug_pubnames)
read(*file.debug_pubnames);
if (file.debug_pubtypes)
read(*file.debug_pubtypes);
// Uniquify elements because GCC 11 seems to emit one record for each
// comdat group which results in having a lot of duplicate records.
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
return std::tuple{a.hash, a.attr, a.name} <
std::tuple{b.hash, b.attr, b.name};
};
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
return std::tuple{a.hash, a.attr, a.name} ==
std::tuple{b.hash, b.attr, b.name};
};
std::sort(vec.begin(), vec.end(), less);
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
return vec;
}
template <typename E>
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
if (u8 *buf = chunk->get_uncompressed_data())
return buf;
return ctx.buf + chunk->shdr.sh_offset;
}
// Try to find a compilation unit from .debug_info and its
// corresponding record from .debug_abbrev and returns them.
template <typename E>
static std::tuple<u8 *, u8 *, u32>
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
// Read .debug_info to find the record at a given offset.
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
u32 dwarf_version = *(U16<E> *)(cu + 4);
u32 abbrev_offset;
// Skip a header.
switch (dwarf_version) {
case 2:
case 3:
case 4:
abbrev_offset = *(U32<E> *)(cu + 6);
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
<< address_size;
cu += 11;
break;
case 5: {
abbrev_offset = *(U32<E> *)(cu + 8);
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
<< address_size;
switch (u32 unit_type = cu[6]; unit_type) {
case DW_UT_compile:
case DW_UT_partial:
cu += 12;
break;
case DW_UT_skeleton:
case DW_UT_split_compile:
cu += 20;
break;
default:
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
<< std::hex << unit_type;
}
break;
}
default:
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
<< dwarf_version;
}
u32 abbrev_code = read_uleb(&cu);
// Find a .debug_abbrev record corresponding to the .debug_info record.
// We assume the .debug_info record at a given offset is of
// DW_TAG_compile_unit which describes a compunit.
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
for (;;) {
u32 code = read_uleb(&abbrev);
if (code == 0)
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
<< " a record for the first .debug_info record";
if (code == abbrev_code) {
// Found a record
u64 abbrev_tag = read_uleb(&abbrev);
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
<< std::hex << abbrev_tag;
break;
}
// Skip an uninteresting record
read_uleb(&abbrev); // tag
abbrev++; // has_children byte
for (;;) {
u64 name = read_uleb(&abbrev);
u64 form = read_uleb(&abbrev);
if (name == 0 && form == 0)
break;
if (form == DW_FORM_implicit_const)
read_uleb(&abbrev);
}
}
abbrev++; // skip has_children byte
return {cu, abbrev, dwarf_version};
}
// Estimate the number of address ranges contained in a given file.
// It may over-estimate but never under-estimate.
template <typename E>
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
// Each CU contains zero or one address area.
i64 ret = file.compunits.size();
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
// .debug_ranges contains a vector of [begin, end) address pairs.
// The last entry must be a null terminator, so we do -1.
if (file.debug_ranges)
ret += file.debug_ranges->sh_size / sizeof(Word<E>) / 2 - 1;
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
// contains variable-length entries. The smallest possible range entry
// is one byte for the code and two ULEB128 values (each can be as
// small as one byte), so 3 bytes.
if (file.debug_rnglists)
ret += file.debug_rnglists->sh_size / 3;
return ret;
}
// .debug_info contains variable-length fields. This class reads them.
template <typename E>
class DebugInfoReader {
public:
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
: ctx(ctx), file(file), cu(cu) {}
u64 read(u64 form);
Context<E> &ctx;
ObjectFile<E> &file;
u8 *cu;
};
// Read value of the given DW_FORM_* form. If a value is not scalar,
// returns a dummy value 0.
template <typename E>
u64 DebugInfoReader<E>::read(u64 form) {
switch (form) {
case DW_FORM_flag_present:
return 0;
case DW_FORM_data1:
case DW_FORM_flag:
case DW_FORM_strx1:
case DW_FORM_addrx1:
case DW_FORM_ref1:
return *cu++;
case DW_FORM_data2:
case DW_FORM_strx2:
case DW_FORM_addrx2:
case DW_FORM_ref2: {
u64 val = *(U16<E> *)cu;
cu += 2;
return val;
}
case DW_FORM_strx3:
case DW_FORM_addrx3: {
u64 val = *(U24<E> *)cu;
cu += 3;
return val;
}
case DW_FORM_data4:
case DW_FORM_strp:
case DW_FORM_sec_offset:
case DW_FORM_line_strp:
case DW_FORM_strx4:
case DW_FORM_addrx4:
case DW_FORM_ref4: {
u64 val = *(U32<E> *)cu;
cu += 4;
return val;
}
case DW_FORM_data8:
case DW_FORM_ref8: {
u64 val = *(U64<E> *)cu;
cu += 8;
return val;
}
case DW_FORM_addr:
case DW_FORM_ref_addr: {
u64 val = *(Word<E> *)cu;
cu += sizeof(Word<E>);
return val;
}
case DW_FORM_strx:
case DW_FORM_addrx:
case DW_FORM_udata:
case DW_FORM_ref_udata:
case DW_FORM_loclistx:
case DW_FORM_rnglistx:
return read_uleb(&cu);
case DW_FORM_string:
cu += strlen((char *)cu) + 1;
return 0;
default:
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
<< std::hex << form;
return 0;
}
}
// Read a range list from .debug_ranges starting at the given offset.
template <typename E>
static std::vector<u64>
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
std::vector<u64> vec;
u64 base = 0;
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
if (range[i] + 1 == 0) {
// base address selection entry
base = range[i + 1];
} else {
vec.push_back(range[i] + base);
vec.push_back(range[i + 1] + base);
}
}
return vec;
}
// Read a range list from .debug_rnglists starting at the given offset.
template <typename E>
static std::vector<u64>
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
Word<E> *addrx) {
std::vector<u64> vec;
u64 base = 0;
for (;;) {
switch (*rnglist++) {
case DW_RLE_end_of_list:
return vec;
case DW_RLE_base_addressx:
base = addrx[read_uleb(&rnglist)];
break;
case DW_RLE_startx_endx:
vec.push_back(addrx[read_uleb(&rnglist)]);
vec.push_back(addrx[read_uleb(&rnglist)]);
break;
case DW_RLE_startx_length:
vec.push_back(addrx[read_uleb(&rnglist)]);
vec.push_back(vec.back() + read_uleb(&rnglist));
break;
case DW_RLE_offset_pair:
vec.push_back(base + read_uleb(&rnglist));
vec.push_back(base + read_uleb(&rnglist));
break;
case DW_RLE_base_address:
base = *(Word<E> *)rnglist;
rnglist += sizeof(Word<E>);
break;
case DW_RLE_start_end:
vec.push_back(*(Word<E> *)rnglist);
rnglist += sizeof(Word<E>);
vec.push_back(*(Word<E> *)rnglist);
rnglist += sizeof(Word<E>);
break;
case DW_RLE_start_length:
vec.push_back(*(Word<E> *)rnglist);
rnglist += sizeof(Word<E>);
vec.push_back(vec.back() + read_uleb(&rnglist));
break;
}
}
}
// Returns a list of address ranges explained by a compunit at the
// `offset` in an output .debug_info section.
//
// .debug_info contains DWARF debug info records, so this function
// parses DWARF. If a designated compunit contains multiple ranges, the
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
// Otherwise, a range is read directly from .debug_info (or possibly
// from .debug_addr for DWARF5).
template <typename E>
std::vector<u64>
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
u8 *cu;
u8 *abbrev;
u32 dwarf_version;
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
DebugInfoReader<E> reader{ctx, file, cu};
struct Record {
u64 form = 0;
u64 value = 0;
};
Record low_pc;
Record high_pc;
Record ranges;
std::optional<u64> rnglists_base;
Word<E> *addrx = nullptr;
// Read all interesting debug records.
for (;;) {
u64 name = read_uleb(&abbrev);
u64 form = read_uleb(&abbrev);
if (name == 0 && form == 0)
break;
u64 val = reader.read(form);
switch (name) {
case DW_AT_low_pc:
low_pc = {form, val};
break;
case DW_AT_high_pc:
high_pc = {form, val};
break;
case DW_AT_rnglists_base:
rnglists_base = val;
break;
case DW_AT_addr_base:
addrx = (Word<E> *)(get_buffer(ctx, ctx.debug_addr) + val);
break;
case DW_AT_ranges:
ranges = {form, val};
break;
}
}
// Handle non-contiguous address ranges.
if (ranges.form) {
if (dwarf_version <= 4) {
Word<E> *range_begin =
(Word<E> *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
return read_debug_range(ctx, file, range_begin);
}
assert(dwarf_version == 5);
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
if (ranges.form == DW_FORM_sec_offset)
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
if (!rnglists_base)
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
u8 *base = buf + *rnglists_base;
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
}
// Handle a contiguous address range.
if (low_pc.form && high_pc.form) {
u64 lo;
switch (low_pc.form) {
case DW_FORM_addr:
lo = low_pc.value;
break;
case DW_FORM_addrx:
case DW_FORM_addrx1:
case DW_FORM_addrx2:
case DW_FORM_addrx4:
lo = addrx[low_pc.value];
break;
default:
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
<< std::hex << high_pc.form;
}
switch (high_pc.form) {
case DW_FORM_addr:
return {lo, high_pc.value};
case DW_FORM_addrx:
case DW_FORM_addrx1:
case DW_FORM_addrx2:
case DW_FORM_addrx4:
return {lo, addrx[high_pc.value]};
case DW_FORM_udata:
case DW_FORM_data1:
case DW_FORM_data2:
case DW_FORM_data4:
case DW_FORM_data8:
return {lo, lo + high_pc.value};
default:
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
<< std::hex << high_pc.form;
}
}
return {};
}
using E = MOLD_TARGET;
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
} // namespace mold::elf
+916 -998
View File
File diff suppressed because it is too large. Load diff
+179 -328
View File
@@ -2,6 +2,7 @@
#include "../common/integers.h"
#include <concepts>
#include <ostream>
#include <string>
#include <type_traits>
@@ -24,8 +25,6 @@ struct SPARC64;
struct M68K;
struct SH4;
struct ALPHA;
struct MIPS64LE;
struct MIPS64BE;
struct LOONGARCH64;
struct LOONGARCH32;
@@ -91,7 +90,6 @@ enum : u32 {
SHT_ARM_EXIDX = 0x70000001,
SHT_ARM_ATTRIBUTES = 0x70000003,
SHT_RISCV_ATTRIBUTES = 0x70000003,
SHT_MIPS_ABIFLAGS = 0x7000002a,
};
enum : u32 {
@@ -106,7 +104,6 @@ enum : u32 {
SHF_TLS = 0x400,
SHF_COMPRESSED = 0x800,
SHF_GNU_RETAIN = 0x200000,
SHF_MIPS_GPREL = 0x10000000,
SHF_EXCLUDE = 0x80000000,
};
@@ -191,7 +188,6 @@ enum : u32 {
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
PT_ARM_EXIDX = 0x70000001,
PT_RISCV_ATTRIBUTES = 0x70000003,
PT_MIPS_ABIFLAGS = 0x70000003,
};
enum : u32 {
@@ -226,7 +222,6 @@ enum : u32 {
EM_NONE = 0,
EM_386 = 3,
EM_68K = 4,
EM_MIPS = 8,
EM_PPC = 20,
EM_PPC64 = 21,
EM_S390X = 22,
@@ -296,13 +291,7 @@ enum : u32 {
DT_VERNEEDNUM = 0x6fffffff,
DT_PPC_GOT = 0x70000000,
DT_PPC64_GLINK = 0x70000000,
DT_MIPS_RLD_VERSION = 0x70000001,
DT_MIPS_FLAGS = 0x70000005,
DT_MIPS_BASE_ADDRESS = 0x70000006,
DT_MIPS_LOCAL_GOTNO = 0x7000000a,
DT_MIPS_SYMTABNO = 0x70000011,
DT_MIPS_GOTSYM = 0x70000013,
DT_MIPS_OPTIONS = 0x70000029,
DT_AARCH64_VARIANT_PCS = 0x70000005,
DT_AUXILIARY = 0x7ffffffd,
DT_FILTER = 0x7fffffff,
};
@@ -756,6 +745,7 @@ enum : u32 {
R_RISCV_TLS_DTPREL64 = 9,
R_RISCV_TLS_TPREL32 = 10,
R_RISCV_TLS_TPREL64 = 11,
R_RISCV_TLSDESC = 12,
R_RISCV_BRANCH = 16,
R_RISCV_JAL = 17,
R_RISCV_CALL = 18,
@@ -785,6 +775,9 @@ enum : u32 {
R_RISCV_RVC_BRANCH = 44,
R_RISCV_RVC_JUMP = 45,
R_RISCV_RVC_LUI = 46,
R_RISCV_GPREL_LO12_I = 47,
R_RISCV_GPREL_LO12_S = 48,
R_RISCV_GPREL_HI20 = 49,
R_RISCV_RELAX = 51,
R_RISCV_SUB6 = 52,
R_RISCV_SET6 = 53,
@@ -796,6 +789,10 @@ enum : u32 {
R_RISCV_PLT32 = 59,
R_RISCV_SET_ULEB128 = 60,
R_RISCV_SUB_ULEB128 = 61,
R_RISCV_TLSDESC_HI20 = 62,
R_RISCV_TLSDESC_LOAD_LO12 = 63,
R_RISCV_TLSDESC_ADD_LO12 = 64,
R_RISCV_TLSDESC_CALL = 65,
};
enum : u32 {
@@ -1260,85 +1257,6 @@ enum : u32 {
R_ALPHA_TPREL16 = 41,
};
enum : u32 {
R_MIPS_NONE = 0,
R_MIPS_16 = 1,
R_MIPS_32 = 2,
R_MIPS_REL32 = 3,
R_MIPS_26 = 4,
R_MIPS_HI16 = 5,
R_MIPS_LO16 = 6,
R_MIPS_GPREL16 = 7,
R_MIPS_LITERAL = 8,
R_MIPS_GOT16 = 9,
R_MIPS_PC16 = 10,
R_MIPS_CALL16 = 11,
R_MIPS_GPREL32 = 12,
R_MIPS_UNUSED1 = 13,
R_MIPS_UNUSED2 = 14,
R_MIPS_UNUSED3 = 15,
R_MIPS_SHIFT5 = 16,
R_MIPS_SHIFT6 = 17,
R_MIPS_64 = 18,
R_MIPS_GOT_DISP = 19,
R_MIPS_GOT_PAGE = 20,
R_MIPS_GOT_OFST = 21,
R_MIPS_GOT_HI16 = 22,
R_MIPS_GOT_LO16 = 23,
R_MIPS_SUB = 24,
R_MIPS_INSERT_A = 25,
R_MIPS_INSERT_B = 26,
R_MIPS_DELETE = 27,
R_MIPS_HIGHER = 28,
R_MIPS_HIGHEST = 29,
R_MIPS_CALL_HI16 = 30,
R_MIPS_CALL_LO16 = 31,
R_MIPS_SCN_DISP = 32,
R_MIPS_REL16 = 33,
R_MIPS_ADD_IMMEDIATE = 34,
R_MIPS_PJUMP = 35,
R_MIPS_RELGOT = 36,
R_MIPS_JALR = 37,
R_MIPS_TLS_DTPMOD32 = 38,
R_MIPS_TLS_DTPREL32 = 39,
R_MIPS_TLS_DTPMOD64 = 40,
R_MIPS_TLS_DTPREL64 = 41,
R_MIPS_TLS_GD = 42,
R_MIPS_TLS_LDM = 43,
R_MIPS_TLS_DTPREL_HI16 = 44,
R_MIPS_TLS_DTPREL_LO16 = 45,
R_MIPS_TLS_GOTTPREL = 46,
R_MIPS_TLS_TPREL32 = 47,
R_MIPS_TLS_TPREL64 = 48,
R_MIPS_TLS_TPREL_HI16 = 49,
R_MIPS_TLS_TPREL_LO16 = 50,
R_MIPS_GLOB_DAT = 51,
R_MIPS_PC21_S2 = 60,
R_MIPS_PC26_S2 = 61,
R_MIPS_PC18_S3 = 62,
R_MIPS_PC19_S2 = 63,
R_MIPS_PCHI16 = 64,
R_MIPS_PCLO16 = 65,
R_MIPS16_26 = 100,
R_MIPS16_GPREL = 101,
R_MIPS16_GOT16 = 102,
R_MIPS16_CALL16 = 103,
R_MIPS16_HI16 = 104,
R_MIPS16_LO16 = 105,
R_MIPS16_TLS_GD = 106,
R_MIPS16_TLS_LDM = 107,
R_MIPS16_TLS_DTPREL_HI16 = 108,
R_MIPS16_TLS_DTPREL_LO16 = 109,
R_MIPS16_TLS_GOTTPREL = 110,
R_MIPS16_TLS_TPREL_HI16 = 111,
R_MIPS16_TLS_TPREL_LO16 = 112,
R_MIPS_COPY = 126,
R_MIPS_JUMP_SLOT = 127,
R_MIPS_NUM = 218,
R_MIPS_PC32 = 248,
R_MIPS_EH = 249,
};
enum : u32 {
R_LARCH_NONE = 0,
R_LARCH_32 = 1,
@@ -1724,6 +1642,16 @@ struct ElfRel<E> {
std::conditional_t<E::is_64, U32<E>, u8> r_type;
};
// Returns true if a given relocation is of type used for direct
// function call.
template <typename E>
inline bool is_func_call_rel(const ElfRel<E> &r) {
for (u32 r_type : E::R_FUNCALL)
if (r.r_type == r_type)
return true;
return false;
}
template <typename E>
struct ElfDyn {
Word<E> d_tag;
@@ -1791,6 +1719,35 @@ struct ElfNhdr {
// Target-specific ELF data types
//
template <>
struct ElfSym<ARM64> {
bool is_undef() const { return st_shndx == SHN_UNDEF; }
bool is_abs() const { return st_shndx == SHN_ABS; }
bool is_common() const { return st_shndx == SHN_COMMON; }
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_weak() const { return is_undef() && is_weak(); }
ul32 st_name;
#ifdef __LITTLE_ENDIAN__
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u8 : 5;
u8 arm64_variant_pcs : 1; // ARM64-specific
#else
u8 st_bind : 4;
u8 st_type : 4;
u8 arm64_variant_pcs : 1;
u8 : 6;
u8 st_visibility : 2;
#endif
ul16 st_shndx;
ul64 st_value;
ul64 st_size;
};
template <>
struct ElfSym<PPC64V2> {
bool is_undef() const { return st_shndx == SHN_UNDEF; }
@@ -1864,86 +1821,42 @@ struct ElfRel<SPARC64> {
ib64 r_addend;
};
template <>
struct ElfRel<MIPS64LE> {
ElfRel() = default;
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
: r_offset(offset), r_sym(sym), r_type(type), r_addend(addend) {}
// In the little-endian MIPS64, r_sym and r_type are reversed, with
// r_type being stored in big-endian order. It's extremely odd though.
ul64 r_offset;
ul32 r_sym;
ub32 r_type;
il64 r_addend;
};
// .MIPS.options section
template <typename E>
struct MipsOptions {
u8 kind;
u8 size;
U16<E> section;
U32<E> info;
};
template <typename E>
struct MipsRegInfo {
U32<E> ri_gprmask;
U32<E> ri_pad;
U32<E> ri_cprmask[4];
U64<E> ri_gp_value;
};
enum : u32 {
ODK_REGINFO = 1,
};
//
// Machine descriptions
//
template <typename E>
static constexpr bool supports_ifunc = requires { E::R_IRELATIVE; };
template <typename E> concept supports_ifunc = requires { E::R_IRELATIVE; };
template <typename E> concept supports_tlsdesc = requires { E::R_TLSDESC; };
template <typename E> concept needs_thunk = requires { E::thunk_size; };
template <typename E>
static constexpr bool supports_tlsdesc = requires { E::R_TLSDESC; };
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
template <typename E> concept is_i386 = std::same_as<E, I386>;
template <typename E> concept is_arm64 = std::same_as<E, ARM64>;
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
template <typename E> concept is_rv32be = std::same_as<E, RV32BE>;
template <typename E> concept is_ppc32 = std::same_as<E, PPC32>;
template <typename E> concept is_ppc64v1 = std::same_as<E, PPC64V1>;
template <typename E> concept is_ppc64v2 = std::same_as<E, PPC64V2>;
template <typename E> concept is_s390x = std::same_as<E, S390X>;
template <typename E> concept is_sparc64 = std::same_as<E, SPARC64>;
template <typename E> concept is_m68k = std::same_as<E, M68K>;
template <typename E> concept is_sh4 = std::same_as<E, SH4>;
template <typename E> concept is_alpha = std::same_as<E, ALPHA>;
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
template <typename E>
static constexpr bool needs_thunk = requires { E::thunk_size; };
template <typename E> static constexpr bool is_x86_64 = std::is_same_v<E, X86_64>;
template <typename E> static constexpr bool is_i386 = std::is_same_v<E, I386>;
template <typename E> static constexpr bool is_arm64 = std::is_same_v<E, ARM64>;
template <typename E> static constexpr bool is_arm32 = std::is_same_v<E, ARM32>;
template <typename E> static constexpr bool is_rv64le = std::is_same_v<E, RV64LE>;
template <typename E> static constexpr bool is_rv64be = std::is_same_v<E, RV64BE>;
template <typename E> static constexpr bool is_rv32le = std::is_same_v<E, RV32LE>;
template <typename E> static constexpr bool is_rv32be = std::is_same_v<E, RV32BE>;
template <typename E> static constexpr bool is_ppc32 = std::is_same_v<E, PPC32>;
template <typename E> static constexpr bool is_ppc64v1 = std::is_same_v<E, PPC64V1>;
template <typename E> static constexpr bool is_ppc64v2 = std::is_same_v<E, PPC64V2>;
template <typename E> static constexpr bool is_s390x = std::is_same_v<E, S390X>;
template <typename E> static constexpr bool is_sparc64 = std::is_same_v<E, SPARC64>;
template <typename E> static constexpr bool is_m68k = std::is_same_v<E, M68K>;
template <typename E> static constexpr bool is_sh4 = std::is_same_v<E, SH4>;
template <typename E> static constexpr bool is_alpha = std::is_same_v<E, ALPHA>;
template <typename E> static constexpr bool is_mips64le = std::is_same_v<E, MIPS64LE>;
template <typename E> static constexpr bool is_mips64be = std::is_same_v<E, MIPS64BE>;
template <typename E> static constexpr bool is_loongarch64 = std::is_same_v<E, LOONGARCH64>;
template <typename E> static constexpr bool is_loongarch32 = std::is_same_v<E, LOONGARCH32>;
template <typename E> static constexpr bool is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> static constexpr bool is_arm = is_arm64<E> || is_arm32<E>;
template <typename E> static constexpr bool is_rv64 = is_rv64le<E> || is_rv64be<E>;
template <typename E> static constexpr bool is_rv32 = is_rv32le<E> || is_rv32be<E>;
template <typename E> static constexpr bool is_riscv = is_rv64<E> || is_rv32<E>;
template <typename E> static constexpr bool is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
template <typename E> static constexpr bool is_ppc = is_ppc64<E> || is_ppc32<E>;
template <typename E> static constexpr bool is_sparc = is_sparc64<E>;
template <typename E> static constexpr bool is_mips64 = is_mips64le<E> || is_mips64be<E>;
template <typename E> static constexpr bool is_mips = is_mips64<E>;
template <typename E> static constexpr bool is_loongarch = is_loongarch64<E> || is_loongarch32<E>;
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
template <typename E> concept is_rv32 = is_rv32le<E> || is_rv32be<E>;
template <typename E> concept is_riscv = is_rv64<E> || is_rv32<E>;
template <typename E> concept is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
template <typename E> concept is_ppc = is_ppc64<E> || is_ppc32<E>;
template <typename E> concept is_sparc = is_sparc64<E>;
template <typename E> concept is_loongarch = is_loongarch64<E> || is_loongarch32<E>;
struct X86_64 {
static constexpr std::string_view target_name = "x86_64";
@@ -1954,19 +1867,20 @@ struct X86_64 {
static constexpr u32 e_machine = EM_X86_64;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0xcc };
static constexpr u32 R_COPY = R_X86_64_COPY;
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
static constexpr u32 R_ABS = R_X86_64_64;
static constexpr u32 R_DYNAMIC = R_X86_64_64;
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
};
struct I386 {
@@ -1979,18 +1893,19 @@ struct I386 {
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0xcc };
static constexpr u32 R_COPY = R_386_COPY;
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
static constexpr u32 R_ABS = R_386_32;
static constexpr u32 R_DYNAMIC = R_386_32;
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
static constexpr u32 R_FUNCALL[] = { R_386_PLT32 };
};
struct ARM64 {
@@ -2004,19 +1919,20 @@ struct ARM64 {
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 12;
static constexpr u32 thunk_size = 16;
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 };
static constexpr u32 R_COPY = R_AARCH64_COPY;
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_AARCH64_JUMP_SLOT;
static constexpr u32 R_ABS = R_AARCH64_ABS64;
static constexpr u32 R_DYNAMIC = R_AARCH64_ABS64;
static constexpr u32 R_RELATIVE = R_AARCH64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_AARCH64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_AARCH64_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
};
struct ARM32 {
@@ -2029,112 +1945,90 @@ struct ARM32 {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 12;
static constexpr u32 thunk_size = 20;
static constexpr u32 thunk_hdr_size = 16;
static constexpr u32 thunk_size = 16;
static constexpr u8 filler[] = { 0xff, 0xde };
static constexpr u32 R_COPY = R_ARM_COPY;
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ARM_JUMP_SLOT;
static constexpr u32 R_ABS = R_ARM_ABS32;
static constexpr u32 R_DYNAMIC = R_ARM_ABS32;
static constexpr u32 R_RELATIVE = R_ARM_RELATIVE;
static constexpr u32 R_IRELATIVE = R_ARM_IRELATIVE;
static constexpr u32 R_DTPOFF = R_ARM_TLS_DTPOFF32;
static constexpr u32 R_TPOFF = R_ARM_TLS_TPOFF32;
static constexpr u32 R_DTPMOD = R_ARM_TLS_DTPMOD32;
static constexpr u32 R_TLSDESC = R_ARM_TLS_DESC;
static constexpr u32 R_FUNCALL[] = {
R_ARM_JUMP24, R_ARM_THM_JUMP24, R_ARM_CALL, R_ARM_THM_CALL, R_ARM_PLT32,
};
};
struct RV64LE {
struct RV64 {
static constexpr bool is_64 = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x02, 0x90 };
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_64;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
};
struct RV64LE : RV64 {
static constexpr std::string_view target_name = "riscv64";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_64;
static constexpr u32 R_DYNAMIC = R_RISCV_64;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
};
struct RV64BE {
struct RV64BE : RV64 {
static constexpr std::string_view target_name = "riscv64be";
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
};
struct RV32 {
static constexpr bool is_64 = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
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 };
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_64;
static constexpr u32 R_DYNAMIC = R_RISCV_64;
static constexpr u32 R_ABS = R_RISCV_32;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
};
struct RV32LE {
struct RV32LE : RV32 {
static constexpr std::string_view target_name = "riscv32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_32;
static constexpr u32 R_DYNAMIC = R_RISCV_32;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
};
struct RV32BE {
struct RV32BE : RV32 {
static constexpr std::string_view target_name = "riscv32be";
static constexpr bool is_64 = false;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
static constexpr u32 R_ABS = R_RISCV_32;
static constexpr u32 R_DYNAMIC = R_RISCV_32;
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
};
struct PPC32 {
@@ -2149,66 +2043,60 @@ 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 };
static constexpr u32 R_COPY = R_PPC_COPY;
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_PPC_JMP_SLOT;
static constexpr u32 R_ABS = R_PPC_ADDR32;
static constexpr u32 R_DYNAMIC = R_PPC_ADDR32;
static constexpr u32 R_RELATIVE = R_PPC_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC_DTPREL32;
static constexpr u32 R_TPOFF = R_PPC_TPREL32;
static constexpr u32 R_DTPMOD = R_PPC_DTPMOD32;
static constexpr u32 R_FUNCALL[] = {
R_PPC_REL24, R_PPC_PLTREL24, R_PPC_LOCAL24PC,
};
};
struct PPC64V1 {
static constexpr std::string_view target_name = "ppc64v1";
struct PPC64 {
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_PPC64;
static constexpr u32 plt_hdr_size = 52;
static constexpr u32 R_COPY = R_PPC64_COPY;
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
static constexpr u32 R_ABS = R_PPC64_ADDR64;
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
static constexpr u32 R_FUNCALL[] = { R_PPC64_REL24, R_PPC64_REL24_NOTOC };
};
struct PPC64V1 : PPC64 {
static constexpr std::string_view target_name = "ppc64v1";
static constexpr bool is_le = false;
static constexpr u32 plt_hdr_size = 44;
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 28;
static constexpr u32 R_COPY = R_PPC64_COPY;
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
static constexpr u32 R_ABS = R_PPC64_ADDR64;
static constexpr u32 R_DYNAMIC = R_PPC64_ADDR64;
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
};
struct PPC64V2 {
struct PPC64V2 : PPC64 {
static constexpr std::string_view target_name = "ppc64v2";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_PPC64;
static constexpr u32 plt_hdr_size = 60;
static constexpr u32 plt_hdr_size = 52;
static constexpr u32 plt_size = 4;
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 20;
static constexpr u32 R_COPY = R_PPC64_COPY;
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
static constexpr u32 R_ABS = R_PPC64_ADDR64;
static constexpr u32 R_DYNAMIC = R_PPC64_ADDR64;
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
static constexpr u32 thunk_size = 24;
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f };
};
struct S390X {
@@ -2218,20 +2106,21 @@ struct S390X {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_S390X;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 32;
static constexpr u32 plt_hdr_size = 48;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x07, 0x00 };
static constexpr u32 R_COPY = R_390_COPY;
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_390_JMP_SLOT;
static constexpr u32 R_ABS = R_390_64;
static constexpr u32 R_DYNAMIC = R_390_64;
static constexpr u32 R_RELATIVE = R_390_RELATIVE;
static constexpr u32 R_IRELATIVE = R_390_IRELATIVE;
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
};
struct SPARC64 {
@@ -2244,17 +2133,18 @@ 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 };
static constexpr u32 R_COPY = R_SPARC_COPY;
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_SPARC_JMP_SLOT;
static constexpr u32 R_ABS = R_SPARC_64;
static constexpr u32 R_DYNAMIC = R_SPARC_64;
static constexpr u32 R_RELATIVE = R_SPARC_RELATIVE;
static constexpr u32 R_IRELATIVE = R_SPARC_IRELATIVE;
static constexpr u32 R_DTPOFF = R_SPARC_TLS_DTPOFF64;
static constexpr u32 R_TPOFF = R_SPARC_TLS_TPOFF64;
static constexpr u32 R_DTPMOD = R_SPARC_TLS_DTPMOD64;
static constexpr u32 R_FUNCALL[] = { R_SPARC_WPLT30, R_SPARC_WDISP30 };
};
struct M68K {
@@ -2267,16 +2157,17 @@ 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 };
static constexpr u32 R_COPY = R_68K_COPY;
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_68K_JMP_SLOT;
static constexpr u32 R_ABS = R_68K_32;
static constexpr u32 R_DYNAMIC = R_68K_32;
static constexpr u32 R_RELATIVE = R_68K_RELATIVE;
static constexpr u32 R_DTPOFF = R_68K_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_68K_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_68K_TLS_DTPMOD32;
static constexpr u32 R_FUNCALL[] = { R_68K_PLT32 };
};
struct SH4 {
@@ -2289,16 +2180,17 @@ struct SH4 {
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 12;
static constexpr u8 filler[] = { 0x00, 0x00 };
static constexpr u32 R_COPY = R_SH_COPY;
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_SH_JMP_SLOT;
static constexpr u32 R_ABS = R_SH_DIR32;
static constexpr u32 R_DYNAMIC = R_SH_DIR32;
static constexpr u32 R_RELATIVE = R_SH_RELATIVE;
static constexpr u32 R_DTPOFF = R_SH_TLS_DTPOFF32;
static constexpr u32 R_TPOFF = R_SH_TLS_TPOFF32;
static constexpr u32 R_DTPMOD = R_SH_TLS_DTPMOD32;
static constexpr u32 R_FUNCALL[] = { R_SH_PLT32 };
};
struct ALPHA {
@@ -2311,60 +2203,17 @@ struct ALPHA {
static constexpr u32 plt_hdr_size = 0;
static constexpr u32 plt_size = 0;
static constexpr u32 pltgot_size = 0;
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 };
static constexpr u32 R_COPY = R_ALPHA_COPY;
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
static constexpr u32 R_DYNAMIC = R_ALPHA_REFQUAD;
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
};
struct MIPS64LE {
static constexpr std::string_view target_name = "mips64le";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_MIPS;
static constexpr u32 plt_hdr_size = 0;
static constexpr u32 plt_size = 0;
static constexpr u32 pltgot_size = 0;
static constexpr u32 R_COPY = R_MIPS_COPY;
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT | (R_MIPS_64 << 8);
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
static constexpr u32 R_ABS = R_MIPS_64;
static constexpr u32 R_DYNAMIC = R_MIPS_REL32 | (R_MIPS_64 << 8);
static constexpr u32 R_RELATIVE = R_MIPS_REL32 | (R_MIPS_64 << 8);
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
};
struct MIPS64BE {
static constexpr std::string_view target_name = "mips64";
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_MIPS;
static constexpr u32 plt_hdr_size = 0;
static constexpr u32 plt_size = 0;
static constexpr u32 pltgot_size = 0;
static constexpr u32 R_COPY = R_MIPS_COPY;
static constexpr u32 R_GLOB_DAT = R_MIPS_GLOB_DAT | (R_MIPS_64 << 8);
static constexpr u32 R_JUMP_SLOT = R_MIPS_JUMP_SLOT;
static constexpr u32 R_ABS = R_MIPS_64;
static constexpr u32 R_DYNAMIC = R_MIPS_REL32 | (R_MIPS_64 << 8);
static constexpr u32 R_RELATIVE = R_MIPS_REL32 | (R_MIPS_64 << 8);
static constexpr u32 R_DTPOFF = R_MIPS_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_MIPS_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_MIPS_TLS_DTPMOD64;
static constexpr u32 R_FUNCALL[] = {};
};
struct LOONGARCH64 {
@@ -2378,18 +2227,19 @@ struct LOONGARCH64 {
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 16;
static constexpr u32 thunk_size = 8;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
static constexpr u32 R_ABS = R_LARCH_64;
static constexpr u32 R_DYNAMIC = R_LARCH_64;
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
};
struct LOONGARCH32 {
@@ -2403,18 +2253,19 @@ struct LOONGARCH32 {
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 16;
static constexpr u32 thunk_size = 8;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
static constexpr u32 R_ABS = R_LARCH_32;
static constexpr u32 R_DYNAMIC = R_LARCH_32;
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
};
} // namespace mold::elf
+793
View File
@@ -0,0 +1,793 @@
// This file contains code to read DWARF debug info to create .gdb_index.
//
// .gdb_index is an optional section to speed up GNU debugger. It contains
// two maps: 1) a map from function/variable/type names to compunits, and
// 2) a map from function address ranges to compunits. gdb uses these
// maps to quickly find a compunit given a name or an instruction pointer.
//
// (Terminology: a compilation unit, often abbreviated as compunit or
// CU, is a unit of debug info. An input .debug_info section usually
// contains one compunit, and thus an output .debug_info contains as
// many compunits as the number of input files.)
//
// .gdb_index is not mandatory. All the information in .gdb_index is
// also in other debug info sections. You can actually create an
// executable without .gdb_index and later add it using the
// `gdb-add-index` post-processing tool that comes with gdb.
//
// Post-relocated debug section contents are needed to create a
// .gdb_index. Therefore, we create it after relocating all the other
// sections. The size of the section is also hard to estimate before
// applying relocations to debug info sections, so a .gdb_index is
// placed at the very end of the output file, even after the section
// header.
//
// The mapping from names to compunits is 1:n while the mapping from
// address ranges to compunits is 1:1. That is, two object files may
// define the same type name, while there should be no two functions
// that overlap with each other in memory.
//
// .gdb_index contains an on-disk hash table for names, so gdb can
// lookup names without loading all strings into memory and construct an
// in-memory hash table.
//
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
// sections. These sections are created if `-ggnu-pubnames` is given.
// Besides names, these sections contain attributes for each name so
// that gdb can distinguish type names from function names, for example.
//
// A compunit contains one or more function address ranges. If an
// object file is compiled without -ffunction-sections, it contains
// only one .text section and therefore contains a single address range.
// Such range is typically stored directly to the compunit.
//
// If an object file is compiled with -ffunction-sections, it contains
// more than one .text section, and it has as many address ranges as
// the number of .text sections. Such discontiguous address ranges are
// stored to .debug_ranges in DWARF 2/3/4 and .debug_rnglists/.debug_addr
// in DWARF 5.
//
// .debug_info section contains DWARF debug info. Although we don't need
// to parse the whole .debug_info section to read address ranges, we
// have to do a little bit. DWARF is complicated and often handled using
// a library such as libdwarf. But we don't use any library because we
// don't want to add an extra run-time dependency just for --gdb-index.
//
// This page explains the format of .gdb_index:
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
#include "mold.h"
#include <tbb/parallel_for_each.h>
#include <tbb/parallel_sort.h>
namespace mold::elf {
enum DwarfKind { DWARF2_32, DWARF5_32, DWARF2_64, DWARF5_64 };
template <typename E>
struct CuHdrDwarf2_32 {
U32<E> size;
U16<E> version;
U32<E> abbrev_offset;
u8 address_size;
};
template <typename E>
struct CuHdrDwarf5_32 {
U32<E> size;
U16<E> version;
u8 unit_type;
u8 address_size;
U32<E> abbrev_offset;
};
template <typename E>
struct CuHdrDwarf2_64 {
U32<E> magic;
U64<E> size;
U16<E> version;
U64<E> abbrev_offset;
u8 address_size;
};
template <typename E>
struct CuHdrDwarf5_64 {
U32<E> magic;
U64<E> size;
U16<E> version;
u8 unit_type;
u8 address_size;
U64<E> abbrev_offset;
};
template <typename E>
struct PubnamesHdr32 {
U32<E> size;
U16<E> version;
U32<E> debug_info_offset;
U32<E> debug_info_size;
};
template <typename E>
struct PubnamesHdr64 {
U32<E> magic;
U64<E> size;
U16<E> version;
U64<E> debug_info_offset;
U64<E> debug_info_size;
};
struct SectionHeader {
ul32 version = 7;
ul32 cu_list_offset = 0;
ul32 cu_types_offset = 0;
ul32 ranges_offset = 0;
ul32 symtab_offset = 0;
ul32 const_pool_offset = 0;
};
struct NameType {
bool operator==(const NameType &) const = default;
bool operator<(const NameType &other) const {
return std::tuple(hash, type, name) <
std::tuple(other.hash, other.type, other.name);
}
std::string_view name;
u32 hash;
u8 type;
};
struct MapValue {
u32 hash = 0;
Atomic<u32> count = 0;
u32 name_offset = 0;
u32 type_offset = 0;
};
struct Compunit {
DwarfKind kind;
i64 offset;
i64 size;
std::vector<std::pair<u64, u64>> ranges;
std::vector<NameType> nametypes;
std::vector<MapValue *> entries;
};
// The hash function for .gdb_index.
static u32 gdb_hash(std::string_view name) {
u32 h = 0;
for (u8 c : name) {
if ('A' <= c && c <= 'Z')
c = 'a' + c - 'A';
h = h * 67 + c - 113;
}
return h;
}
template <typename E>
static DwarfKind get_dwarf_kind(Context<E> &ctx, u8 *p) {
if (*(U32<E> *)p == 0xffff'ffff) {
CuHdrDwarf2_64<E> &hdr = *(CuHdrDwarf2_64<E> *)p;
if (hdr.version > 5)
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
<< " is not supported";
return (hdr.version == 5) ? DWARF5_64 : DWARF2_64;
}
CuHdrDwarf2_32<E> &hdr = *(CuHdrDwarf2_32<E> *)p;
if (hdr.version > 5)
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
<< " is not supported";
return (hdr.version == 5) ? DWARF5_32 : DWARF2_32;
}
template <typename E, typename CuHdr>
u8 *find_cu_abbrev(Context<E> &ctx, u8 **p, const CuHdr &hdr) {
if (hdr.address_size != sizeof(Word<E>))
Fatal(ctx) << "--gdb-index: unsupported address size " << hdr.address_size;
if constexpr (requires { hdr.unit_type; }) {
switch (hdr.unit_type) {
case DW_UT_compile:
case DW_UT_partial:
break;
case DW_UT_skeleton:
case DW_UT_split_compile:
*p += 8;
break;
default:
Fatal(ctx) << "--gdb-index: unknown unit type: 0x"
<< std::hex << hdr.unit_type;
}
}
i64 abbrev_code = read_uleb(p);
// Find a .debug_abbrev record corresponding to the .debug_info record.
// We assume the .debug_info record at a given offset is of
// DW_TAG_compile_unit which describes a compunit.
u8 *abbrev = &ctx.debug_abbrev[0] + hdr.abbrev_offset;
for (;;) {
u32 code = read_uleb(&abbrev);
if (code == 0)
Fatal(ctx) << "--gdb-index: .debug_abbrev does not contain"
<< " a record for the first .debug_info record";
if (code == abbrev_code) {
// Found a record
u64 abbrev_tag = read_uleb(&abbrev);
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
Fatal(ctx) << "--gdb-index: the first entry's tag is not"
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
<< std::hex << abbrev_tag;
break;
}
// Skip an uninteresting record
read_uleb(&abbrev); // tag
abbrev++; // has_children byte
for (;;) {
u64 name = read_uleb(&abbrev);
u64 form = read_uleb(&abbrev);
if (name == 0 && form == 0)
break;
if (form == DW_FORM_implicit_const)
read_uleb(&abbrev);
}
}
abbrev++; // skip has_children byte
return abbrev;
}
// .debug_info contains variable-length fields.
// This function reads one scalar value from a given location.
template <typename E, typename Offset>
u64 read_scalar(Context<E> &ctx, u8 **p, u64 form) {
switch (form) {
case DW_FORM_flag_present:
return 0;
case DW_FORM_data1:
case DW_FORM_flag:
case DW_FORM_strx1:
case DW_FORM_addrx1:
case DW_FORM_ref1:
return *(*p)++;
case DW_FORM_data2:
case DW_FORM_strx2:
case DW_FORM_addrx2:
case DW_FORM_ref2: {
u64 val = *(U16<E> *)*p;
*p += 2;
return val;
}
case DW_FORM_strx3:
case DW_FORM_addrx3: {
u64 val = *(U24<E> *)*p;
*p += 3;
return val;
}
case DW_FORM_data4:
case DW_FORM_strx4:
case DW_FORM_addrx4:
case DW_FORM_ref4: {
u64 val = *(U32<E> *)*p;
*p += 4;
return val;
}
case DW_FORM_data8:
case DW_FORM_ref8: {
u64 val = *(U64<E> *)*p;
*p += 8;
return val;
}
case DW_FORM_strp:
case DW_FORM_sec_offset:
case DW_FORM_line_strp: {
u64 val = *(Offset *)*p;
*p += sizeof(Offset);
return val;
}
case DW_FORM_addr:
case DW_FORM_ref_addr: {
u64 val = *(Word<E> *)*p;
*p += sizeof(Word<E>);
return val;
}
case DW_FORM_strx:
case DW_FORM_addrx:
case DW_FORM_udata:
case DW_FORM_ref_udata:
case DW_FORM_loclistx:
case DW_FORM_rnglistx:
return read_uleb(p);
case DW_FORM_string:
*p += strlen((char *)*p) + 1;
return 0;
default:
Fatal(ctx) << "--gdb-index: unhandled debug info form: 0x"
<< std::hex << form;
}
}
// Read a range list from .debug_ranges starting at the given offset.
template <typename E>
static std::vector<std::pair<u64, u64>>
read_debug_range(Word<E> *range, u64 base) {
std::vector<std::pair<u64, u64>> vec;
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
if (range[i] + 1 == 0)
base = range[i + 1];
else
vec.emplace_back(range[i] + base, range[i + 1] + base);
}
return vec;
}
// Read a range list from .debug_rnglists starting at the given offset.
template <typename E>
static void
read_rnglist_range(std::vector<std::pair<u64, u64>> &vec, u8 *p,
Word<E> *addrx, u64 base) {
for (;;) {
switch (*p++) {
case DW_RLE_end_of_list:
return;
case DW_RLE_base_addressx:
base = addrx[read_uleb(&p)];
break;
case DW_RLE_startx_endx: {
u64 val1 = read_uleb(&p);
u64 val2 = read_uleb(&p);
vec.emplace_back(addrx[val1], addrx[val2]);
break;
}
case DW_RLE_startx_length: {
u64 val1 = read_uleb(&p);
u64 val2 = read_uleb(&p);
vec.emplace_back(addrx[val1], addrx[val1] + val2);
break;
}
case DW_RLE_offset_pair: {
u64 val1 = read_uleb(&p);
u64 val2 = read_uleb(&p);
// If the base is 0, this address range is for an eliminated
// section. We only emit it if it's alive.
if (base)
vec.emplace_back(base + val1, base + val2);
break;
}
case DW_RLE_base_address:
base = *(Word<E> *)p;
p += sizeof(Word<E>);
break;
case DW_RLE_start_end: {
u64 val1 = ((Word<E> *)p)[0];
u64 val2 = ((Word<E> *)p)[1];
p += sizeof(Word<E>) * 2;
vec.emplace_back(val1, val2);
break;
}
case DW_RLE_start_length: {
u64 val1 = *(Word<E> *)p;
p += sizeof(Word<E>);
u64 val2 = read_uleb(&p);
vec.emplace_back(val1, val1 + val2);
break;
}
}
}
}
// Returns a list of address ranges explained by a compunit at the
// `offset` in an output .debug_info section.
//
// .debug_info contains DWARF debug info records, so this function
// parses DWARF. If a designated compunit contains multiple ranges, the
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
// Otherwise, a range is read directly from .debug_info (or possibly
// from .debug_addr for DWARF5).
template <typename E, typename CuHdr>
static std::vector<std::pair<u64, u64>>
read_address_ranges(Context<E> &ctx, const Compunit &cu) {
// Read .debug_info to find the record at a given offset.
u8 *p = &ctx.debug_info[0] + cu.offset;
CuHdr &hdr = *(CuHdr *)p;
p += sizeof(hdr);
u8 *abbrev = find_cu_abbrev(ctx, &p, hdr);
// Now, read debug info records.
struct Record {
u64 form = 0;
u64 value = 0;
};
using Offset = decltype(hdr.size);
Record low_pc;
Record high_pc;
Record ranges;
u64 rnglists_base = -1;
Word<E> *addrx = nullptr;
// Read all interesting debug records.
for (;;) {
u64 name = read_uleb(&abbrev);
u64 form = read_uleb(&abbrev);
if (name == 0 && form == 0)
break;
u64 val = read_scalar<E, Offset>(ctx, &p, form);
switch (name) {
case DW_AT_low_pc:
low_pc = {form, val};
break;
case DW_AT_high_pc:
high_pc = {form, val};
break;
case DW_AT_rnglists_base:
rnglists_base = val;
break;
case DW_AT_addr_base:
addrx = (Word<E> *)(&ctx.debug_addr[0] + val);
break;
case DW_AT_ranges:
ranges = {form, val};
break;
}
}
// Handle non-contiguous address ranges.
if (ranges.form) {
if (hdr.version <= 4) {
Word<E> *p = (Word<E> *)(&ctx.debug_ranges[0] + ranges.value);
return read_debug_range<E>(p, low_pc.value);
}
assert(hdr.version == 5);
std::vector<std::pair<u64, u64>> vec;
u8 *buf = &ctx.debug_rnglists[0];
if (ranges.form == DW_FORM_sec_offset) {
read_rnglist_range<E>(vec, buf + ranges.value, addrx, low_pc.value);
} else {
if (rnglists_base == -1)
Fatal(ctx) << "--gdb-index: missing DW_AT_rnglists_base";
u8 *base = buf + rnglists_base;
i64 num_offsets = *(U32<E> *)(base - 4);
Offset *offsets = (Offset *)base;
for (i64 i = 0; i < num_offsets; i++)
read_rnglist_range<E>(vec, base + offsets[i], addrx, low_pc.value);
}
return vec;
}
// Handle a contiguous address range.
if (low_pc.form && high_pc.form) {
u64 lo;
switch (low_pc.form) {
case DW_FORM_addr:
lo = low_pc.value;
break;
case DW_FORM_addrx:
case DW_FORM_addrx1:
case DW_FORM_addrx2:
case DW_FORM_addrx4:
lo = addrx[low_pc.value];
break;
default:
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_low_pc: 0x"
<< std::hex << high_pc.form;
}
switch (high_pc.form) {
case DW_FORM_addr:
return {{lo, high_pc.value}};
case DW_FORM_addrx:
case DW_FORM_addrx1:
case DW_FORM_addrx2:
case DW_FORM_addrx4:
return {{lo, addrx[high_pc.value]}};
case DW_FORM_udata:
case DW_FORM_data1:
case DW_FORM_data2:
case DW_FORM_data4:
case DW_FORM_data8:
return {{lo, lo + high_pc.value}};
default:
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_high_pc: 0x"
<< std::hex << high_pc.form;
}
}
return {};
}
template <typename E, typename PubnamesHdr>
static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
std::vector<Compunit> &cus, ObjectFile<E> &file) {
using Offset = decltype(hdr.size);
auto get_cu = [&](i64 offset) {
for (i64 i = 0; i < cus.size(); i++)
if (cus[i].offset == offset)
return &cus[i];
Fatal(ctx) << file << ": corrupted debug_info_offset";
};
Compunit *cu = get_cu(file.debug_info->offset + hdr.debug_info_offset);
i64 size = hdr.size + offsetof(PubnamesHdr, size) + sizeof(hdr.size);
u8 *p = (u8 *)&hdr + sizeof(hdr);
u8 *end = (u8 *)&hdr + size;
while (p < end) {
if (*(Offset *)p == 0)
break;
p += sizeof(Offset);
u8 type = *p++;
std::string_view name = (char *)p;
p += name.size() + 1;
cu->nametypes.push_back({name, gdb_hash(name), type});
}
return size;
}
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
// null-terminated string) tuples.
//
// The 4-byte offset is an offset into .debug_info that contains details
// about the name. The 1-byte type is a type of the corresponding name
// (e.g. function, variable or datatype). The string is a name of a
// function, a variable or a type.
template <typename E>
static void read_pubnames(Context<E> &ctx, std::vector<Compunit> &cus,
ObjectFile<E> &file) {
for (InputSection<E> *isec : { file.debug_pubnames, file.debug_pubtypes }) {
if (!isec)
continue;
isec->uncompress(ctx);
if (isec->contents.empty())
continue;
u8 *p = (u8*)&isec->contents[0];
u8 *end = p + isec->contents.size();
while (p < end) {
if (*(U32<E> *)p == 0xffff'ffff)
p += read_pubnames_cu(ctx, *(PubnamesHdr64<E> *)p, cus, file);
else
p += read_pubnames_cu(ctx, *(PubnamesHdr32<E> *)p, cus, file);
}
};
}
template <typename E>
static std::vector<Compunit> read_compunits(Context<E> &ctx) {
std::vector<Compunit> cus;
// Read compunits from the output .debug_info section.
u8 *begin = &ctx.debug_info[0];
u8 *end = begin + ctx.debug_info.size();
for (u8 *p = begin; p < end;) {
DwarfKind kind = get_dwarf_kind(ctx, p);
i64 size;
if (kind == DWARF2_32 || kind == DWARF5_32)
size = ((CuHdrDwarf2_32<E> *)p)->size + 4;
else
size = ((CuHdrDwarf2_64<E> *)p)->size + 12;
cus.push_back(Compunit{kind, p - begin, size});
p += size;
}
// Read address ranges for each compunit.
tbb::parallel_for_each(cus, [&](Compunit &cu) {
switch (cu.kind) {
case DWARF2_32:
cu.ranges = read_address_ranges<E, CuHdrDwarf2_32<E>>(ctx, cu);
break;
case DWARF5_32:
cu.ranges = read_address_ranges<E, CuHdrDwarf5_32<E>>(ctx, cu);
break;
case DWARF2_64:
cu.ranges = read_address_ranges<E, CuHdrDwarf2_64<E>>(ctx, cu);
break;
case DWARF5_64:
cu.ranges = read_address_ranges<E, CuHdrDwarf5_64<E>>(ctx, cu);
break;
}
// Remove empty ranges
std::erase_if(cu.ranges, [](std::pair<u64, u64> p) {
return p.first == 0 || p.first == p.second;
});
});
// Read symbols from .debug_gnu_pubnames and .debug_gnu_pubtypes.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
read_pubnames(ctx, cus, *file);
});
// Uniquify elements because GCC 11 seems to emit one record for each
// comdat group which results in having a lot of duplicate records.
tbb::parallel_for_each(cus, [&](Compunit &cu) {
sort(cu.nametypes);
remove_duplicates(cu.nametypes);
});
return cus;
}
template <typename E>
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
if (chunk->is_compressed)
return chunk->uncompressed_data;
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
}
template <typename E>
void write_gdb_index(Context<E> &ctx) {
Timer t(ctx, "write_gdb_index");
// Find debug info sections
for (Chunk<E> *chunk : ctx.chunks) {
std::string_view name = chunk->name;
if (name == ".debug_info")
ctx.debug_info = get_buffer(ctx, chunk);
if (name == ".debug_abbrev")
ctx.debug_abbrev = get_buffer(ctx, chunk);
if (name == ".debug_ranges")
ctx.debug_ranges = get_buffer(ctx, chunk);
if (name == ".debug_addr")
ctx.debug_addr = get_buffer(ctx, chunk);
if (name == ".debug_rnglists")
ctx.debug_rnglists = get_buffer(ctx, chunk);
}
if (ctx.debug_info.empty())
return;
// Read debug info
std::vector<Compunit> cus = read_compunits(ctx);
// Uniquify symbols
HyperLogLog estimator;
tbb::parallel_for_each(cus, [&](Compunit &cu) {
HyperLogLog e;
for (NameType &nt : cu.nametypes)
e.insert(nt.hash);
estimator.merge(e);
});
ConcurrentMap<MapValue> map(estimator.get_cardinality() * 3 / 2);
tbb::parallel_for_each(cus, [&](Compunit &cu) {
cu.entries.reserve(cu.nametypes.size());
for (NameType &nt : cu.nametypes) {
MapValue *ent;
bool inserted;
std::tie(ent, inserted) = map.insert(nt.name, nt.hash, MapValue{nt.hash});
ent->count++;
cu.entries.push_back(ent);
}
});
// Sort symbols for build reproducibility
using Entry = typename decltype(map)::Entry;
std::vector<Entry *> entries = map.get_sorted_entries_all();
// Compute sizes of each components
SectionHeader hdr;
hdr.cu_list_offset = sizeof(hdr);
hdr.cu_types_offset = hdr.cu_list_offset + cus.size() * 16;
hdr.ranges_offset = hdr.cu_types_offset;
hdr.symtab_offset = hdr.ranges_offset;
for (Compunit &cu : cus)
hdr.symtab_offset += cu.ranges.size() * 20;
i64 ht_size = bit_ceil(entries.size() * 5 / 4 + 1);
hdr.const_pool_offset = hdr.symtab_offset + ht_size * 8;
i64 offset = 0;
for (Entry *ent : entries) {
ent->value.type_offset = offset;
offset += ent->value.count * 4 + 4;
}
for (Entry *ent : entries) {
ent->value.name_offset = offset;
offset += ent->keylen + 1;
}
i64 bufsize = hdr.const_pool_offset + offset;
// Allocate an output buffer
ctx.output_file->buf2.resize(bufsize);
u8 *buf = ctx.output_file->buf2.data();
// Write a section header
memcpy(buf, &hdr, sizeof(hdr));
// Write a CU list
u8 *p = buf + sizeof(hdr);
for (Compunit &cu : cus) {
*(ul64 *)p = cu.offset;
*(ul64 *)(p + 8) = cu.size;
p += 16;
}
// Write address areas
for (i64 i = 0; i < cus.size(); i++) {
for (std::pair<u64, u64> range : cus[i].ranges) {
*(ul64 *)p = range.first;
*(ul64 *)(p + 8) = range.second;
*(ul32 *)(p + 16) = i;
p += 20;
}
}
// Write a symbol table
u32 mask = ht_size - 1;
ul32 *ht = (ul32 *)(buf + hdr.symtab_offset);
for (Entry *ent : entries) {
u32 hash = ent->value.hash;
u32 step = ((hash * 17) & mask) | 1;
u32 j = hash & mask;
while (ht[j * 2] || ht[j * 2 + 1])
j = (j + step) & mask;
ht[j * 2] = ent->value.name_offset;
ht[j * 2 + 1] = ent->value.type_offset;
}
// Write types
for (i64 i = 0; i < cus.size(); i++) {
Compunit &cu = cus[i];
u8 *base = buf + hdr.const_pool_offset;
for (i64 j = 0; j < cu.nametypes.size(); j++) {
ul32 *p = (ul32 *)(base + cu.entries[j]->type_offset);
i64 idx = ++p[0];
p[idx] = (cu.nametypes[j].type << 24) | i;
}
}
// Write names
tbb::parallel_for_each(entries, [&](Entry *ent) {
memcpy(buf + hdr.const_pool_offset + ent->value.name_offset,
ent->key, ent->keylen);
});
// Update the section size and rewrite the section header
if (ctx.shdr) {
ctx.gdb_index->shdr.sh_size = bufsize;
ctx.shdr->copy_buf(ctx);
}
}
using E = MOLD_TARGET;
template void write_gdb_index(Context<E> &);
} // namespace mold::elf
+30 -28
View File
@@ -65,7 +65,7 @@
// conditions.
#include "mold.h"
#include "../common/sha.h"
#include "blake3.h"
#include <array>
#include <cstdio>
@@ -81,7 +81,7 @@ static constexpr int64_t HASH_SIZE = 16;
typedef std::array<uint8_t, HASH_SIZE> Digest;
namespace std {
template<> struct hash<Digest> {
template <> struct hash<Digest> {
size_t operator()(const Digest &k) const {
return *(int64_t *)&k[0];
}
@@ -115,26 +115,26 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
const ElfShdr<E> &shdr = isec.shdr();
std::string_view name = isec.name();
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
ctx.arg.ignore_data_address_equality;
bool is_relro = (name == ".data.rel.ro" ||
name.starts_with(".data.rel.ro."));
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
bool is_bss = (shdr.sh_type == SHT_NOBITS);
bool is_empty = (shdr.sh_size == 0);
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
bool is_enumerable = is_c_identifier(name);
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
return false;
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
if (shdr.sh_flags & SHF_EXECINSTR) {
return (ctx.arg.icf_all || !isec.address_taken) &&
name != ".init" && name != ".fini";
} else {
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
bool is_relro = isec.output_section && isec.output_section->is_relro;
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
(is_readonly || is_relro);
}
}
static Digest digest_final(SHA256Hash &sha) {
u8 buf[SHA256_SIZE];
sha.finish(buf);
static Digest digest_final(blake3_hasher *hasher) {
assert(HASH_SIZE <= BLAKE3_OUT_LEN);
u8 buf[BLAKE3_OUT_LEN];
blake3_hasher_finalize(hasher, buf, BLAKE3_OUT_LEN);
Digest digest;
memcpy(digest.data(), buf, HASH_SIZE);
@@ -234,15 +234,16 @@ static void merge_leaf_nodes(Context<E> &ctx) {
template <typename E>
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
SHA256Hash sha;
blake3_hasher hasher;
blake3_hasher_init(&hasher);
auto hash = [&](auto val) {
sha.update((u8 *)&val, sizeof(val));
blake3_hasher_update(&hasher, (u8 *)&val, sizeof(val));
};
auto hash_string = [&](std::string_view str) {
hash(str.size());
sha.update((u8 *)str.data(), str.size());
blake3_hasher_update(&hasher, (u8 *)str.data(), str.size());
};
auto hash_symbol = [&](Symbol<E> &sym) {
@@ -298,7 +299,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
hash_symbol(*isec.file.symbols[rel.r_sym]);
}
return digest_final(sha);
return digest_final(&hasher);
}
template <typename E>
@@ -411,16 +412,17 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
if (converged.get(i))
return;
SHA256Hash sha;
sha.update(digests[2][i].data(), HASH_SIZE);
blake3_hasher hasher;
blake3_hasher_init(&hasher);
blake3_hasher_update(&hasher, digests[2][i].data(), HASH_SIZE);
i64 begin = edge_indices[i];
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
for (i64 j : edges.subspan(begin, end - begin))
sha.update(digests[slot][j].data(), HASH_SIZE);
blake3_hasher_update(&hasher, digests[slot][j].data(), HASH_SIZE);
digests[!slot][i] = digest_final(sha);
digests[!slot][i] = digest_final(&hasher);
if (digests[slot][i] == digests[!slot][i]) {
// This node has converged. Skip further iterations as it will
@@ -563,7 +565,7 @@ void icf_sections(Context<E> &ctx) {
}
}
// Group sections by SHA digest.
// Group sections by BLAKE3 digest.
{
Timer t(ctx, "group");
+43 -138
View File
@@ -104,7 +104,7 @@ static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
template <typename E>
void
ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
ObjectFile<E>::parse_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
std::string_view data = this->get_string(ctx, shdr);
while (!data.empty()) {
@@ -140,19 +140,11 @@ ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
}
}
static inline std::string_view read_string(std::string_view &str) {
i64 pos = str.find_first_of('\0');
std::string_view val = str.substr(0, pos);
str = str.substr(pos + 1);
return val;
}
// <format-version>
// [ <section-length> "vendor-name" <file-tag> <size> <attribute>*]+ ]*
template <typename E>
static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
std::string_view data) {
const char *begin = data.data();
if (data.empty())
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
@@ -183,9 +175,12 @@ static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
case ELF_TAG_RISCV_STACK_ALIGN:
file.extra.stack_align = read_uleb(&p);
break;
case ELF_TAG_RISCV_ARCH:
file.extra.arch = read_string(p);
case ELF_TAG_RISCV_ARCH: {
i64 pos = p.find_first_of('\0');
file.extra.arch = p.substr(0, pos);
p = p.substr(pos + 1);
break;
}
case ELF_TAG_RISCV_UNALIGNED_ACCESS:
file.extra.unaligned_access = read_uleb(&p);
break;
@@ -196,26 +191,6 @@ static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
}
}
template <typename E>
static u64 read_mips_gp0(Context<E> &ctx, InputSection<E> &isec) {
std::string_view data = isec.contents;
while (!data.empty()) {
if (data.size() < sizeof(MipsOptions<E>))
Fatal(ctx) << isec << ": corrupted .MIPS.options section";
MipsOptions<E> *opt = (MipsOptions<E> *)data.data();
if (opt->kind == ODK_REGINFO) {
if (data.size() < sizeof(MipsOptions<E>) + sizeof(MipsRegInfo<E>))
Fatal(ctx) << isec << ": corrupted .MIPS.options section";
MipsRegInfo<E> *info = (MipsRegInfo<E> *)(opt + 1);
return info->ri_gp_value;
}
data = data.substr(opt->size);
}
return 0;
}
template <typename E>
void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// Read sections
@@ -274,15 +249,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
break;
}
case SHT_REL:
if constexpr (E::is_rela)
Fatal(ctx) << *this << ": REL-type relocation table is not supported"
<< " for this target";
break;
case SHT_RELA:
if constexpr (!E::is_rela)
Fatal(ctx) << *this <<": RELA-type relocation table is not supported"
<< " for this target";
break;
case SHT_SYMTAB:
case SHT_SYMTAB_SHNDX:
case SHT_STRTAB:
@@ -295,11 +262,8 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// area in GNU linkers. We ignore that section because silently
// making the stack area executable is too dangerous. Tell our
// users about the difference if that matters.
//
// MIPS object files don't contain .note.GNU-stack for some reason,
// so ignore this error on MIPS.
if (name == ".note.GNU-stack" && !ctx.arg.relocatable) {
if ((shdr.sh_flags & SHF_EXECINSTR) && !is_mips<E>) {
if (shdr.sh_flags & SHF_EXECINSTR) {
if (!ctx.arg.z_execstack && !ctx.arg.z_execstack_if_needed)
Warn(ctx) << *this << ": this file may cause a segmentation"
" fault because it requires an executable stack. See"
@@ -311,7 +275,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
}
if (name == ".note.gnu.property") {
read_note_gnu_property(ctx, shdr);
parse_note_gnu_property(ctx, shdr);
continue;
}
@@ -342,7 +306,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
if (ctx.arg.oformat_binary && !(shdr.sh_flags & SHF_ALLOC))
continue;
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
// Save .llvm_addrsig for --icf=safe.
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
@@ -350,30 +314,19 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
continue;
}
if (name == ".eh_frame")
eh_frame_section = this->sections[i].get();
if constexpr (is_ppc32<E>)
if (name == ".got2")
extra.got2 = this->sections[i].get();
if constexpr (is_mips<E>) {
if (name == ".MIPS.abiflags") {
extra.abi_flags = std::move(this->sections[i]);
continue;
}
if (name == ".MIPS.options")
extra.gp0 = read_mips_gp0(ctx, *this->sections[i]);
}
// Save debug sections for --gdb-index.
if (ctx.arg.gdb_index) {
InputSection<E> *isec = this->sections[i].get();
if (name == ".debug_info")
debug_info = isec;
if (name == ".debug_ranges")
debug_ranges = isec;
if (name == ".debug_rnglists")
debug_rnglists = isec;
// If --gdb-index is given, contents of .debug_gnu_pubnames and
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
@@ -423,16 +376,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
}
}
template <typename E>
void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
for (i64 i = 0; i < sections.size(); i++) {
std::unique_ptr<InputSection<E>> &isec = sections[i];
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
read_ehframe(ctx, *isec);
}
}
}
// .eh_frame contains data records explaining how to handle exceptions.
// When an exception is thrown, the runtime searches a record from
// .eh_frame with the current program counter as a key. A record that
@@ -463,7 +406,11 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
//
// This function parses an input .eh_frame section.
template <typename E>
void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
if (!eh_frame_section)
return;
InputSection<E> &isec = *eh_frame_section;
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
i64 cies_begin = cies.size();
i64 fdes_begin = fdes.size();
@@ -690,9 +637,16 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
if (!(shdr.sh_flags & SHF_MERGE))
return nullptr;
i64 entsize = shdr.sh_entsize;
if (entsize == 0)
entsize = (shdr.sh_flags & SHF_STRINGS) ? 1 : (int)shdr.sh_addralign;
if (entsize == 0)
return nullptr;
std::unique_ptr<MergeableSection<E>> rec(new MergeableSection<E>);
rec->parent = MergedSection<E>::get_instance(ctx, sec.name(), shdr.sh_type,
shdr.sh_flags);
shdr.sh_flags, entsize);
rec->p2align = sec.p2align;
if (sec.sh_size == 0)
@@ -703,18 +657,10 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
std::string_view data = sec.contents;
const char *begin = data.data();
u64 entsize = shdr.sh_entsize;
HyperLogLog estimator;
// Split sections
if (shdr.sh_flags & SHF_STRINGS) {
if (entsize == 0) {
// GHC (Glasgow Haskell Compiler) sometimes creates a mergeable
// string section with entsize of 0 instead of 1, though such
// entsize is technically wrong. This is a workaround for the issue.
entsize = 1;
}
while (!data.empty()) {
size_t end = find_null(data, entsize);
if (end == data.npos)
@@ -731,11 +677,6 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
estimator.insert(hash);
}
} else {
// OCaml compiler seems to create a mergeable non-string section with
// entisze of 0. Such section is malformed. We do not split such section.
if (entsize == 0)
return nullptr;
if (data.size() % entsize)
Fatal(ctx) << sec << ": section size is not multiple of sh_entsize";
@@ -907,33 +848,6 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
this->symbols.push_back(&sym);
}
template <typename E>
void ObjectFile<E>::mark_addrsig(Context<E> &ctx) {
// Parse a .llvm_addrsig section.
if (llvm_addrsig) {
u8 *cur = (u8 *)llvm_addrsig->contents.data();
u8 *end = cur + llvm_addrsig->contents.size();
while (cur != end) {
Symbol<E> &sym = *this->symbols[read_uleb(&cur)];
if (sym.file == this)
if (InputSection<E> *isec = sym.get_input_section())
isec->address_significant = true;
}
}
// We treat a symbol's address as significant if
//
// 1. we have no address significance information for the symbol, or
// 2. the symbol can be referenced from the outside in an address-
// significant manner.
for (Symbol<E> *sym : this->symbols)
if (sym->file == this)
if (InputSection<E> *isec = sym->get_input_section())
if (!llvm_addrsig || sym->is_exported)
isec->address_significant = true;
}
template <typename E>
void ObjectFile<E>::parse(Context<E> &ctx) {
sections.resize(this->elf_sections.size());
@@ -953,7 +867,7 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
initialize_sections(ctx);
initialize_symbols(ctx);
sort_relocations(ctx);
initialize_ehframe_sections(ctx);
parse_ehframe(ctx);
}
// Symbols with higher priorities overwrites symbols with lower priorities.
@@ -1109,11 +1023,10 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
for (ElfRel<E> &rel : cie.get_rels()) {
Symbol<E> &sym = *this->symbols[rel.r_sym];
if constexpr (!is_mips<E>)
if (ctx.arg.pic && rel.r_type == E::R_ABS)
Error(ctx) << *this << ": relocation " << rel << " in .eh_frame can"
<< " not be used when making a position-independent output;"
<< " recompile with -fPIE or -fPIC";
if (ctx.arg.pic && rel.r_type == E::R_ABS)
Error(ctx) << *this << ": relocation " << rel << " in .eh_frame can"
<< " not be used when making a position-independent output;"
<< " recompile with -fPIE or -fPIC";
if (sym.is_imported) {
if (sym.get_type() != STT_FUNC)
@@ -1166,13 +1079,9 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
ElfShdr<E> &shdr = elf_sections2.back();
memset(&shdr, 0, sizeof(shdr));
std::string_view name;
if (sym.get_type() == STT_TLS) {
name = ".tls_common";
shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_TLS;
} else {
name = ".common";
shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
}
@@ -1182,7 +1091,7 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
std::unique_ptr<InputSection<E>> isec =
std::make_unique<InputSection<E>>(ctx, *this, name, idx);
std::make_unique<InputSection<E>>(ctx, *this, idx);
sym.file = this;
sym.set_input_section(isec.get());
@@ -1226,9 +1135,6 @@ void ObjectFile<E>::compute_symtab_size(Context<E> &ctx) {
this->output_sym_indices.resize(this->elf_syms.size(), -1);
auto is_alive = [&](Symbol<E> &sym) -> bool {
if (!ctx.arg.gc_sections)
return true;
if (SectionFragment<E> *frag = sym.get_frag())
return frag->is_alive;
if (InputSection<E> *isec = sym.get_input_section())
@@ -1274,30 +1180,29 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
i64 strtab_off = this->strtab_offset;
auto write_sym = [&](Symbol<E> &sym, i64 &symtab_idx) {
auto write_sym = [&](Symbol<E> &sym, i64 idx) {
U32<E> *xindex = nullptr;
if (ctx.symtab_shndx)
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + symtab_idx;
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + idx;
symtab_base[symtab_idx++] = to_output_esym(ctx, sym, strtab_off, xindex);
symtab_base[idx] = to_output_esym(ctx, sym, strtab_off, xindex);
strtab_off += write_string(strtab_base + strtab_off, sym.name());
};
i64 local_symtab_idx = this->local_symtab_idx;
i64 global_symtab_idx = this->global_symtab_idx;
for (i64 i = 1; i < this->first_global; i++) {
Symbol<E> &sym = *this->symbols[i];
if (sym.write_to_symtab)
write_sym(sym, local_symtab_idx);
}
for (i64 i = 1; i < this->first_global; i++)
if (Symbol<E> &sym = *this->symbols[i]; sym.write_to_symtab)
write_sym(sym, local_symtab_idx++);
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
Symbol<E> &sym = *this->symbols[i];
if (sym.file == this && sym.write_to_symtab) {
if (sym.is_local(ctx))
write_sym(sym, local_symtab_idx);
write_sym(sym, local_symtab_idx++);
else
write_sym(sym, global_symtab_idx);
write_sym(sym, global_symtab_idx++);
}
}
}
@@ -1328,7 +1233,6 @@ SharedFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
template <typename E>
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
: InputFile<E>(ctx, mf) {
this->is_needed = ctx.as_needed;
this->is_alive = !ctx.as_needed;
}
@@ -1483,7 +1387,7 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
if (sym.is_traced)
print_trace_symbol(ctx, *this, esym, sym);
if (esym.is_undef() && !esym.is_weak() && sym.file &&
if (esym.is_undef() && !esym.is_weak() && sym.file && !sym.file->is_dso &&
!sym.file->is_alive.test_and_set()) {
feeder(sym.file);
@@ -1540,7 +1444,8 @@ bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
u64 val = sym->esym().st_value;
for (ElfPhdr<E> &phdr : this->get_phdrs())
if (phdr.p_type == PT_LOAD && !(phdr.p_flags & PF_W) &&
if ((phdr.p_type == PT_LOAD || phdr.p_type == PT_GNU_RELRO) &&
!(phdr.p_flags & PF_W) &&
phdr.p_vaddr <= val && val < phdr.p_vaddr + phdr.p_memsz)
return true;
return false;
+73 -11
View File
@@ -7,7 +7,8 @@
namespace mold::elf {
typedef enum {
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL, BASEREL, IFUNC,
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL,
BASEREL, IFUNC_DYNREL,
} Action;
template <typename E>
@@ -37,8 +38,7 @@ static i64 to_p2align(u64 alignment) {
}
template <typename E>
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
std::string_view name, i64 shndx)
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
: file(file), shndx(shndx) {
if (shndx < file.elf_sections.size())
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
@@ -150,40 +150,67 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
case NONE:
break;
case ERROR:
// Print out the "recompile with -fPIC" error message.
error();
break;
case COPYREL:
// Create a copy relocation.
if (!ctx.arg.z_copyreloc)
error();
copyrel();
break;
case DYN_COPYREL:
// Same as COPYREL but try to avoid creating a copy relocation by
// creating a dynamic relocation instead if the relocation is in
// a writable section.
//
// GHC (Glasgow Haskell Compiler) places a small amount of data in
// .text before each function and access that data with a fixed
// offset. The function breaks if we copy-relocate the data. For such
// programs, we should avoid copy relocations if possible.
//
// Besides GHC, copy relocation is a hacky solution, so if we can
// represent a relocation either with copyrel or dynrel, we prefer
// dynamic relocation.
if (writable || !ctx.arg.z_copyreloc)
dynrel();
else
copyrel();
break;
case PLT:
// Create a PLT entry.
sym.flags |= NEEDS_PLT;
break;
case CPLT:
// Create a canonical PLT entry.
sym.flags |= NEEDS_CPLT;
break;
case DYN_CPLT:
// Same as CPLT but try to avoid creating a canonical PLT creating by
// creating a dynamic relocation instead if the relocation is in a
// writable section. The motivation behind it is hte same as DYN_COPYREL.
if (writable)
dynrel();
else
sym.flags |= NEEDS_CPLT;
break;
case DYNREL:
// Create a dynamic relocation.
dynrel();
break;
case BASEREL:
// Create a base relocation.
check_textrel();
if (!isec.is_relr_reloc(ctx, rel))
isec.file.num_dynrel++;
break;
case IFUNC:
case IFUNC_DYNREL:
// Create an IRELATIVE relocation for a GNU ifunc symbol.
//
// We usually create an IRELATIVE relocation in .got for each ifunc.
// However, if a statically-initialized pointer is initialized to an
// ifunc's address, we have no choice other than emitting an IRELATIVE
// relocation for each such pointer.
dynrel();
ctx.num_ifunc_dynrels++;
break;
@@ -230,9 +257,9 @@ static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
template <typename E>
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
// This is a decision table for absolute relocations that is smaller
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
// than the pointer size (e.g. R_X86_64_32). Since the dynamic linker
// generally does not support dynamic relocations smaller than the
// word size, we need to report an error if a relocation cannot be
// pointer size, we need to report an error if a relocation cannot be
// resolved at link-time.
static Action table[3][4] = {
// Absolute Local Imported data Imported code
@@ -247,9 +274,9 @@ static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
template <typename E>
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC;
return ctx.arg.pic ? IFUNC_DYNREL : NONE;
// This is a decision table for absolute relocations for the word
// This is a decision table for absolute relocations for the pointer
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
// a dynamic relocation if we cannot resolve an address at link-time.
static Action table[3][4] = {
@@ -265,7 +292,7 @@ static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
template <typename E>
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC;
return IFUNC_DYNREL;
// As a special case, we do not create copy relocations nor canonical
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
@@ -305,6 +332,29 @@ void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
}
template <typename E>
void InputSection<E>::scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
if (ctx.arg.is_static ||
(ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// Relax TLSDESC to Local Exec. In this case, we directly materialize
// a TP-relative offset, so no dynamic relocation is needed.
//
// TLSDESC relocs must always be relaxed for statically-linked
// executables even if -no-relax is given. It is because a
// statically-linked executable doesn't contain a trampoline
// function needed for TLSDESC.
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
// In this condition, TP-relative offset of a thread-local variable
// is known at process startup time, so we can relax TLSDESC to the
// code that reads the TP-relative offset from GOT and add TP to it.
sym.flags |= NEEDS_GOTTP;
} else {
// If no relaxation is doable, we simply create a TLSDESC dynamic
// relocation.
sym.flags |= NEEDS_TLSDESC;
}
}
template <typename E>
void InputSection<E>::check_tlsle(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
@@ -322,7 +372,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
auto emit_abs_dynrel = [&] {
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, sym.get_dynsym_idx(ctx), A);
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = A;
};
@@ -357,7 +407,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
case DYNREL:
emit_abs_dynrel();
break;
case IFUNC:
case IFUNC_DYNREL:
if constexpr (supports_ifunc<E>) {
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
@@ -407,6 +457,18 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
apply_reloc_alloc(ctx, buf);
else
apply_reloc_nonalloc(ctx, buf);
if constexpr (is_x86_64<E>) {
u8 endbr[] = {0xf3, 0x0f, 0x1e, 0xfa};
u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
// Rewrite the leading endbr instruction with a nop if the section
// is not address-taken.
if (ctx.arg.z_rewrite_endbr && (shdr().sh_flags & SHF_EXECINSTR) &&
!address_taken && sh_size >= 4 && memcmp(buf, endbr, 4) == 0) {
memcpy(buf, nop, 4);
}
}
}
}
+17 -5
View File
@@ -1,3 +1,14 @@
// Many build systems attempt to invoke as many linker processes as there
// are cores, based on the assumption that the linker is single-threaded.
// However, since mold is multi-threaded, such build systems' behavior is
// not beneficial and just increases the overall peak memory usage.
// On machines with limited memory, this could lead to an out-of-memory
// error.
//
// This file implements a feature that limits the number of concurrent
// 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 "mold.h"
#ifndef _WIN32
@@ -14,14 +25,15 @@ template <typename E>
void acquire_global_lock(Context<E> &ctx) {
#ifndef _WIN32
char *jobs = getenv("MOLD_JOBS");
if (!jobs || std::string(jobs) != "1")
if (!jobs || jobs != "1"s)
return;
char *home = getenv("HOME");
if (!home)
home = getpwuid(getuid())->pw_dir;
std::string path;
if (char *dir = getenv("XDG_RUNTIME_DIR"))
path = dir + "/mold-lock"s;
else
path = "/tmp/mold-lock-"s + getpwuid(getuid())->pw_name;
std::string path = std::string(home) + "/.mold-lock";
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
if (fd == -1)
return;
+5 -6
View File
@@ -279,9 +279,8 @@ static bool read_label(std::span<std::string_view> &tok,
template <typename E>
static void
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
std::string_view ver_str, u16 ver_idx, bool is_cpp) {
bool is_global = true;
std::string_view ver_str, u16 ver_idx,
bool is_global, bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (read_label(tok, "global")) {
is_global = true;
@@ -299,11 +298,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script_commands( ctx, tok, ver_str, ver_idx, false);
read_version_script_commands( ctx, tok, ver_str, ver_idx, is_global, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
read_version_script_commands(ctx, tok, ver_str, ver_idx, is_global, true);
}
tok = skip(ctx, tok, "}");
@@ -349,7 +348,7 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
}
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, false);
read_version_script_commands(ctx, tok, ver_str, ver_idx, true, false);
tok = skip(ctx, tok, "}");
if (!tok.empty() && tok[0] != ";")
tok = tok.subspan(1);
+3
View File
@@ -709,6 +709,9 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
get_symbol(ctx, y)->referenced_by_regular_obj = true;
}
// Handle --entry
get_symbol(ctx, ctx.arg.entry)->referenced_by_regular_obj = true;
// all_symbols_read_hook() calls add_input_file() and add_input_library()
LOG << "all symbols read\n";
if (PluginStatus st = all_symbols_read_hook(); st != LDPS_OK)
+1 -3
View File
@@ -55,9 +55,7 @@ enum PluginApiVersion {
struct PluginTagValue {
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
template <typename T>
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
PluginTagValue(PluginTag tag, auto *ptr) : tag(tag), ptr((void *)ptr) {}
PluginTag tag;
union {
+14 -94
View File
@@ -70,10 +70,6 @@ std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
return SH4::target_name;
case EM_ALPHA:
return ALPHA::target_name;
case EM_MIPS:
if (is_64)
return is_le ? MIPS64LE::target_name : MIPS64BE::target_name;
return "";
case EM_LOONGARCH:
return is_64 ? LOONGARCH64::target_name : LOONGARCH32::target_name;
default:
@@ -195,7 +191,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
break;
}
}
ctx.visited.insert(mf->name);
if (!ctx.whole_archive)
ctx.visited.insert(mf->name);
return;
case FileType::TEXT:
parse_linker_script(ctx, mf);
@@ -342,52 +339,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
ctx.tg.wait();
}
// Since elf_main is a template, we can't run it without a type parameter.
// We speculatively run elf_main with X86_64, and if the speculation was
// wrong, re-run it with an actual machine type.
template <typename E>
static int redo_main(int argc, char **argv, std::string_view target) {
if (target == I386::target_name)
return elf_main<I386>(argc, argv);
if (target == ARM64::target_name)
return elf_main<ARM64>(argc, argv);
if (target == ARM32::target_name)
return elf_main<ARM32>(argc, argv);
if (target == RV64LE::target_name)
return elf_main<RV64LE>(argc, argv);
if (target == RV64BE::target_name)
return elf_main<RV64BE>(argc, argv);
if (target == RV32LE::target_name)
return elf_main<RV32LE>(argc, argv);
if (target == RV32BE::target_name)
return elf_main<RV32BE>(argc, argv);
if (target == PPC32::target_name)
return elf_main<PPC32>(argc, argv);
if (target == PPC64V1::target_name)
return elf_main<PPC64V1>(argc, argv);
if (target == PPC64V2::target_name)
return elf_main<PPC64V2>(argc, argv);
if (target == S390X::target_name)
return elf_main<S390X>(argc, argv);
if (target == SPARC64::target_name)
return elf_main<SPARC64>(argc, argv);
if (target == M68K::target_name)
return elf_main<M68K>(argc, argv);
if (target == SH4::target_name)
return elf_main<SH4>(argc, argv);
if (target == ALPHA::target_name)
return elf_main<ALPHA>(argc, argv);
if (target == MIPS64LE::target_name)
return elf_main<MIPS64LE>(argc, argv);
if (target == MIPS64BE::target_name)
return elf_main<MIPS64BE>(argc, argv);
if (target == LOONGARCH32::target_name)
return elf_main<LOONGARCH32>(argc, argv);
if (target == LOONGARCH64::target_name)
return elf_main<LOONGARCH64>(argc, argv);
unreachable();
}
template <typename E>
int elf_main(int argc, char **argv) {
Context<E> ctx;
@@ -411,7 +362,7 @@ int elf_main(int argc, char **argv) {
// Redo if -m is not x86-64.
if constexpr (is_x86_64<E>)
if (ctx.arg.emulation != X86_64::target_name)
return redo_main<E>(argc, argv, ctx.arg.emulation);
return redo_main(ctx, argc, argv);
Timer t_all(ctx, "all");
@@ -505,9 +456,9 @@ int elf_main(int argc, char **argv) {
// Set is_imported and is_exported bits for each symbol.
compute_import_export(ctx);
// Read address-significant section information.
if (ctx.arg.icf && !ctx.arg.icf_all)
mark_addrsig(ctx);
// Set "address-taken" bits for input sections.
if (ctx.arg.icf || ctx.arg.z_rewrite_endbr)
compute_address_significance(ctx);
// Garbage-collect unreachable sections.
if (ctx.arg.gc_sections)
@@ -646,10 +597,6 @@ int elf_main(int argc, char **argv) {
// Here, we construct output .eh_frame contents.
ctx.eh_frame->construct(ctx);
// Handle --gdb-index.
if (ctx.arg.gdb_index)
ctx.gdb_index->construct(ctx);
// If --emit-relocs is given, we'll copy relocation sections from input
// files to an output file.
if (ctx.arg.emit_relocs)
@@ -695,12 +642,6 @@ int elf_main(int argc, char **argv) {
// Copy input sections to the output file and apply relocations.
copy_chunks(ctx);
// Some part of .gdb_index couldn't be computed until other debug
// sections are complete. We have complete debug sections now, so
// write the rest of .gdb_index.
if (ctx.gdb_index)
ctx.gdb_index->write_address_areas(ctx);
// Dynamic linker works better with sorted .rela.dyn section,
// so we sort them.
ctx.reldyn->sort(ctx);
@@ -708,6 +649,12 @@ int elf_main(int argc, char **argv) {
// Zero-clear paddings between sections
clear_padding(ctx);
// .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)
write_gdb_index(ctx);
// .note.gnu.build-id section contains a cryptographic hash of the
// entire output file. Now that we wrote everything except build-id,
// we can compute it.
@@ -755,41 +702,14 @@ int elf_main(int argc, char **argv) {
return 0;
}
using E = MOLD_TARGET;
template void read_file(Context<E> &, MappedFile<Context<E>> *);
template MappedFile<Context<E>> *open_library(Context<E> &, std::string);
#ifdef MOLD_X86_64
extern template int elf_main<I386>(int, char **);
extern template int elf_main<ARM32>(int, char **);
extern template int elf_main<ARM64>(int, char **);
extern template int elf_main<RV32BE>(int, char **);
extern template int elf_main<RV32LE>(int, char **);
extern template int elf_main<RV64LE>(int, char **);
extern template int elf_main<RV64BE>(int, char **);
extern template int elf_main<PPC32>(int, char **);
extern template int elf_main<PPC64V1>(int, char **);
extern template int elf_main<PPC64V2>(int, char **);
extern template int elf_main<S390X>(int, char **);
extern template int elf_main<SPARC64>(int, char **);
extern template int elf_main<M68K>(int, char **);
extern template int elf_main<SH4>(int, char **);
extern template int elf_main<ALPHA>(int, char **);
extern template int elf_main<MIPS64LE>(int, char **);
extern template int elf_main<MIPS64BE>(int, char **);
extern template int elf_main<LOONGARCH32>(int, char **);
extern template int elf_main<LOONGARCH64>(int, char **);
int main(int argc, char **argv) {
return elf_main<X86_64>(argc, argv);
}
#endif
#else
using E = MOLD_TARGET;
template int elf_main<E>(int, char **);
#endif
} // namespace mold::elf
+150 -245
View File
@@ -36,8 +36,6 @@
namespace mold::elf {
static constexpr i32 SHA256_SIZE = 32;
template <typename E> class InputFile;
template <typename E> class InputSection;
template <typename E> class MergedSection;
@@ -50,7 +48,6 @@ template <typename E> struct CieRecord;
template <typename E> struct Context;
template <typename E> struct FdeRecord;
template <typename E> class RelocSection;
template <typename E> class MipsGotSection;
template <typename E>
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
@@ -100,7 +97,7 @@ struct SymbolAux<PPC64V1> : SymbolAux<X86_64> {
template <typename E>
class RangeExtensionThunk {};
template <typename E> requires needs_thunk<E>
template <needs_thunk E>
class RangeExtensionThunk<E> {
public:
RangeExtensionThunk(OutputSection<E> &osec, i64 offset)
@@ -127,9 +124,6 @@ struct RangeExtensionRef {
i16 sym_idx = -1;
};
template <typename E>
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec);
//
// input-sections.cc
//
@@ -216,12 +210,12 @@ struct FdeRecord {
template <typename E>
struct InputSectionExtras {};
template <typename E> requires needs_thunk<E>
template <needs_thunk E>
struct InputSectionExtras<E> {
std::vector<RangeExtensionRef> range_extn;
};
template <typename E> requires is_riscv<E>
template <is_riscv E>
struct InputSectionExtras<E> {
std::vector<i32> r_deltas;
};
@@ -230,8 +224,7 @@ struct InputSectionExtras<E> {
template <typename E>
class InputSection {
public:
InputSection(Context<E> &ctx, ObjectFile<E> &file, std::string_view name,
i64 shndx);
InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx);
void uncompress(Context<E> &ctx);
void uncompress_to(Context<E> &ctx, u8 *buf);
@@ -264,17 +257,19 @@ public:
i32 fde_begin = -1;
i32 fde_end = -1;
u32 offset = -1;
u64 offset = -1;
u32 shndx = -1;
u32 relsec_idx = -1;
u32 reldyn_offset = 0;
bool uncompressed = false;
// For COMDAT de-duplication and garbage collection
std::atomic_bool is_alive = true;
u8 p2align = 0;
bool address_significant : 1 = false;
bool uncompressed : 1 = false;
// For ICF
Atomic<bool> address_taken = false;
// For garbage collection
Atomic<bool> is_visited = false;
@@ -296,7 +291,7 @@ private:
void scan_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
void scan_dyn_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
void scan_toc_rel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
void scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym);
void check_tlsle(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel);
void apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel,
@@ -319,9 +314,9 @@ private:
// tls.cc
//
template<typename E> u64 get_tls_begin(Context<E> &);
template<typename E> u64 get_tp_addr(Context<E> &);
template<typename E> u64 get_dtp_addr(Context<E> &);
template <typename E> u64 get_tls_begin(Context<E> &);
template <typename E> u64 get_tp_addr(Context<E> &);
template <typename E> u64 get_dtp_addr(Context<E> &);
//
// output-chunks.cc
@@ -355,18 +350,20 @@ public:
virtual ChunkKind kind() { return SYNTHETIC; }
virtual OutputSection<E> *to_osec() { return nullptr; }
virtual i64 get_reldyn_size(Context<E> &ctx) const { return 0; }
virtual void construct_relr(Context<E> &ctx) {}
virtual void copy_buf(Context<E> &ctx) {}
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
virtual void update_shdr(Context<E> &ctx) {}
// For --gdb-index
virtual u8 *get_uncompressed_data() { return nullptr; }
std::string_view name;
ElfShdr<E> shdr = { .sh_addralign = 1 };
i64 shndx = 0;
bool is_relro = false;
// 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
@@ -384,6 +381,9 @@ public:
// For --section-order
i64 sect_order = 0;
// For --pack-dyn-relocs=relr
std::vector<u64> relr;
};
// ELF header
@@ -449,20 +449,19 @@ public:
template <typename E>
class OutputSection : public Chunk<E> {
public:
OutputSection(std::string_view name, u32 type, u64 flags);
OutputSection(Context<E> &ctx, std::string_view name, u32 type, u64 flags);
ChunkKind kind() override { return OUTPUT_SECTION; }
OutputSection<E> *to_osec() override { return this; }
void construct_relr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
void write_to(Context<E> &ctx, u8 *buf) override;
void compute_symtab_size(Context<E> &ctx) override;
void populate_symtab(Context<E> &ctx) override;
void create_range_extension_thunks(Context<E> &ctx);
std::vector<InputSection<E> *> members;
void construct_relr(Context<E> &ctx);
std::vector<u64> relr;
std::vector<std::unique_ptr<RangeExtensionThunk<E>>> thunks;
std::unique_ptr<RelocSection<E>> reloc_sec;
};
@@ -478,7 +477,7 @@ public:
this->shdr.sh_addralign = sizeof(Word<E>);
// We always create a .got so that _GLOBAL_OFFSET_TABLE_ has
// something to point to. s390x/MIPS psABIs define GOT[1] as a
// something to point to. s390x psABI define GOT[1] as a
// reserved slot, so we allocate one more for them.
this->shdr.sh_size = (is_s390x<E> ? 2 : 1) * sizeof(Word<E>);
}
@@ -494,6 +493,7 @@ public:
i64 get_reldyn_size(Context<E> &ctx) const override;
void copy_buf(Context<E> &ctx) override;
void construct_relr(Context<E> &ctx) override;
void compute_symtab_size(Context<E> &ctx) override;
void populate_symtab(Context<E> &ctx) override;
@@ -502,9 +502,6 @@ public:
std::vector<Symbol<E> *> tlsdesc_syms;
std::vector<Symbol<E> *> gottp_syms;
u32 tlsld_idx = -1;
void construct_relr(Context<E> &ctx);
std::vector<u64> relr;
};
template <typename E>
@@ -625,6 +622,12 @@ public:
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
// Offsets in .strtab for ARM32 mapping symbols
static constexpr i64 ARM = 1;
static constexpr i64 THUMB = 4;
static constexpr i64 DATA = 7;
};
template <typename E>
@@ -648,7 +651,6 @@ public:
this->shdr.sh_flags = SHF_ALLOC;
}
void keep() { this->shdr.sh_size = 1; }
i64 add_string(std::string_view str);
i64 find_string(std::string_view str);
void copy_buf(Context<E> &ctx) override;
@@ -675,7 +677,7 @@ public:
void copy_buf(Context<E> &ctx) override;
};
template<typename E>
template <typename E>
ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
U32<E> *shndx);
@@ -715,13 +717,13 @@ public:
this->shdr.sh_addralign = sizeof(Word<E>);
}
void keep() { this->symbols.resize(1); }
void add_symbol(Context<E> &ctx, Symbol<E> *sym);
void finalize(Context<E> &ctx);
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
std::vector<Symbol<E> *> symbols;
bool finalized = false;
};
template <typename E>
@@ -765,7 +767,8 @@ template <typename E>
class MergedSection : public Chunk<E> {
public:
static MergedSection<E> *
get_instance(Context<E> &ctx, std::string_view name, u64 type, u64 flags);
get_instance(Context<E> &ctx, std::string_view name, i64 type, i64 flags,
i64 entsize);
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
u64 hash, i64 p2align);
@@ -778,7 +781,7 @@ public:
HyperLogLog estimator;
private:
MergedSection(std::string_view name, u64 flags, u32 type);
MergedSection(std::string_view name, i64 flags, i64 type, i64 entsize);
ConcurrentMap<SectionFragment<E>> map;
std::vector<i64> shard_offsets;
@@ -954,13 +957,6 @@ private:
std::map<u32, u32> properties;
};
struct GdbIndexName {
std::string_view name;
u32 hash = 0;
u32 attr = 0;
u32 entry_idx = 0;
};
template <typename E>
class GdbIndexSection : public Chunk<E> {
public:
@@ -969,38 +965,6 @@ public:
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_addralign = 4;
}
void construct(Context<E> &ctx);
void copy_buf(Context<E> &ctx) override;
void write_address_areas(Context<E> &ctx);
private:
struct SectionHeader {
ul32 version = 7;
ul32 cu_list_offset = 0;
ul32 cu_types_offset = 0;
ul32 areas_offset = 0;
ul32 symtab_offset = 0;
ul32 const_pool_offset = 0;
};
struct MapEntry {
MapEntry(ObjectFile<E> *owner, u32 hash) : owner(owner), hash(hash) {}
MapEntry(const MapEntry &other)
: owner(other.owner.load()), num_attrs(other.num_attrs.load()),
hash(other.hash), name_offset(other.name_offset),
attr_offset(other.attr_offset) {}
std::atomic<ObjectFile<E> *> owner;
std::atomic_uint32_t num_attrs = 0;
u32 hash = 0;
u32 name_offset = -1;
u32 attr_offset = -1;
};
SectionHeader header;
ConcurrentMap<MapEntry> map;
};
template <typename E>
@@ -1008,12 +972,10 @@ class CompressedSection : public Chunk<E> {
public:
CompressedSection(Context<E> &ctx, Chunk<E> &chunk);
void copy_buf(Context<E> &ctx) override;
u8 *get_uncompressed_data() override { return uncompressed.get(); }
private:
ElfChdr<E> chdr = {};
std::unique_ptr<Compressor> compressed;
std::unique_ptr<u8[]> uncompressed;
};
template <typename E>
@@ -1065,22 +1027,10 @@ private:
};
//
// dwarf.cc
// gdb-index.cc
//
template <typename E>
std::vector<std::string_view>
read_compunits(Context<E> &ctx, ObjectFile<E> &file);
template <typename E>
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file);
template <typename E>
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file);
template <typename E>
std::vector<u64>
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset);
template <typename E> void write_gdb_index(Context<E> &ctx);
//
// input-files.cc
@@ -1133,11 +1083,11 @@ public:
virtual ~InputFile() = default;
template<typename T> std::span<T>
get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
template <typename T>
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
template<typename T> std::span<T>
get_data(Context<E> &ctx, i64 idx);
template <typename T>
std::span<T> get_data(Context<E> &ctx, i64 idx);
std::string_view get_string(Context<E> &ctx, const ElfShdr<E> &shdr);
std::string_view get_string(Context<E> &ctx, i64 idx);
@@ -1188,24 +1138,18 @@ protected:
template <typename E> struct ObjectFileExtras {};
template <typename E> requires is_riscv<E>
template <is_riscv E>
struct ObjectFileExtras<E> {
std::optional<i64> stack_align;
std::optional<std::string_view> arch;
bool unaligned_access = false;
};
template <> struct ObjectFileExtras<PPC32> {
template <>
struct ObjectFileExtras<PPC32> {
InputSection<PPC32> *got2 = nullptr;
};
template <typename E> requires is_mips<E>
struct ObjectFileExtras<E> {
std::unique_ptr<InputSection<E>> abi_flags;
MipsGotSection<E> *got = nullptr;
u64 gp0 = 0;
};
// ObjectFile represents an input .o file.
template <typename E>
class ObjectFile : public InputFile<E> {
@@ -1222,7 +1166,6 @@ public:
void mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) override;
void convert_undefined_weak_symbols(Context<E> &ctx);
void mark_addrsig(Context<E> &ctx);
void scan_relocations(Context<E> &ctx);
void convert_common_symbols(Context<E> &ctx);
void compute_symtab_size(Context<E> &ctx);
@@ -1240,6 +1183,7 @@ public:
std::vector<FdeRecord<E>> fdes;
BitVector has_symver;
std::vector<ComdatGroupRef<E>> comdat_groups;
InputSection<E> *eh_frame_section = nullptr;
bool exclude_libs = false;
std::map<u32, u32> gnu_properties;
bool is_lto_obj = false;
@@ -1257,19 +1201,8 @@ public:
// For .gdb_index
InputSection<E> *debug_info = nullptr;
InputSection<E> *debug_ranges = nullptr;
InputSection<E> *debug_rnglists = nullptr;
InputSection<E> *debug_pubnames = nullptr;
InputSection<E> *debug_pubtypes = nullptr;
std::vector<std::string_view> compunits;
std::vector<GdbIndexName> gdb_names;
i64 compunits_idx = 0;
i64 attrs_size = 0;
i64 attrs_offset = 0;
i64 names_size = 0;
i64 names_offset = 0;
i64 num_areas = 0;
i64 area_offset = 0;
// For LTO
std::vector<std::string_view> lto_symbol_versions;
@@ -1285,8 +1218,8 @@ private:
void initialize_symbols(Context<E> &ctx);
void sort_relocations(Context<E> &ctx);
void initialize_ehframe_sections(Context<E> &ctx);
void read_note_gnu_property(Context <E> &ctx, const ElfShdr <E> &shdr);
void read_ehframe(Context<E> &ctx, InputSection<E> &isec);
void parse_note_gnu_property(Context <E> &ctx, const ElfShdr <E> &shdr);
void parse_ehframe(Context<E> &ctx);
void override_symbol(Context<E> &ctx, Symbol<E> &sym,
const ElfSym<E> &esym, i64 symidx);
void merge_visibility(Context<E> &ctx, Symbol<E> &sym, u8 visibility);
@@ -1315,7 +1248,6 @@ public:
void compute_symtab_size(Context<E> &ctx);
void populate_symtab(Context<E> &ctx);
bool is_needed = false;
std::string soname;
std::vector<std::string_view> version_strings;
std::vector<ElfSym<E>> elf_syms2;
@@ -1411,7 +1343,7 @@ template <typename E> void acquire_global_lock(Context<E> &ctx);
template <typename E> void release_global_lock(Context<E> &ctx);
//
// commandline.cc
// cmdline.cc
//
template <typename E>
@@ -1421,6 +1353,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
// passes.cc
//
template <typename E> int redo_main(Context<E> &, int argc, char **argv);
template <typename E> void create_internal_file(Context<E> &);
template <typename E> void apply_exclude_libs(Context<E> &);
template <typename E> void create_synthetic_sections(Context<E> &);
@@ -1452,7 +1385,7 @@ template <typename E> void copy_chunks(Context<E> &);
template <typename E> void apply_version_script(Context<E> &);
template <typename E> void parse_symbol_version(Context<E> &);
template <typename E> void compute_import_export(Context<E> &);
template <typename E> void mark_addrsig(Context<E> &);
template <typename E> void compute_address_significance(Context<E> &);
template <typename E> void clear_padding(Context<E> &);
template <typename E> void compute_section_headers(Context<E> &);
template <typename E> i64 set_osec_offsets(Context<E> &);
@@ -1465,13 +1398,14 @@ template <typename E> void show_stats(Context<E> &);
// arch-arm32.cc
//
template <> u64 get_eflags(Context<ARM32> &ctx);
void fixup_arm_exidx_section(Context<ARM32> &ctx);
//
// arch-riscv64.cc
// arch-riscv.cc
//
template <typename E> requires is_riscv<E>
template <is_riscv E>
class RiscvAttributesSection : public Chunk<E> {
public:
RiscvAttributesSection() {
@@ -1485,7 +1419,10 @@ public:
std::vector<u8> contents;
};
template <typename E>
template <is_riscv E>
u64 get_eflags(Context<E> &ctx);
template <is_riscv E>
i64 riscv_resize_sections(Context<E> &ctx);
//
@@ -1513,6 +1450,12 @@ public:
std::vector<Symbol<PPC64V1> *> symbols;
};
//
// arch-ppc64v2.cc
//
template <> u64 get_eflags(Context<PPC64V2> &ctx);
//
// arch-sparc.cc
//
@@ -1561,86 +1504,6 @@ private:
std::mutex mu;
};
//
// arch-mips64.cc
//
template <typename E>
class MipsQuickstartSection : public Chunk<E> {
public:
MipsQuickstartSection() {
this->name = ".mips_quickstart";
this->is_relro = true;
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_MIPS_GPREL;
this->shdr.sh_addralign = 8;
}
static constexpr i64 NUM_RESERVED = 2;
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
};
template <typename E>
class MipsGotSection : public Chunk<E> {
public:
MipsGotSection(Context<E> &ctx, const ObjectFile<E> &file) {
this->name = save_string(ctx, ".mips_got." + std::to_string(file.priority));
this->is_relro = true;
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_MIPS_GPREL;
this->shdr.sh_addralign = 8;
}
u64 get_got_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
u64 get_gotpage_addr(Context<E> &ctx, Symbol<E> &sym, i64 addend) const;
u64 get_tlsgd_addr(Context<E> &ctx, Symbol<E> &sym) const;
u64 get_gottp_addr(Context<E> &ctx, Symbol<E> &sym) const;
u64 get_tlsld_addr(Context<E> &ctx) const;
void update_shdr(Context<E> &ctx) override;
i64 get_reldyn_size(Context<E> &ctx) const override;
void copy_buf(Context<E> &ctx) override;
struct SymbolAddend {
bool operator==(const SymbolAddend &) const = default;
bool operator<(const SymbolAddend &) const;
u64 get_addr(Context<E> &ctx, i64 flags = 0) const;
Symbol<E> *sym;
i64 addend;
};
std::vector<SymbolAddend> got_syms;
std::vector<SymbolAddend> gotpage_syms;
std::vector<Symbol<E> *> tlsgd_syms;
std::vector<Symbol<E> *> gottp_syms;
bool has_tlsld = false;
};
template <typename E>
class MipsABIFlagsSection : public Chunk<E> {
public:
MipsABIFlagsSection() {
this->name = ".MIPS.abiflags";
this->shdr.sh_type = SHT_MIPS_ABIFLAGS;
this->shdr.sh_flags = SHF_ALLOC;
this->shdr.sh_addralign = 8;
}
std::string_view contents;
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
};
template <typename E>
void mips_merge_got_sections(Context<E> &ctx);
template <typename E>
void mips_rewrite_cie(Context<E> &ctx, u8 *buf, CieRecord<E> &cie);
//
// main.cc
//
@@ -1694,42 +1557,42 @@ struct SectionOrder {
};
// Target-specific context members
template <typename E> struct ContextExtras {};
template <typename E>
struct ContextExtras {};
template <typename E> requires is_riscv<E>
template <is_riscv E>
struct ContextExtras<E> {
RiscvAttributesSection<E> *riscv_attributes = nullptr;
};
template <> struct ContextExtras<PPC32> {
template <>
struct ContextExtras<PPC32> {
Symbol<PPC32> *_SDA_BASE_ = nullptr;
};
template <> struct ContextExtras<PPC64V1> {
template <>
struct ContextExtras<PPC64V1> {
PPC64OpdSection *opd = nullptr;
Symbol<PPC64V1> *TOC = nullptr;
};
template <> struct ContextExtras<PPC64V2> {
template <>
struct ContextExtras<PPC64V2> {
Symbol<PPC64V2> *TOC = nullptr;
Atomic<bool> is_power10 = false;
};
template <> struct ContextExtras<SPARC64> {
template <>
struct ContextExtras<SPARC64> {
SparcTlsGetAddrSection *tls_get_addr_sec = nullptr;
Symbol<SPARC64> *tls_get_addr_sym = nullptr;
};
template <> struct ContextExtras<ALPHA> {
template <>
struct ContextExtras<ALPHA> {
AlphaGotSection *got = nullptr;
};
template <typename E> requires is_mips<E>
struct ContextExtras<E> {
MipsQuickstartSection<E> *quickstart = nullptr;
MipsABIFlagsSection<E> *abi_flags = nullptr;
};
// Context represents a context object for each invocation of the linker.
// It contains command line flags, pointers to singleton objects
// (such as linker-synthesized output sections), unique_ptrs for
@@ -1772,7 +1635,7 @@ struct Context {
bool fork = true;
bool gc_sections = false;
bool gdb_index = false;
bool hash_style_gnu = !is_mips<E>;
bool hash_style_gnu = true;
bool hash_style_sysv = true;
bool icf = false;
bool icf_all = false;
@@ -1823,6 +1686,7 @@ struct Context {
bool z_now = false;
bool z_origin = false;
bool z_relro = true;
bool z_rewrite_endbr = false;
bool z_sectionheader = true;
bool z_shstk = false;
bool z_text = false;
@@ -1839,7 +1703,7 @@ struct Context {
std::string dependency_file;
std::string directory;
std::string dynamic_linker;
std::string entry = is_mips<E> ? "__start" : "_start";
std::string entry = "_start";
std::string fini = "_fini";
std::string init = "_init";
std::string output = "a.out";
@@ -1869,7 +1733,7 @@ struct Context {
std::vector<VersionPattern> version_patterns;
u16 default_version = VER_NDX_GLOBAL;
i64 page_size = -1;
i64 page_size = E::page_size;
std::optional<int> global_lock_fd;
// true if default_version is set by a wildcard in version script.
@@ -1921,9 +1785,9 @@ struct Context {
bool overwrite_output_file = true;
std::vector<Chunk<E> *> chunks;
std::atomic_bool needs_tlsld = false;
std::atomic_bool has_textrel = false;
std::atomic_uint32_t num_ifunc_dynrels = 0;
Atomic<bool> needs_tlsld = false;
Atomic<bool> has_textrel = false;
Atomic<u32> num_ifunc_dynrels = 0;
tbb::concurrent_hash_map<std::string_view, std::vector<std::string>> undef_errors;
@@ -1965,11 +1829,11 @@ struct Context {
[[no_unique_address]] ContextExtras<E> extra;
// For --gdb-index
Chunk<E> *debug_info = nullptr;
Chunk<E> *debug_abbrev = nullptr;
Chunk<E> *debug_ranges = nullptr;
Chunk<E> *debug_addr = nullptr;
Chunk<E> *debug_rnglists = nullptr;
std::span<u8> debug_info;
std::span<u8> debug_abbrev;
std::span<u8> debug_ranges;
std::span<u8> debug_addr;
std::span<u8> debug_rnglists;
// For thread-local variables
u64 tls_begin = 0;
@@ -2000,7 +1864,6 @@ struct Context {
Symbol<E> *_edata = nullptr;
Symbol<E> *_end = nullptr;
Symbol<E> *_etext = nullptr;
Symbol<E> *_gp = nullptr;
Symbol<E> *edata = nullptr;
Symbol<E> *end = nullptr;
Symbol<E> *etext = nullptr;
@@ -2045,7 +1908,7 @@ enum {
template <typename E>
struct SymbolExtras {};
template <typename E> requires needs_thunk<E>
template <needs_thunk E>
struct SymbolExtras<E> {
// For range extension thunks
i16 thunk_idx = -1;
@@ -2078,6 +1941,7 @@ public:
u64 get_tlsdesc_addr(Context<E> &ctx) const;
u64 get_plt_addr(Context<E> &ctx) const;
u64 get_opd_addr(Context<E> &ctx) const;
u64 get_got_pltgot_addr(Context<E> &ctx) const;
void set_got_idx(Context<E> &ctx, i32 idx);
void set_gottp_idx(Context<E> &ctx, i32 idx);
@@ -2111,8 +1975,13 @@ public:
bool is_relative() const { return !is_absolute(); }
bool is_local(Context<E> &ctx) const;
bool is_ifunc() const { return get_type() == STT_GNU_IFUNC; }
bool is_pde_ifunc(Context<E> &ctx) const;
bool is_remaining_undef_weak() const;
bool is_pcrel_linktime_const(Context<E> &ctx) const;
bool is_tprel_linktime_const(Context<E> &ctx) const;
bool is_tprel_runtime_const(Context<E> &ctx) const;
InputSection<E> *get_input_section() const;
Chunk<E> *get_output_section() const;
SectionFragment<E> *get_frag() const;
@@ -2484,7 +2353,7 @@ InputSection<E>::is_relr_reloc(Context<E> &ctx, const ElfRel<E> &rel) const {
(rel.r_offset % sizeof(Word<E>)) == 0;
}
template<typename E>
template <typename E>
inline bool InputSection<E>::is_killed_by_icf() const {
return this->leader && this->leader != this;
}
@@ -2708,6 +2577,25 @@ inline u64 Symbol<E>::get_opd_addr(Context<E> &ctx) const {
get_opd_idx(ctx) * PPC64OpdSection::ENTRY_SIZE;
}
template <typename E>
inline u64 Symbol<E>::get_got_pltgot_addr(Context<E> &ctx) const {
// An ifunc symbol occupies two consecutive GOT slots in a
// position-dependent executable (PDE). The first slot contains the
// symbol's PLT address, and the second slot holds the resolved
// address. A PDE uses the ifunc symbol's PLT entry as the address
// for the symbol, akin to a canonical PLT.
//
// This function returns the address that the PLT entry should use
// to jump to the resolved address.
//
// Note that we don't use this function for PPC64. In PPC64, symbols
// are always accessed through the TOC table regardless of the
// -fno-PIE setting. We don't need canonical PLTs on the psABIs too.
if (is_pde_ifunc(ctx))
return get_got_addr(ctx) + sizeof(Word<E>);
return get_got_addr(ctx);
}
template <typename E>
inline void Symbol<E>::set_got_idx(Context<E> &ctx, i32 idx) {
assert(aux_idx != -1);
@@ -2836,14 +2724,43 @@ inline bool Symbol<E>::is_local(Context<E> &ctx) const {
return !is_imported && !is_exported;
}
template <typename E>
inline bool Symbol<E>::is_pde_ifunc(Context<E> &ctx) const {
// Returns true if this is an ifunc tha uses two GOT slots
return is_ifunc() && !ctx.arg.pic && !is_ppc64<E>;
}
// A remaining weak undefined symbol is promoted to a dynamic symbol
// in DSO and resolved to 0 in an executable. This function returns
// true if it's latter.
template<typename E>
template <typename E>
inline bool Symbol<E>::is_remaining_undef_weak() const {
return !is_imported && esym().is_undef_weak();
}
// Returns true if the symbol's PC-relative address is known at link-time.
template <typename E>
inline bool Symbol<E>::is_pcrel_linktime_const(Context<E> &ctx) const {
return !is_imported && !is_ifunc() && (is_relative() || !ctx.arg.pic);
}
// Returns true if the symbol's Thread Pointer-relative address is
// known at link-time.
template <typename E>
inline bool Symbol<E>::is_tprel_linktime_const(Context<E> &ctx) const {
assert(get_type() == STT_TLS);
return !ctx.arg.shared && !is_imported;
}
// Returns true if the symbol's Thread Pointer-relative address is
// known at load-time.
template <typename E>
inline bool Symbol<E>::is_tprel_runtime_const(Context<E> &ctx) const {
// Returns true unless we are creating a dlopen'able DSO.
assert(get_type() == STT_TLS);
return !(ctx.arg.shared && ctx.arg.z_dlopen);
}
template <typename E>
inline InputSection<E> *Symbol<E>::get_input_section() const {
if ((origin & TAG_MASK) == TAG_ISEC)
@@ -2928,9 +2845,8 @@ inline std::string_view Symbol<E>::name() const {
template <typename E>
inline void Symbol<E>::add_aux(Context<E> &ctx) {
if (aux_idx == -1) {
i64 sz = ctx.symbol_aux.size();
aux_idx = sz;
ctx.symbol_aux.resize(sz + 1);
aux_idx = ctx.symbol_aux.size();
ctx.symbol_aux.resize(aux_idx + 1);
}
}
@@ -2954,15 +2870,4 @@ inline bool is_c_identifier(std::string_view s) {
return true;
}
template <typename E>
inline bool relax_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
// TLSDESC relocs must be always relaxed for statically-linked
// executables even if -no-relax is given. It is because a
// statically-linked executable doesn't contain a tranpoline
// function needed for TLSDESC.
if (ctx.arg.is_static)
return true;
return ctx.arg.relax && !ctx.arg.shared && !sym.is_imported;
}
} // namespace mold::elf
+292 -555
View File
File diff suppressed because it is too large. Load diff
+229 -127
View File
@@ -13,6 +13,50 @@
namespace mold::elf {
// Since elf_main is a template, we can't run it without a type parameter.
// We speculatively run elf_main with X86_64, and if the speculation was
// wrong, re-run it with an actual machine type.
template <typename E>
int redo_main(Context<E> &ctx, int argc, char **argv) {
std::string_view target = ctx.arg.emulation;
if (target == I386::target_name)
return elf_main<I386>(argc, argv);
if (target == ARM64::target_name)
return elf_main<ARM64>(argc, argv);
if (target == ARM32::target_name)
return elf_main<ARM32>(argc, argv);
if (target == RV64LE::target_name)
return elf_main<RV64LE>(argc, argv);
if (target == RV64BE::target_name)
return elf_main<RV64BE>(argc, argv);
if (target == RV32LE::target_name)
return elf_main<RV32LE>(argc, argv);
if (target == RV32BE::target_name)
return elf_main<RV32BE>(argc, argv);
if (target == PPC32::target_name)
return elf_main<PPC32>(argc, argv);
if (target == PPC64V1::target_name)
return elf_main<PPC64V1>(argc, argv);
if (target == PPC64V2::target_name)
return elf_main<PPC64V2>(argc, argv);
if (target == S390X::target_name)
return elf_main<S390X>(argc, argv);
if (target == SPARC64::target_name)
return elf_main<SPARC64>(argc, argv);
if (target == M68K::target_name)
return elf_main<M68K>(argc, argv);
if (target == SH4::target_name)
return elf_main<SH4>(argc, argv);
if (target == ALPHA::target_name)
return elf_main<ALPHA>(argc, argv);
if (target == LOONGARCH32::target_name)
return elf_main<LOONGARCH32>(argc, argv);
if (target == LOONGARCH64::target_name)
return elf_main<LOONGARCH64>(argc, argv);
unreachable();
}
template <typename E>
void apply_exclude_libs(Context<E> &ctx) {
Timer t(ctx, "apply_exclude_libs");
@@ -31,9 +75,17 @@ void apply_exclude_libs(Context<E> &ctx) {
}
}
template <typename E>
static bool has_debug_info_section(Context<E> &ctx) {
for (ObjectFile<E> *file : ctx.objs)
if (file->debug_info)
return true;
return false;
}
template <typename E>
void create_synthetic_sections(Context<E> &ctx) {
auto push = [&]<typename T>(T *x) {
auto push = [&](auto *x) {
ctx.chunks.push_back(x);
ctx.chunk_pool.emplace_back(x);
return x;
@@ -91,7 +143,7 @@ void create_synthetic_sections(Context<E> &ctx) {
ctx.buildid = push(new BuildIdSection<E>);
if (ctx.arg.eh_frame_hdr)
ctx.eh_frame_hdr = push(new EhFrameHdrSection<E>);
if (ctx.arg.gdb_index)
if (ctx.arg.gdb_index && has_debug_info_section(ctx))
ctx.gdb_index = push(new GdbIndexSection<E>);
if (ctx.arg.z_relro && ctx.arg.section_order.empty() &&
ctx.arg.z_separate_code != SEPARATE_LOADABLE_SEGMENTS)
@@ -105,9 +157,15 @@ void create_synthetic_sections(Context<E> &ctx) {
if (ctx.arg.emit_relocs)
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie)
if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie) {
ctx.dynamic = push(new DynamicSection<E>);
// If .dynamic exists, .dynsym and .dynstr must exist as well
// since .dynamic refers to them.
ctx.dynstr->shdr.sh_size = 1;
ctx.dynsym->symbols.resize(1);
}
ctx.versym = push(new VersymSection<E>);
ctx.verneed = push(new VerneedSection<E>);
ctx.note_package = push(new NotePackageSection<E>);
@@ -127,21 +185,6 @@ void create_synthetic_sections(Context<E> &ctx) {
if constexpr (is_alpha<E>)
ctx.extra.got = push(new AlphaGotSection);
if constexpr (is_mips<E>) {
ctx.extra.quickstart = push(new MipsQuickstartSection<E>);
ctx.extra.abi_flags = push(new MipsABIFlagsSection<E>);
for (ObjectFile<E> *file : ctx.objs)
file->extra.got = push(new MipsGotSection<E>(ctx, *file));
}
// If .dynamic exists, .dynsym and .dynstr must exist as well
// since .dynamic refers them.
if (ctx.dynamic) {
ctx.dynstr->keep();
ctx.dynsym->keep();
}
}
template <typename E>
@@ -304,6 +347,7 @@ void resolve_symbols(Context<E> &ctx) {
// .eh_frame sections are parsed and regenerated by the linker for the purpose
// of deduplication and garbage collection. As such, the input sections should
// not be copied over.
//
// However, in very rare cases (e.g. GCC CRT compiled with LTO) we might need
// to resolve cross-object .eh_frame section references (they only point to
// begin or end and don't depend on the actual section contents).
@@ -313,15 +357,9 @@ template <typename E>
void kill_eh_frame_sections(Context<E> &ctx) {
Timer t(ctx, "kill_eh_frame_sections");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (i64 i = 0; i < file->sections.size(); i++) {
if (std::unique_ptr<InputSection<E>> &isec = file->sections[i]) {
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
isec->is_alive = false;
}
}
}
});
for (ObjectFile<E> *file : ctx.objs)
if (file->eh_frame_section)
file->eh_frame_section->is_alive = false;
}
template <typename E>
@@ -359,7 +397,7 @@ template <typename E>
void add_comment_string(Context<E> &ctx, std::string str) {
MergedSection<E> *sec =
MergedSection<E>::get_instance(ctx, ".comment", SHT_PROGBITS,
SHF_MERGE | SHF_STRINGS);
SHF_MERGE | SHF_STRINGS, 1);
std::string_view buf = save_string(ctx, str);
std::string_view data(buf.data(), buf.size() + 1);
@@ -414,13 +452,10 @@ static u64 canonicalize_type(std::string_view name, u64 type) {
}
struct OutputSectionKey {
bool operator==(const OutputSectionKey &) const = default;
std::string_view name;
u64 type;
u64 flags;
bool operator==(const OutputSectionKey &other) const {
return name == other.name && type == other.type && flags == other.flags;
}
};
template <typename E>
@@ -481,10 +516,7 @@ get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
const ElfShdr<E> &shdr = isec.shdr();
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
u64 type = canonicalize_type<E>(name, shdr.sh_type);
u64 flags = shdr.sh_flags & ~(u64)SHF_COMPRESSED;
if (!ctx.arg.relocatable)
flags &= ~(u64)SHF_GROUP & ~(u64)SHF_GNU_RETAIN;
u64 flags = shdr.sh_flags & ~(u64)(SHF_COMPRESSED | SHF_GROUP | SHF_GNU_RETAIN);
// .init_array is usually writable. We don't want to create multiple
// .init_array output sections, so make it always writable.
@@ -511,6 +543,8 @@ void create_output_sections(Context<E> &ctx) {
std::unordered_map<OutputSectionKey, OutputSection<E> *, Hash> map;
std::shared_mutex mu;
i64 size = ctx.osec_pool.size();
// Instantiate output sections
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
// Make a per-thread cache of the main map to avoid lock contention.
@@ -525,6 +559,15 @@ void create_output_sections(Context<E> &ctx) {
if (!isec || !isec->is_alive)
continue;
const ElfShdr<E> &shdr = isec->shdr();
if (ctx.arg.relocatable && (shdr.sh_flags & SHF_GROUP)) {
OutputSection<E> *osec =
new OutputSection<E>(ctx, isec->name(), shdr.sh_type, shdr.sh_flags);
isec->output_section = osec;
ctx.osec_pool.emplace_back(osec);
continue;
}
OutputSectionKey key = get_output_section_key(ctx, *isec);
if (auto it = cache.find(key); it != cache.end()) {
@@ -540,12 +583,11 @@ void create_output_sections(Context<E> &ctx) {
}
std::unique_ptr<OutputSection<E>> osec =
std::make_unique<OutputSection<E>>(key.name, key.type, key.flags);
std::make_unique<OutputSection<E>>(ctx, key.name, key.type, key.flags);
std::unique_lock lock(mu);
auto [it, inserted] = map.insert({key, osec.get()});
OutputSection<E> *ret = it->second;
lock.unlock();
if (inserted)
ctx.osec_pool.emplace_back(std::move(osec));
@@ -559,29 +601,29 @@ void create_output_sections(Context<E> &ctx) {
});
// Add input sections to output sections
std::vector<Chunk<E> *> chunks;
for (i64 i = size; i < ctx.osec_pool.size(); i++)
chunks.push_back(ctx.osec_pool[i].get());
for (ObjectFile<E> *file : ctx.objs)
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (isec && isec->is_alive)
isec->output_section->members.push_back(isec.get());
// Add output sections and mergeable sections to ctx.chunks
std::vector<Chunk<E> *> vec;
for (std::pair<const OutputSectionKey, OutputSection<E> *> &kv : map)
vec.push_back(kv.second);
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
if (osec->shdr.sh_size)
vec.push_back(osec.get());
chunks.push_back(osec.get());
// Sections are added to the section lists in an arbitrary order
// because they are created in parallel. Sort them to to make the
// output deterministic.
tbb::parallel_sort(vec.begin(), vec.end(), [](Chunk<E> *x, Chunk<E> *y) {
tbb::parallel_sort(chunks.begin(), chunks.end(), [](Chunk<E> *x, Chunk<E> *y) {
return std::tuple(x->name, x->shdr.sh_type, x->shdr.sh_flags) <
std::tuple(y->name, y->shdr.sh_type, y->shdr.sh_flags);
});
append(ctx.chunks, vec);
append(ctx.chunks, chunks);
}
// Create a dummy object file containing linker-synthesized
@@ -688,10 +730,10 @@ template <typename E>
void add_synthetic_symbols(Context<E> &ctx) {
ObjectFile<E> &obj = *ctx.internal_obj;
auto add = [&](std::string_view name) {
auto add = [&](std::string_view name, u32 type = STT_NOTYPE) {
ElfSym<E> esym;
memset(&esym, 0, sizeof(esym));
esym.st_type = STT_NOTYPE;
esym.st_type = type;
esym.st_shndx = SHN_ABS;
esym.st_bind = STB_GLOBAL;
esym.st_visibility = STV_HIDDEN;
@@ -737,7 +779,7 @@ void add_synthetic_symbols(Context<E> &ctx) {
ctx.__dso_handle = add("__dso_handle");
if constexpr (supports_tlsdesc<E>)
ctx._TLS_MODULE_BASE_ = add("_TLS_MODULE_BASE_");
ctx._TLS_MODULE_BASE_ = add("_TLS_MODULE_BASE_", STT_TLS);
if constexpr (is_riscv<E>)
if (!ctx.arg.shared)
@@ -754,9 +796,6 @@ void add_synthetic_symbols(Context<E> &ctx) {
if constexpr (is_ppc32<E>)
ctx.extra._SDA_BASE_ = add("_SDA_BASE_");
if constexpr (is_mips<E>)
ctx._gp = add("_gp");
for (Chunk<E> *chunk : ctx.chunks) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
add(save_string(ctx, "__start_" + *name));
@@ -982,33 +1021,30 @@ template <typename E>
void check_symbol_types(Context<E> &ctx) {
Timer t(ctx, "check_symbol_types");
auto check = [&](InputFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
std::vector<InputFile<E> *> files;
append(files, ctx.objs);
append(files, ctx.dsos);
if (!sym.file)
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
Symbol<E> &sym = *file->symbols[i];
if (!sym.file || sym.file == file)
continue;
u32 x = sym.esym().st_type;
if (x == STT_GNU_IFUNC)
x = STT_FUNC;
const ElfSym<E> &esym1 = sym.esym();
const ElfSym<E> &esym2 = file->elf_syms[i];
u32 y = esym.st_type;
if (y == STT_GNU_IFUNC)
y = STT_FUNC;
u32 ty1 = (esym1.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym1.st_type;
u32 ty2 = (esym2.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym2.st_type;
if (x != STT_NOTYPE && y != STT_NOTYPE && x != y)
if (ty1 != STT_NOTYPE && ty2 != STT_NOTYPE && ty1 != ty2)
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
<< ">>> defined in " << *sym.file << " as "
<< stt_to_string<E>(sym.esym().st_type) << '\n'
<< stt_to_string<E>(esym1.st_type) << '\n'
<< ">>> defined in " << *file << " as "
<< stt_to_string<E>(esym.st_type);
<< stt_to_string<E>(esym2.st_type);
}
};
tbb::parallel_for_each(ctx.objs, check);
tbb::parallel_for_each(ctx.dsos, check);
});
}
template <typename E>
@@ -1185,19 +1221,16 @@ void compute_section_sizes(Context<E> &ctx) {
}
});
i64 offset = 0;
i64 p2align = 0;
ElfShdr<E> &shdr = osec->shdr;
shdr.sh_size = 0;
for (i64 i = 0; i < groups.size(); i++) {
offset = align_to(offset, 1 << groups[i].p2align);
groups[i].offset = offset;
offset += groups[i].size;
p2align = std::max(p2align, groups[i].p2align);
shdr.sh_size = align_to(shdr.sh_size, 1 << groups[i].p2align);
groups[i].offset = shdr.sh_size;
shdr.sh_size += groups[i].size;
shdr.sh_addralign = std::max<u32>(shdr.sh_addralign, 1 << groups[i].p2align);
}
osec->shdr.sh_size = offset;
osec->shdr.sh_addralign = 1 << p2align;
// Assign offsets to input sections.
tbb::parallel_for_each(groups, [](Group &group) {
i64 offset = group.offset;
@@ -1218,22 +1251,14 @@ void compute_section_sizes(Context<E> &ctx) {
// create_range_extension_thunks is parallelized internally, but the
// function itself is not thread-safe.
if constexpr (needs_thunk<E>) {
for (Chunk<E> *chunk : ctx.chunks) {
OutputSection<E> *osec = chunk->to_osec();
if (osec && (osec->shdr.sh_flags & SHF_EXECINSTR) && !ctx.arg.relocatable) {
create_range_extension_thunks(ctx, *osec);
Timer t2(ctx, "create_range_extension_thunks");
for (InputSection<E> *isec : osec->members)
osec->shdr.sh_addralign =
std::max<u32>(osec->shdr.sh_addralign, 1 << isec->p2align);
}
}
if (!ctx.arg.relocatable)
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_flags & SHF_EXECINSTR)
osec->create_range_extension_thunks(ctx);
}
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (u32 align = ctx.arg.section_align[osec->name])
osec->shdr.sh_addralign = std::max<u32>(osec->shdr.sh_addralign, align);
}
// Find all unresolved symbols and attach them to the most appropriate files.
@@ -1413,9 +1438,6 @@ void scan_relocations(Context<E> &ctx) {
if constexpr (is_alpha<E>)
ctx.extra.got->finalize();
if constexpr (is_mips<E>)
mips_merge_got_sections(ctx);
if (ctx.has_textrel && ctx.arg.warn_textrel)
Warn(ctx) << "creating a DT_TEXTREL in an output file";
}
@@ -1505,20 +1527,19 @@ void construct_relr(Context<E> &ctx) {
Timer t(ctx, "construct_relr");
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
if (OutputSection<E> *osec = chunk->to_osec())
osec->construct_relr(ctx);
chunk->construct_relr(ctx);
});
ctx.got->construct_relr(ctx);
}
template <typename E>
void create_output_symtab(Context<E> &ctx) {
Timer t(ctx, "compute_symtab_size");
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
chunk->compute_symtab_size(ctx);
});
if (!ctx.arg.strip_all && !ctx.arg.retain_symbols_file) {
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
chunk->compute_symtab_size(ctx);
});
}
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->compute_symtab_size(ctx);
@@ -1708,12 +1729,85 @@ void compute_import_export(Context<E> &ctx) {
});
}
// Compute the "address-taken" bit for each input section.
//
// As a space-saving optimization, we want to merge two read-only objects
// into a single object if their contents are equivalent. That
// optimization is called the Identical Code Folding or ICF.
//
// A catch is that comparing object contents is not enough to determine if
// two objects can be merged safely; we need to take care of pointer
// equivalence.
//
// In C/C++, two pointers are equivalent if and only if they are taken for
// the same object. Merging two objects into a single object can break
// this assumption because two distinctive pointers would become
// equivalent as a result of merging. We can still merge one object with
// another if no pointer to the object was taken in code, because without
// a pointer, comparing its address becomes moot.
//
// In mold, each input section has an "address-taken" bit. If there is a
// pointer-taking reference to the object, it's set to true. At the ICF
// stage, we merge only objects whose addresses were not taken.
//
// For functions, address-taking relocations are separated from
// non-address-taking ones. For example, x86-64 uses R_X86_64_PLT32 for
// direct function calls (e.g., "call foo" to call the function foo) while
// R_X86_64_PC32 or R_X86_64_GOT32 are used for pointer-taking operations.
//
// Unfortunately, for data, we can't distinguish between address-taking
// relocations and non-address-taking ones. LLVM generates an "address
// significance" table in the ".llvm_addrsig" section to mark symbols
// whose addresses are taken in code. If that table is available, we use
// that information in this function. Otherwise, we conservatively assume
// that all data items are address-taken.
template <typename E>
void mark_addrsig(Context<E> &ctx) {
Timer t(ctx, "mark_addrsig");
void compute_address_significance(Context<E> &ctx) {
Timer t(ctx, "compute_address_significance");
// Flip address-taken bit for executable sections first.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->mark_addrsig(ctx);
for (std::unique_ptr<InputSection<E>> &src : file->sections)
if (src && src->is_alive && (src->shdr().sh_flags & SHF_ALLOC))
for (const ElfRel<E> &r : src->get_rels(ctx))
if (!is_func_call_rel(r))
if (InputSection<E> *dst = file->symbols[r.r_sym]->get_input_section())
if (dst->shdr().sh_flags & SHF_EXECINSTR)
dst->address_taken = true;
});
auto mark = [](Symbol<E> *sym) {
if (sym)
if (InputSection<E> *isec = sym->get_input_section())
isec->address_taken = true;
};
// Some symbols' pointer values are leaked to the dynamic section.
mark(get_symbol(ctx, ctx.arg.entry));
mark(get_symbol(ctx, ctx.arg.init));
mark(get_symbol(ctx, ctx.arg.fini));
// Exported symbols are conservatively considered address-taken.
if (ctx.dynsym)
for (Symbol<E> *sym : ctx.dynsym->symbols)
if (sym->is_exported)
mark(sym);
// Handle data objects.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
if (InputSection<E> *sec = file->llvm_addrsig.get()) {
u8 *p = (u8 *)sec->contents.data();
u8 *end = p + sec->contents.size();
while (p != end) {
Symbol<E> *sym = file->symbols[read_uleb(&p)];
if (InputSection<E> *isec = sym->get_input_section())
isec->address_taken = true;
}
} else {
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (isec && !(isec->shdr().sh_flags & SHF_EXECINSTR))
isec->address_taken = true;
}
});
}
@@ -1765,6 +1859,7 @@ void clear_padding(Context<E> &ctx) {
// <writable non-RELRO bss>
// <non-memory-allocated sections>
// <section header>
// .gdb_index
//
// .interp and some other linker-synthesized sections are placed at the
// beginning of a file because they are needed by loader. Especially on
@@ -1776,6 +1871,10 @@ void clear_padding(Context<E> &ctx) {
// particular, if .note.gnu.build-id is in a truncated core file, you
// can at least identify which executable has crashed.
//
// .gdb_index cannot be constructed before applying relocations to
// other debug sections, so we create it after completing other part
// of the output file and append it to the very end of the file.
//
// A PT_NOTE segment will contain multiple .note sections if exists,
// but there's no way to represent a gap between .note sections.
// Therefore, we sort .note sections by decreasing alignment
@@ -1821,6 +1920,8 @@ void sort_output_sections_regular(Context<E> &ctx) {
if (chunk == ctx.relplt)
return 11;
if (chunk == ctx.shdr)
return INT32_MAX - 1;
if (chunk == ctx.gdb_index)
return INT32_MAX;
bool alloc = (flags & SHF_ALLOC);
@@ -1834,30 +1935,33 @@ void sort_output_sections_regular(Context<E> &ctx) {
(!tls << 6) | (!relro << 5) | (is_bss << 4);
};
// Ties are broken by additional rules
auto get_rank2 = [&](Chunk<E> *chunk) -> i64 {
if (chunk->shdr.sh_type == SHT_NOTE)
return -chunk->shdr.sh_addralign;
ElfShdr<E> &shdr = chunk->shdr;
if (shdr.sh_type == SHT_NOTE)
return -shdr.sh_addralign;
if (chunk == ctx.got)
return 1;
if (chunk->name == ".toc")
return 2;
if (chunk->name == ".alpha_got")
if (chunk->name == ".toc")
return 3;
if (chunk->name == ".alpha_got")
return 4;
if (shdr.sh_flags & SHF_MERGE) {
if (shdr.sh_flags & SHF_STRINGS)
return (5LL << 32) | shdr.sh_entsize;
return (6LL << 32) | shdr.sh_entsize;
}
if (chunk == ctx.relro_padding)
return INT_MAX;
return INT64_MAX;
return 0;
};
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
// Sort sections by segments
i64 x = get_rank1(a);
i64 y = get_rank1(b);
if (x != y)
return x < y;
// Ties are broken by additional rules
return get_rank2(a) < get_rank2(b);
return std::tuple{get_rank1(a), get_rank2(a), a->name} <
std::tuple{get_rank1(b), get_rank2(b), b->name};
});
}
@@ -2229,8 +2333,9 @@ void compute_section_headers(Context<E> &ctx) {
chunk->update_shdr(ctx);
// Remove empty chunks.
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
return chunk->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
std::erase_if(ctx.chunks, [&](Chunk<E> *chunk) {
return chunk->kind() != OUTPUT_SECTION && chunk != ctx.gdb_index &&
chunk->shdr.sh_size == 0;
});
// Set section indices.
@@ -2423,7 +2528,7 @@ void fix_synthetic_symbols(Context<E> &ctx) {
// this symbol.
if (ctx._TLS_MODULE_BASE_) {
ctx._TLS_MODULE_BASE_->set_output_section(sections[0]);
ctx._TLS_MODULE_BASE_->value = ctx.tls_begin;
ctx._TLS_MODULE_BASE_->value = ctx.dtp_addr;
}
// __GNU_EH_FRAME_HDR
@@ -2459,10 +2564,6 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
}
// MIPS' _gp symbol.
if constexpr (is_mips<E>)
start(ctx._gp, ctx.extra.quickstart, 0x7ff0);
// __start_ and __stop_ symbols
for (Chunk<E> *chunk : sections) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
@@ -2637,6 +2738,7 @@ void show_stats(Context<E> &ctx) {
using E = MOLD_TARGET;
template int redo_main(Context<E> &, int, char **);
template void create_internal_file(Context<E> &);
template void apply_exclude_libs(Context<E> &);
template void create_synthetic_sections(Context<E> &);
@@ -2667,7 +2769,7 @@ template void create_output_symtab(Context<E> &);
template void apply_version_script(Context<E> &);
template void parse_symbol_version(Context<E> &);
template void compute_import_export(Context<E> &);
template void mark_addrsig(Context<E> &);
template void compute_address_significance(Context<E> &);
template void clear_padding(Context<E> &);
template void compute_section_headers(Context<E> &);
template i64 set_osec_offsets(Context<E> &);
+1 -1
View File
@@ -40,7 +40,7 @@ namespace mold::elf {
// Create linker-synthesized sections
template <typename E>
static void r_create_synthetic_sections(Context<E> &ctx) {
auto push = [&]<typename T>(T *x) {
auto push = [&](auto *x) {
ctx.chunks.push_back(x);
ctx.chunk_pool.emplace_back(x);
return x;
+43 -63
View File
@@ -30,8 +30,9 @@
namespace mold::elf {
using E = MOLD_TARGET;
// Returns a branch reach in bytes for a given target.
template <typename E>
static consteval i64 max_distance() {
// ARM64's branch has 26 bits immediate. The immediate is padded with
// implicit two-bit zeros because all instructions are 4 bytes aligned
@@ -60,32 +61,12 @@ static consteval i64 max_distance() {
// We create thunks for each 12.8/1.6/3.2 MiB code block for
// ARM64/ARM32/PPC, respectively.
template <typename E>
static constexpr i64 batch_size = max_distance<E>() / 10;
static constexpr i64 batch_size = max_distance() / 10;
// We assume that a single thunk group is smaller than 100 KiB.
static constexpr i64 max_thunk_size = 102400;
// Returns true if a given relocation is of type used for function calls.
template <typename E>
static bool needs_thunk_rel(const ElfRel<E> &r) {
u32 ty = r.r_type;
if constexpr (is_arm64<E>) {
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
} else if constexpr (is_arm32<E>) {
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL ||
ty == R_ARM_PLT32;
} else if constexpr (is_ppc32<E>) {
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
} else if constexpr (is_ppc64<E>) {
return ty == R_PPC64_REL24 || ty == R_PPC64_REL24_NOTOC;
} else {
static_assert(is_loongarch<E>);
return ty == R_LARCH_B26;
}
}
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
template <typename E>
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
@@ -135,10 +116,9 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -max_distance<E>() <= val && val < max_distance<E>();
return -max_distance() <= val && val < max_distance();
}
template <typename E>
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
for (Symbol<E> *sym : thunk.symbols) {
sym->extra.thunk_idx = -1;
@@ -148,7 +128,6 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
}
// Scan relocations to collect symbols that need thunks.
template <typename E>
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
RangeExtensionThunk<E> &thunk, i64 thunk_idx) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
@@ -157,7 +136,7 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (!needs_thunk_rel(rel))
if (!is_func_call_rel(rel))
continue;
// Skip if the symbol is undefined. apply_reloc() will report an error.
@@ -189,19 +168,19 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
}
}
template <typename E>
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
std::span<InputSection<E> *> m = osec.members;
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
using Thunk = RangeExtensionThunk<E>;
std::span<InputSection<E> *> m = members;
if (m.empty())
return;
m[0]->offset = 0;
// Initialize input sections with a dummy offset so that we can
// distinguish sections that have got an address with the one who
// haven't.
tbb::parallel_for((i64)1, (i64)m.size(), [&](i64 i) {
m[i]->offset = -1;
tbb::parallel_for_each(m, [](InputSection<E> *isec) {
isec->offset = -1;
});
// We create thunks from the beginning of the section to the end.
@@ -211,7 +190,7 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
// Input sections between B and C are in the current batch.
//
// A is the input section with the smallest address than can reach
// anywhere from the current batch.
// from the current batch.
//
// D is the input section with the largest address such that the thunk
// is reachable from the current batch if it's inserted right before D.
@@ -219,9 +198,9 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
// ................................ <input sections> ............
// A B C D
// ^ We insert a thunk for the current batch just before D
// <---> The current batch, which is smaller than batch_size
// <--------> Smaller than max_distance
// <--------> Smaller than max_distance
// <---> The current batch, which is smaller than BATCH_SIZE
// <--------> Smaller than MAX_DISTANCE
// <--------> Smaller than MAX_DISTANCE
// <-------------> Reachable from the current batch
i64 a = 0;
i64 b = 0;
@@ -233,10 +212,14 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
i64 t = 0;
while (b < m.size()) {
// Move D foward as far as we can jump from B to anywhere in a thunk at D.
// Move D foward as far as we can jump from B to a thunk at D.
auto d_thunk_end = [&] {
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
return align_to(d_end, Thunk::alignment) + max_thunk_size;
};
while (d < m.size() &&
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
m[b]->offset + max_distance<E>()) {
(b == d || d_thunk_end() <= m[b]->offset + max_distance())) {
offset = align_to(offset, 1 << m[d]->p2align);
m[d]->offset = offset;
offset += m[d]->sh_size;
@@ -247,34 +230,33 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
// to make sure that there's at least one section between B and C
// to ensure progress.
c = b + 1;
while (c < m.size() &&
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size<E>)
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
c++;
// Move A forward so that A is reachable from C.
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
while (a < m.size() && m[a]->offset + max_distance<E>() < c_offset)
while (a < b && m[a]->offset + max_distance() < c_offset)
a++;
// Erase references to out-of-range thunks.
while (t < osec.thunks.size() &&
osec.thunks[t]->offset < m[a]->offset)
reset_thunk(*osec.thunks[t++]);
while (t < thunks.size() && thunks[t]->offset < m[a]->offset)
reset_thunk(*thunks[t++]);
// Create a thunk for input sections between B and C and place it at D.
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
i64 thunk_idx = osec.thunks.size();
RangeExtensionThunk<E> *thunk = new RangeExtensionThunk<E>(osec, offset);
osec.thunks.emplace_back(thunk);
// Create a new thunk and place it at D.
offset = align_to(offset, Thunk::alignment);
i64 thunk_idx = thunks.size();
Thunk *thunk = new Thunk(*this, offset);
thunks.emplace_back(thunk);
// Scan relocations between B and C to collect symbols that need thunks.
// Scan relocations between B and C to collect symbols that need
// entries in the new thunk.
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
[&](InputSection<E> *isec) {
scan_rels(ctx, *isec, *thunk, thunk_idx);
});
// Now that we know the number of symbols in the thunk, we can compute
// its size.
// the thunk's size.
assert(thunk->size() < max_thunk_size);
offset += thunk->size();
@@ -307,18 +289,16 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
b = c;
}
while (t < osec.thunks.size())
reset_thunk(*osec.thunks[t++]);
while (t < thunks.size())
reset_thunk(*thunks[t++]);
osec.shdr.sh_size = offset;
this->shdr.sh_size = offset;
for (InputSection<E> *isec : members)
this->shdr.sh_addralign =
std::max<u32>(this->shdr.sh_addralign, 1 << isec->p2align);
}
using E = MOLD_TARGET;
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
} // namespace mold::elf
#endif
+16 -15
View File
@@ -160,18 +160,19 @@ u64 get_tp_addr(Context<E> &ctx) {
// of TLV template image when copying TLVs to the TLS block, so we need
// to offset it.
return align_down(phdr->p_vaddr - sizeof(Word<E>) * 2, phdr->p_align);
} else if constexpr (is_ppc<E> || is_m68k<E> || is_mips<E>) {
// On PPC and m68k, TP is 0x7000 (28 KiB) past the beginning of the TLV
// block to maximize the addressable range for load/store instructions
// with 16-bits signed immediates. It's not exactly 0x8000 (32 KiB) off
// because there's a small implementation-defined piece of data before
// the TLV block, and the runtime wants to access them efficiently too.
} else if constexpr (is_ppc<E> || is_m68k<E>) {
// On PowerPC and m68k, TP is 0x7000 (28 KiB) past the beginning
// of the TLV block to maximize the addressable range of load/store
// instructions with 16-bits signed immediates. It's not exactly 0x8000
// (32 KiB) off because there's a small implementation-defined piece of
// data before the initial TLV block, and the runtime wants to access
// them efficiently too.
return phdr->p_vaddr + 0x7000;
} else {
// RISC-V just uses the beginning of the main executable's TLV block as
// TP. RISC-V load/store instructions usually take 12-bits signed
// immediates, so the beginning of the TLS block ± 2 KiB is accessible
// with a single load/store instruction.
// RISC-V and LoongArch just uses the beginning of the main executable's
// TLV block as TP. Their load/store instructions usually take 12-bits
// signed immediates, so the beginning of the TLS block ± 2 KiB is
// accessible with a single load/store instruction.
static_assert(is_riscv<E> || is_loongarch<E>);
return phdr->p_vaddr;
}
@@ -185,12 +186,12 @@ u64 get_dtp_addr(Context<E> &ctx) {
if (!phdr)
return 0;
if constexpr (is_ppc<E> || is_m68k<E> || is_mips<E>) {
// On PPC64 and m68k, R_DTPOFF is resolved to the address 0x8000
if constexpr (is_ppc<E> || is_m68k<E>) {
// On PowerPC and m68k, R_DTPOFF is resolved to the address 0x8000
// (32 KiB) past the start of the TLS block. The bias maximizes the
// accessible range for load/store instructions with 16-bits signed
// immediates. That is, if the offset were right at the beginning of
// the start of the TLS block, the half of addressible space (negative
// accessible range of load/store instructions with 16-bits signed
// immediates. That is, if the offset were right at the beginning of the
// start of the TLS block, the half of addressible space (negative
// immediates) would have been wasted.
return phdr->p_vaddr + 0x8000;
} else if constexpr (is_riscv<E>) {
+10 -10
View File
@@ -12,42 +12,42 @@ set -x
case "$ID-$VERSION_ID" in
ubuntu-20.* | pop-20.*)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
apt-get install -y cmake gcc g++ g++-10
apt-get install -y file
;;
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
apt-get install -y cmake gcc g++
apt-get install -y file
;;
fedora-*)
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
dnf install -y gcc-g++ cmake
dnf install -y glibc-static file libstdc++-static diffutils util-linux
;;
opensuse-leap-*)
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
zypper install -y make cmake gcc-c++ gcc11-c++
zypper install -y glibc-devel-static tar diffutils util-linux
;;
opensuse-tumbleweed-*)
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
zypper install -y make cmake gcc-c++
zypper install -y glibc-devel-static tar diffutils util-linux
;;
gentoo-*)
emerge-webrsync
emerge dev-util/cmake sys-libs/zlib
emerge dev-util/cmake
;;
arch-*)
arch-* | archarm-* | artix-*)
pacman -Sy
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
pacman -S --needed --noconfirm base-devel cmake util-linux
;;
void-*)
xbps-install -Sy xbps
xbps-install -Sy bash make cmake openssl-devel zlib-devel gcc
xbps-install -Sy bash make cmake gcc
xbps-install -Sy tar diffutils util-linux
;;
alpine-*)
apk update
apk add bash make openssl-dev linux-headers cmake zlib-dev gcc g++
apk add bash make linux-headers cmake gcc g++
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
set -e
. /etc/os-release
set -x
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
# to enable cross-target tests.
#
# Feel free to send me a PR if your OS is not on this list.
case "$ID-$VERSION_ID" in
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
exit 1
esac
+49 -44
View File
@@ -1,86 +1,91 @@
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
function(add_target TRIPLE)
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
if(${HOST} MATCHES "amd64")
set(HOST x86_64)
elseif(${HOST} MATCHES "arm.*")
set(HOST arm)
elseif(${HOST} STREQUAL "ppc64")
set(HOST powerpc64)
elseif(${HOST} STREQUAL "ppc64le")
set(HOST powerpc64le)
endif()
# CMAKE_HOST_SYSTEM_PROCESSOR returns "aarch64" on ARM32 userland
# with ARM64 kernel. Here, we run `cc -dumpmachine` to determine the
# compiler's default target.
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpmachine
RESULT_VARIABLE EXIT_CODE
OUTPUT_VARIABLE ARCH
ERROR_QUIET)
if(NOT EXIT_CODE AND ARCH MATCHES "([^-]+).*")
set(HOST ${CMAKE_MATCH_1})
endif()
if(${TRIPLE} MATCHES "${HOST}-.*")
if(${HOST} MATCHES "amd64")
set(HOST x86_64)
elseif(${HOST} MATCHES "i386")
set(HOST i686)
elseif(${HOST} MATCHES "arm.*")
set(HOST arm)
elseif(${HOST} STREQUAL "powerpc64")
set(HOST ppc64)
elseif(${HOST} STREQUAL "powerpc64le")
set(HOST ppc64le)
endif()
function(add_target ARCH TRIPLE)
if(${ARCH} STREQUAL ${HOST})
set(IS_NATIVE 1)
endif()
if(${TRIPLE} MATCHES "([^-]+)-.")
set(MACHINE ${CMAKE_MATCH_1})
endif()
if(IS_NATIVE OR MOLD_ENABLE_QEMU_TESTS)
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
"*.sh")
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "*.sh")
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
"${MACHINE}_*.sh")
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "${ARCH}_*.sh")
list(APPEND TESTS ${ALL_TESTS})
foreach(TEST IN LISTS TESTS)
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${MACHINE}-${TEST}")
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}-${TEST}")
add_test(NAME ${TESTNAME}
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
WORKING_DIRECTORY ${mold_BINARY_DIR})
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT "HOST=${HOST}")
if(IS_NATIVE)
set_tests_properties(${TESTNAME} PROPERTIES
SKIP_REGULAR_EXPRESSION "skipped")
set_property(TEST ${TESTNAME} APPEND PROPERTY SKIP_REGULAR_EXPRESSION "skipped")
else()
set_tests_properties(${TESTNAME} PROPERTIES
ENVIRONMENT "TRIPLE=${TRIPLE}")
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT "TRIPLE=${TRIPLE}")
endif()
endforeach()
endif()
endfunction()
add_target(x86_64-linux-gnu)
add_target(i686-linux-gnu)
add_target(aarch64-linux-gnu)
add_target(arm-linux-gnueabihf)
add_target(riscv64-linux-gnu)
add_target(powerpc-linux-gnu)
add_target(powerpc64-linux-gnu)
add_target(powerpc64le-linux-gnu)
add_target(sparc64-linux-gnu)
add_target(s390x-linux-gnu)
add_target(sh4-linux-gnu)
add_target(alpha-linux-gnu)
add_target(mips64-linux-gnuabi64)
add_target(mips64el-linux-gnuabi64)
add_target(x86_64 x86_64-linux-gnu)
add_target(i686 i686-linux-gnu)
add_target(aarch64 aarch64-linux-gnu)
add_target(arm arm-linux-gnueabihf)
add_target(riscv64 riscv64-linux-gnu)
add_target(ppc powerpc-linux-gnu)
add_target(ppc64 powerpc64-linux-gnu)
add_target(ppc64le powerpc64le-linux-gnu)
add_target(sparc64 sparc64-linux-gnu)
add_target(s390x s390x-linux-gnu)
add_target(sh4 sh4-linux-gnu)
add_target(alpha alpha-linux-gnu)
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
if(MOLD_ENABLE_QEMU_TESTS_RV32)
add_target(riscv32-linux-gnu)
add_target(riscv32 riscv32-linux-gnu)
endif()
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
add_target(powerpc64le_power10-linux-gnu)
add_target(ppc64le powerpc64le_power10-linux-gnu)
endif()
option(MOLD_ENABLE_QEMU_TESTS_M68K "Enable tests for m68k" OFF)
if(MOLD_ENABLE_QEMU_TESTS_M68K)
add_target(m68k-linux-gnu)
add_target(m68k m68k-linux-gnu)
endif()
option(MOLD_ENABLE_QEMU_TESTS_LOONGARCH "Enable tests for LoongArch" OFF)
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH)
add_target(loongarch64-linux-gnu)
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH OR ${HOST} STREQUAL loongarch64)
add_target(loongarch64 loongarch64-linux-gnu)
endif()
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = aarch64 ] || skip
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
.global foo
.type foo, %function
.variant_pcs foo
foo:
ret
EOF
$CC -B. -shared -o $t/b.so $t/a.o
readelf -W --dyn-syms $t/b.so | grep foo | grep -q '[VARIANT_PCS]'
cat <<EOF | $CC -c -o $t/c.o -xc -
void foo();
int main() { foo(); }
EOF
$CC -B. -o $t/exe $t/c.o $t/b.so
readelf -W --dynamic $t/exe | grep -q AARCH64_VARIANT_PCS
-2
View File
@@ -6,8 +6,6 @@
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = s390x ] && skip
[ $MACHINE = alpha ] && skip
[ $MACHINE = mips64el ] && skip
[ $MACHINE = mips64 ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
@@ -23,4 +23,4 @@ $CC -B. -o $t/exe $t/a.o \
$OBJDUMP -dr $t/exe | grep -F -A7 '<fn1$thunk>:' > $t/log
grep -Eq 'bx\s+pc' $t/log
grep -Eq 'add\s+ip, ip, pc' $t/log
grep -Eq 'add\s+pc, ip, pc' $t/log
+1
View File
@@ -2,6 +2,7 @@
. $(dirname $0)/common.inc
[ $MACHINE = arm ] || skip
is_musl && skip
echo 'int main() {}' | $GCC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|| skip
+4 -7
View File
@@ -15,10 +15,7 @@ int fn2();
int main() { fn2(); }
EOF
$CC -B. -o $t/exe1 $t/a.o -L$t -Wl,--as-needed -lbar -Wl,--allow-shlib-undefined
readelf -W --dynamic $t/exe1 > $t/log1
! grep -q libfoo $t/log1 || false
$CC -B. -o $t/exe2 $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
readelf -W --dynamic $t/exe2 > $t/log2
grep -q libfoo $t/log2
$CC -B. -o $t/exe $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
readelf -W --dynamic $t/exe > $t/log2
grep -q libbar $t/log2
! grep -q libfoo $t/log2 || false
-32
View File
@@ -1,32 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -shared -fPIC -o $t/libfoo.so -Wl,--soname,libfoo.so -xc -
int foo() { return 3; }
EOF
cat <<EOF | $CC -shared -fPIC -o $t/libbar.so -Wl,--soname,libbar.so -xc -
int bar() { return 3; }
EOF
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
int foo();
int baz() { return foo(); }
EOF
$CC -B. -shared -o $t/libbaz.so -Wl,--soname,libbaz.so -L$t $t/a.o -lfoo
cat <<EOF | $CC -c -o $t/b.o -xc -
#include <stdio.h>
int baz();
int main() {
printf("%d\n", baz());
}
EOF
$CC -B. -o $t/exe $t/b.o -L$t -Wl,--as-needed -lbaz -lbar -lfoo
readelf --dynamic $t/exe > $t/log
grep -q libbaz $t/log || false
! grep -q libbar $t/log || false
grep -q libfoo $t/log || false
+28 -2
View File
@@ -14,10 +14,12 @@ canonical_name() {
esac
}
HOST=$(canonical_name $(uname -m))
if [ -z "$HOST" ]; then
HOST=$(canonical_name $(uname -m))
fi
# Set tool names
if [ "$TRIPLE" = "" ]; then
if [ -z "$TRIPLE" ]; then
MACHINE=$HOST
TESTDIR=out/test/elf/$HOST
CC="${TEST_CC:-cc}"
@@ -53,6 +55,19 @@ else
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
fi
if [ $MACHINE = x86_64 -o $MACHINE = i386 -o $MACHINE = arm ]; then
tlsdesc_opt=-mtls-dialect=gnu2
elif [ $MACHINE = aarch64 ]; then
tlsdesc_opt=-mtls-dialect=desc
fi
if [ $MACHINE = ppc64le -a -e /proc/cpuinfo ] && grep -q POWER10 /proc/cpuinfo; then
CC="$CC -mcpu=power10"
CXX="$CXX -mcpu=power10"
GCC="$GCC -mcpu=power10"
GXX="$GXX -mcpu=power10"
fi
# Common functions
test_cflags() {
echo 'int main() {}' | $CC "$@" -o /dev/null -xc - >& /dev/null
@@ -63,6 +78,17 @@ supports_ifunc() {
$CC -c -o /dev/null -xc - >& /dev/null
}
is_musl() {
ldd --help 2>&1 | grep -q musl
}
supports_tlsdesc() {
# musl's tlsdesc on arm32 seems to be broken
[ $MACHINE = arm ] && is_musl && return 1
[ -n "$tlsdesc_opt" ]
}
skip() {
echo skipped
trap - EXIT
-1
View File
@@ -4,7 +4,6 @@
# arm-linux-gnueabihf-objcopy crashes on x86-64
[ $MACHINE = arm ] && skip
[ $MACHINE = riscv32 ] && skip
[[ $MACHINE = mips* ]] && skip
command -v zstdcat >& /dev/null || skip
-1
View File
@@ -4,7 +4,6 @@
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -fPIC -shared -o $t/a.so -xc -
-1
View File
@@ -4,7 +4,6 @@
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
extern char readonly[100];
extern char readwrite[100];
static int segv = 0;
static jmp_buf buf;
void handler(int sig) {
segv = 1;
longjmp(buf, 1);
}
int main() {
signal(SIGSEGV, handler);
readwrite[0] = 5;
int x = segv;
if (setjmp(buf) == 0)
*(char *)readonly = 5;
int y = segv;
printf("sigsegv %d %d\n", x, y);
}
EOF
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
__attribute__((section (".data.rel.ro"))) char readonly[100] = "abc";
char readwrite[100] = "abc";
EOF
$CC -B. $t/a.o $t/b.so -o $t/exe -no-pie
$QEMU $t/exe | grep -q '^sigsegv 0 1$'
+2 -2
View File
@@ -6,8 +6,8 @@ void foo() {}
EOF
$CC -B. -o $t/b.so -shared $t/a.o -Wl,-default-symver
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so$'
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so'
$CC -B. -o $t/b.so -shared $t/a.o \
-Wl,--soname=bar -Wl,-default-symver
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar$'
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar'
+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 ] && ldd --help 2>&1 | grep -q musl && skip
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
void keep();
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
void foo() {}
EOF
rm -f $t/b.a
ar rcs $t/b.a $t/a.o
cat <<EOF | $CC -o $t/c.o -c -xc -
void foo();
int main() { foo(); }
EOF
$CC -B. -o $t/exe $t/c.o $t/b.a $t/b.a
! $CC -B. -o $t/exe $t/c.o -Wl,--push-state,--whole-archive \
$t/b.a $t/b.a -Wl,--pop-state 2> $t/log || false
grep -q 'duplicate symbol:.* foo' $t/log
+8 -8
View File
@@ -10,8 +10,8 @@ EOF
$CC -B. -o $t/exe $t/a.o
readelf --dyn-syms $t/exe > $t/log
! grep -q ' foo$' $t/log || false
! grep -q ' bar$' $t/log || false
! grep -q ' foo' $t/log || false
! grep -q ' bar' $t/log || false
cat <<EOF > $t/dyn
{ foo; bar; };
@@ -20,17 +20,17 @@ EOF
$CC -B. -o $t/exe1 $t/a.o -Wl,-dynamic-list=$t/dyn
readelf --dyn-syms $t/exe1 > $t/log1
grep -q ' foo$' $t/log1
grep -q ' bar$' $t/log1
grep -q ' foo' $t/log1
grep -q ' bar' $t/log1
$CC -B. -o $t/exe2 $t/a.o -Wl,--export-dynamic-symbol-list=$t/dyn
readelf --dyn-syms $t/exe2 > $t/log2
grep -q ' foo$' $t/log2
grep -q ' bar$' $t/log2
grep -q ' foo' $t/log2
grep -q ' bar' $t/log2
$CC -B. -o $t/exe3 $t/a.o -Wl,--export-dynamic-symbol=foo,--export-dynamic-symbol=bar
readelf --dyn-syms $t/exe3 > $t/log3
grep -q ' foo$' $t/log3
grep -q ' bar$' $t/log3
grep -q ' foo' $t/log3
grep -q ' bar' $t/log3
+8 -8
View File
@@ -14,8 +14,8 @@ EOF
$CXX -B. -o $t/exe $t/a.o $t/b.o
readelf --dyn-syms $t/exe > $t/log
! grep -q ' foo$' $t/log || false
! grep -q ' bar$' $t/log || false
! grep -q ' foo' $t/log || false
! grep -q ' bar' $t/log || false
cat <<EOF > $t/dyn
{ foo; extern "C++" { "baz(int)"; }; };
@@ -24,13 +24,13 @@ EOF
$CC -B. -o $t/exe1 $t/a.o $t/b.o -Wl,-dynamic-list=$t/dyn
readelf --dyn-syms $t/exe1 > $t/log1
grep -q ' foo$' $t/log1
! grep -q ' bar$' $t/log1 || false
grep -q ' _Z3bazi$' $t/log1
grep -q ' foo' $t/log1
! grep -q ' bar' $t/log1 || false
grep -q ' _Z3bazi' $t/log1
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,--export-dynamic-symbol-list=$t/dyn
readelf --dyn-syms $t/exe2 > $t/log2
grep -q ' foo$' $t/log2
! grep -q ' bar$' $t/log2 || false
grep -q ' _Z3bazi$' $t/log2
grep -q ' foo' $t/log2
! grep -q ' bar' $t/log2 || false
grep -q ' _Z3bazi' $t/log2
+12 -12
View File
@@ -21,19 +21,19 @@ EOF
$CC -B. -Wl,--dynamic-list=$t/dyn -o $t/exe1 $t/b.o
readelf --dyn-syms $t/exe1 > $t/log1
grep -q ' xyz$' $t/log1
! grep -q ' foobarzx$' $t/log1 || false
grep -q ' foobarcx$' $t/log1
grep -q ' foo123bar456bx$' $t/log1
! grep -q ' foo123bar456c$' $t/log1 || false
! grep -q ' foo123bar456x$' $t/log1 || false
grep -q ' xyz' $t/log1
! grep -q ' foobarzx' $t/log1 || false
grep -q ' foobarcx' $t/log1
grep -q ' foo123bar456bx' $t/log1
! grep -q ' foo123bar456c' $t/log1 || false
! grep -q ' foo123bar456x' $t/log1 || false
$CC -B. -Wl,--export-dynamic-symbol-list=$t/dyn -o $t/exe2 $t/b.o
readelf --dyn-syms $t/exe2 > $t/log2
grep -q ' xyz$' $t/log2
! grep -q ' foobarzx$' $t/log2 || false
grep -q ' foobarcx$' $t/log2
grep -q ' foo123bar456bx$' $t/log2
! grep -q ' foo123bar456c$' $t/log2 || false
! grep -q ' foo123bar456x$' $t/log2 || false
grep -q ' xyz' $t/log2
! grep -q ' foobarzx' $t/log2 || false
grep -q ' foobarcx' $t/log2
grep -q ' foo123bar456bx' $t/log2
! grep -q ' foo123bar456c' $t/log2 || false
! grep -q ' foo123bar456x' $t/log2 || false
+2 -2
View File
@@ -11,5 +11,5 @@ EOF
$CC -B. -shared -o $t/b.so $t/a.o
readelf --dyn-syms $t/b.so | grep -q 'bar1$'
readelf --dyn-syms $t/b.so | grep -q 'bar2$'
readelf --dyn-syms $t/b.so | grep -q ' bar1'
readelf --dyn-syms $t/b.so | grep -q ' bar2'
+1 -1
View File
@@ -62,7 +62,7 @@ if [ $MACHINE = x86_64 -o $MACHINE = aarch64 ]; then
fi
# riscv64-linux-gnu-strip crashes for some reason
if [ $MACHINE != riscv32 ] && [[ $MACHINE != mips* ]]; then
if [ $MACHINE != riscv32 ]; then
$CXX -B. -o $t/exe11 $t/b.o -pie
$STRIP $t/exe11
$QEMU $t/exe11
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = $HOST ] || skip
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
command -v gdb >& /dev/null || skip
test_cflags -gdwarf-5 -g -gdwarf64 || skip
cat <<EOF > $t/a.c
void fn3();
static void fn2() {
fn3();
}
void fn1() {
fn2();
}
EOF
cat <<EOF > $t/b.c
void fn5();
static void fn4() {
fn5();
}
void fn3() {
fn4();
}
EOF
cat <<EOF > $t/c.c
void fn7();
static void fn6() {
fn7();
}
void fn5() {
fn6();
}
EOF
cat <<EOF > $t/d.c
#include <stdio.h>
void trap() {}
static void fn8() {
printf("Hello world\n");
trap();
}
void fn7() {
fn8();
}
EOF
$CC -c -o $t/a.o $t/a.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
$CC -c -o $t/b.o $t/b.c -fPIC -g -ggnu-pubnames -gdwarf-4 -gdwarf64 -ffunction-sections
$CC -c -o $t/c.o $t/c.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64
$CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o
$CC -B. -shared -o $t/f.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
readelf -WS $t/f.so 2> /dev/null | grep -Fq .gdb_index
cat <<EOF | $CC -c -o $t/g.o -fPIC -g -ggnu-pubnames -gdwarf-5 -xc - -gz
void fn1();
int main() {
fn1();
}
EOF
# Older versions of gdb are buggy that they complain DWARF64 debug sections
# even without .gdb_index. Skip if such version.
$CC -B. -o $t/exe1 $t/e.so $t/g.o
DEBUGINFOD_URLS= gdb $t/exe1 -nx -batch -ex 'b main' -ex r -ex quit >& $t/log1
grep -q 'DW_FORM_line_strp pointing outside of .debug_line_str' $t/log1 && skip
# We are using a recent version of gdb.
$CC -B. -o $t/exe2 $t/f.so $t/g.o -Wl,--gdb-index
readelf -WS $t/exe2 2> /dev/null | grep -Fq .gdb_index
$QEMU $t/exe2 | grep -q 'Hello world'
DEBUGINFOD_URLS= gdb $t/exe2 -nx -batch -ex 'b main' -ex r -ex 'b trap' \
-ex c -ex bt -ex quit >& $t/log2
grep -q 'fn8 () at .*/d.c:6' $t/log2
grep -q 'fn7 () at .*/d.c:10' $t/log2
grep -q 'fn6 () at .*/c.c:4' $t/log2
grep -q 'fn5 () at .*/c.c:8' $t/log2
grep -q 'fn4 () at .*/b.c:4' $t/log2
grep -q 'fn3 () at .*/b.c:8' $t/log2
grep -q 'fn2 () at .*/a.c:4' $t/log2
grep -q 'fn1 () at .*/a.c:8' $t/log2
-1
View File
@@ -2,7 +2,6 @@
. $(dirname $0)/common.inc
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
# glibc 2.22 or prior have a bug that ld-linux.so.2 crashes on dlopen()
# if .rela.dyn and .rela.plt are not contiguous in a given DSO.
-1
View File
@@ -2,7 +2,6 @@
. $(dirname $0)/common.inc
# MIPS ABIs are not compatible with .gnu.hash
[[ $MACHINE = mips* ]] && skip
cat <<EOF | $CC -o $t/a.o -c -xc -
void foo() {}
+59
View File
@@ -0,0 +1,59 @@
#!/bin/bash
. $(dirname $0)/common.inc
# GCC 11 or older uses R_390_PLT32DBL to take an address of a function
# instead of R_390_PC32DBL
if [ $MACHINE = s390x ]; then
echo 'void *foo() { return foo; }' | $CC -c -o $t/a.o -xc -
readelf -r $t/a.o | grep -q R_390_PLT32DBL && skip
fi
cat <<EOF | $CC -c -o $t/a.o -ffunction-sections -fdata-sections -xc -
int bar() {
return 5;
}
int foo1(int x) {
return bar() + x;
}
int foo2(int x) {
return bar() + x;
}
int foo3(int x) {
return bar() + x;
}
EOF
cat <<EOF | $CC -c -o $t/b.o -ffunction-sections -fdata-sections -xc -
#include <stdio.h>
int foo1();
int foo2();
int foo3();
int main() {
printf("%d %d\n", foo1 == foo2, foo2 == foo3);
}
EOF
$CC -B. -o $t/exe1 -Wl,-icf=safe $t/a.o $t/b.o
$QEMU $t/exe1 | grep -q '^0 0$'
cat <<EOF | $CC -c -o $t/c.o -ffunction-sections -fdata-sections -xc -
int foo1();
int foo2();
int foo3();
int main() {
foo1();
foo2();
foo3();
}
EOF
$CC -B. -o $t/exe2 -Wl,-icf=safe $t/a.o $t/c.o
$QEMU $t/exe2 > $t/log2
! grep foo2 $t/log2 || false
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_ifunc || skip
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
typedef void Func();
void foo();
Func *get_foo() { return foo; }
EOF
$CC -B. -shared -o $t/b.so $t/a.o
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
#include <stdio.h>
typedef void Func();
__attribute__((ifunc("resolve_foo"))) void foo(void);
void real_foo(void) { printf("foo "); }
Func *resolve_foo() { return real_foo; }
Func *get_foo();
int main() {
printf("%p %p\n", foo, get_foo());
foo();
printf("\n");
}
EOF
$CC -B. -o $t/exe1 $t/c.o $t/b.so -no-pie
$QEMU $t/exe1 | grep -Eq '^(\S+) \1'
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_ifunc || skip
cat <<EOF | $CC -c -fno-PIE -o $t/a.o -xc -
#include <stdio.h>
typedef void Func();
__attribute__((ifunc("resolve_foo"))) void foo(void);
void real_foo(void) { printf("foo "); }
Func *resolve_foo() { return real_foo; }
__attribute__((ifunc("resolve_bar"))) void bar(void);
void real_bar(void) { printf("bar "); }
Func *resolve_bar() { return real_bar; }
EOF
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
typedef void Func();
void foo();
void bar();
Func *get_foo() { return foo; }
Func *get_bar() { return bar; }
EOF
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
#include <stdio.h>
typedef void Func();
void foo();
void bar();
Func *get_foo();
Func *get_bar();
int main() {
printf("%p %p %p %p\n", foo, get_foo(), bar, get_bar());
foo();
bar();
printf("\n");
}
EOF
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -no-pie
$QEMU $t/exe1 | grep -Eq '^(\S+) \1 (\S+) \2'
readelf --dynamic $t/exe1 > $t/log1
! grep -q TEXTREL $t/log1 || false
+1 -1
View File
@@ -2,7 +2,7 @@
. $(dirname $0)/common.inc
# musl does not support GNU-style init/fini priorities
ldd --help 2>&1 | grep -q musl && skip
is_musl && skip
cat <<'EOF' | $CC -c -o $t/a.o -xc -
#include <stdio.h>
-1
View File
@@ -3,7 +3,6 @@
# ARM32's strip command crashes on the output of this test for some reason.
[ $MACHINE = arm ] && skip
[[ $MACHINE = mips* ]] && skip
strip=$STRIP
command -v $strip >& /dev/null || skip
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc - -mcmodel=extreme
#include <stdio.h>
char msg[] = "Hello world\n";
int main() { printf(msg); }
EOF
$CC -B. -o $t/exe1 $t/a.o
$QEMU $t/exe1 | grep -q 'Hello world'
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -flto -c -o $t/a.o -xc -
void _start() {}
EOF
$CC -B. -o $t/exe -flto $t/a.o -nostdlib
readelf -Ws $t/exe | grep -Eq ' _start'
-1
View File
@@ -8,7 +8,6 @@
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = sh4 ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = mips* ]] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -shared -o $t/a.so -xc -
-1
View File
@@ -2,7 +2,6 @@
. $(dirname $0)/common.inc
[ $MACHINE = ppc64 ] && skip
[[ $MACHINE = mips* ]] && skip
# BusyBox's grep can't handle capture groups (e.g. \1, \2 ...)
grep --version 2>&1 | grep BusyBox && skip
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
#include <stdio.h>
void f0();
void f1();
void f2();
void f3();
void f4();
void f5();
void f6();
void f7();
void f8();
void f9();
void f10();
void f11();
void f12();
void f13();
void f14();
void f15();
void f16();
void f17();
void f18();
void f19();
void f0(int x) { printf("0 "); if (!x) f9(); }
void space0() { __asm__(".space 1024*1024"); }
void f1(int x) { printf("1 "); f8(x); }
void space1() { __asm__(".space 1024*1024"); }
void f2(int x) { printf("2 "); f7(x); }
void space2() { __asm__(".space 1024*1024"); }
void f3(int x) { printf("3 "); f6(x); }
void space3() { __asm__(".space 1024*1024"); }
void f4(int x) { printf("4 "); f5(x); }
void space4() { __asm__(".space 1024*1024"); }
void f5(int x) { printf("5 "); f10(x); }
void space5() { __asm__(".space 1024*1024"); }
void f6(int x) { printf("6 "); f4(x); }
void space6() { __asm__(".space 1024*1024"); }
void f7(int x) { printf("7 "); f3(x); }
void space7() { __asm__(".space 1024*1024"); }
void f8(int x) { printf("8 "); f2(x); }
void space8() { __asm__(".space 1024*1024"); }
void f9(int x) { printf("9 "); f1(x); }
void space9() { __asm__(".space 1024*1024"); }
void f10(int x) { printf("10 "); f19(x); }
void space10() { __asm__(".space 8*1024*1024"); }
void f11(int x) { printf("11 "); f18(x); }
void space11() { __asm__(".space 8*1024*1024"); }
void f12(int x) { printf("12 "); f17(x); }
void space12() { __asm__(".space 8*1024*1024"); }
void f13(int x) { printf("13 "); f16(x); }
void space13() { __asm__(".space 8*1024*1024"); }
void f14(int x) { printf("14 "); f15(x); }
void space14() { __asm__(".space 8*1024*1024"); }
void f15(int x) { printf("15 "); f0(x + 1); }
void space15() { __asm__(".space 8*1024*1024"); }
void f16(int x) { printf("16 "); f14(x); }
void space16() { __asm__(".space 8*1024*1024"); }
void f17(int x) { printf("17 "); f13(x); }
void space17() { __asm__(".space 8*1024*1024"); }
void f18(int x) { printf("18 "); f12(x); }
void space18() { __asm__(".space 8*1024*1024"); }
void f19(int x) { printf("19 "); f11(x); }
void space19() { __asm__(".space 8*1024*1024"); }
int main() {
f0(0);
printf("\n");
}
EOF
$CC -B. -o $t/exe $t/a.o
$QEMU $t/exe | grep -Eq '^0 9 1 8 2 7 3 6 4 5 10 19 11 18 12 17 13 16 14 15 0 $'
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
. $(dirname $0)/common.inc
# OneTBB isn't tsan-clean
nm mold | grep -q '__tsan_init' && skip
# Ubuntu 22.04 SH4 GCC is broken
[ $MACHINE = sh4 ] && skip
cat <<EOF | $CXX -c -o $t/a.o -xc++ -
void hello();
void world();
template <typename T>
struct Foo {
Foo() { hello(); }
};
template <typename T>
struct Bar {
Bar() { world(); }
};
void baz() {
Foo<int> foo;
Bar<int> bar;
}
EOF
cat <<EOF | $CXX -c -o $t/b.o -xc++ -
#include <iostream>
void hello() { std::cout << "Hello "; }
void world() { std::cout << "world\n"; }
void baz();
int main() {
baz();
}
EOF
./mold --relocatable -o $t/c.o $t/a.o
./mold --relocatable -o $t/d.o $t/b.o
$CXX -B. -o $t/exe $t/c.o $t/d.o
$QEMU $t/exe | grep -q 'Hello world'
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
. $(dirname $0)/common.inc
# OneTBB isn't tsan-clean
nm mold | grep -q '__tsan_init' && skip
cat <<EOF | $CC -c -o $t/a.o -xassembler -
.section .rodata.str1.1,"aMS",@progbits,1
val1:
.ascii "Hello \0"
.section .rodata.str1.1,"aMS",@progbits,1
val5:
.ascii "World \0"
EOF
./mold --relocatable -o $t/b.o $t/a.o
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b0\b.*Hello'
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b7\b.*World'
+1 -1
View File
@@ -17,4 +17,4 @@ EOF
echo "'$t/b.o' '$t/c.o'" > $t/rsp
$CC -o $t/exe $t/a.o -Wl,@$t/rsp
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp
+16
View File
@@ -0,0 +1,16 @@
#!/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/b.o'" > $t/rsp1
echo "@$t/rsp1" > $t/rsp2
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp2
+4 -4
View File
@@ -1,14 +1,14 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -march=rv64imafd_xfoo1p5 -o $t/a.o -c -xc -
cat <<EOF | $CC -march=rv64imafd_xtheadba1p5 -o $t/a.o -c -xc -
void foo() {}
EOF
# The compiler might not create .riscv.attributes
readelf --sections $t/a.o | grep -Fq .riscv.attributes || skip
cat <<EOF | $CC -march=rv64imafd_xfoo2p0 -o $t/b.o -c -xc -
cat <<EOF | $CC -march=rv64imafd_xtheadba2p0 -o $t/b.o -c -xc -
void bar() {}
EOF
@@ -17,7 +17,7 @@ void _start() {}
EOF
$CC -B. -nostdlib -o $t/exe $t/a.o $t/c.o
readelf -A $t/exe | grep -q '_xfoo1p5"'
readelf -A $t/exe | grep -q '_xtheadba1p5"'
$CC -B. -nostdlib -o $t/exe $t/a.o $t/b.o $t/c.o
readelf -A $t/exe | grep -q '_xfoo2p0"'
readelf -A $t/exe | grep -q '_xtheadba2p0"'
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = riscv64 -o $MACHINE = riscv32 ] || skip
cat <<EOF | $CC -o $t/a.o -c -xassembler -
.globl get_foo, get_foo2, get_bar, get_baz
get_foo:
lui a0, %hi(foo)
add a0, a0, %lo(foo)
ret
get_foo2:
lui a0, %hi(foo+0x10000000)
add a0, a0, %lo(foo)
ret
get_bar:
lui a0, %hi(bar)
add a0, a0, %lo(bar)
ret
get_baz:
lui a0, %hi(baz)
add a0, a0, %lo(baz)
ret
EOF
cat <<EOF | $CC -o $t/b.o -c -xassembler -
.globl foo, bar, baz
foo = 0xf00
bar = 0xba
baz = 0x11beef
EOF
cat <<EOF | $CC -o $t/c.o -c -xc -
#include <stdio.h>
int get_foo();
int get_foo2();
int get_bar();
int get_baz();
int main() {
printf("%x %x %x %x\n", get_foo(), get_foo2(), get_bar(), get_baz());
}
EOF
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -Wl,--no-relax
$QEMU $t/exe1 | grep -q 'f00 10000f00 ba 11beef'
$CC -B. -o $t/exe2 $t/a.o $t/b.o $t/c.o
$QEMU $t/exe2 | grep -q 'f00 10000f00 ba 11beef'
+2
View File
@@ -1,6 +1,8 @@
#!/bin/bash
. $(dirname $0)/common.inc
test_cflags -static || skip
cat <<EOF | $CC -c -o $t/a.o -xassembler -
.globl foo
.weak bar
-50
View File
@@ -1,50 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
# ARM assembler has a differnet grammar than the others.
# Concretely speaking, ARM as uses "@" as a start of a comment.
[ $MACHINE = arm ] && skip
cat <<'EOF' | $CC -c -o $t/a.o -x assembler -
.globl val1, val2, val3, val4, val5
.section .rodata.str1.1,"aMS",@progbits,1
val1:
.ascii "Hello \0"
.section .rodata.str4.4,"aMS",@progbits,4
.align 4
val2:
.ascii "world \0\0\0\0"
.section .rodata.foo,"aMS",@progbits,1
val3:
.ascii "foobar\0"
.section .rodata.cst8,"aM",@progbits,8
.align 8
val4:
.ascii "abcdefgh"
.section .rodatabar,"aMS",@progbits,1
val5:
.ascii "bar\0"
EOF
cat <<'EOF' | $CC -c -o $t/b.o -xc -
#include <stdio.h>
extern char val1, val2, val3, val4, val5;
int main() {
printf("%p %p %p %p %p\n", &val1, &val2, &val3, &val4, &val5);
}
EOF
$CC -B. -o $t/exe $t/a.o $t/b.o
readelf -p .rodata.str $t/exe | grep -q Hello
readelf -p .rodata.str $t/exe | grep -q world
readelf -p .rodata.str $t/exe | grep -q foobar
readelf -p .rodata.cst $t/exe | grep -q abcdefgh
readelf -p .rodatabar $t/exe | grep -q bar
+1 -1
View File
@@ -4,7 +4,7 @@
[ "$CC" = cc ] || skip
# ASAN doesn't work with LD_PRELOAD
nm mold-wrapper.so | grep -q '__[at]san_init' && skip
nm mold | grep -q '__[at]san_init' && skip
cat <<'EOF' | $CC -xc -c -o $t/a.o -
#include <stdio.h>
+1 -1
View File
@@ -12,4 +12,4 @@ $CC -B. -o $t/exe $t/a.o -Wl,--strip-debug
readelf -W --sections $t/exe > $t/log
! grep -Fq .debug_info $t/log || false
readelf -W --symbols $t/exe | grep -q ' bar$'
readelf -W --symbols $t/exe | grep -q ' bar'
+4 -4
View File
@@ -16,7 +16,7 @@ EOF
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
readelf -W --dyn-syms $t/c.so > $t/log
grep -q ' foo@TEST$' $t/log
grep -q ' bar@TEST$' $t/log
grep -q ' bar1$' $t/log
! grep -q ' foo@@TEST$' $t/log || false
grep -q ' foo@TEST' $t/log
grep -q ' bar@TEST' $t/log
grep -q ' bar1' $t/log
! grep -q ' foo@@TEST' $t/log || false
+3 -3
View File
@@ -19,7 +19,7 @@ EOF
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
readelf -W --dyn-syms $t/c.so > $t/log
grep -q ' foo@@TEST1$' $t/log
grep -q ' foo@TEST2$' $t/log
grep -q ' foo@TEST3$' $t/log
grep -q ' foo@@TEST1' $t/log
grep -q ' foo@TEST2' $t/log
grep -q ' foo@TEST3' $t/log
! grep -q ' foo$' $t/log || false
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc -
static _Thread_local int foo[10000] = { 3, [9999] = 5 };
int get_foo(int idx) { return foo[idx]; }
EOF
$CC -B. -shared -o $t/b.so $t/a.o
cat <<EOF | $GCC -fPIC -c -o $t/c.o -xc -
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
void *handle = dlopen(argv[1], RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s: %s: \n", argv[1], dlerror());
exit(1);
}
int (*get)(int) = dlsym(handle, "get_foo");
assert(get);
printf("%d %d %d\n", get(0), get(1), get(9999));
}
EOF
$CC -B. -o $t/exe $t/c.o -ldl
$QEMU $t/exe $t/b.so | grep -q '3 0 5'
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_tlsdesc || skip
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc - $tlsdesc_opt
static _Thread_local int foo[10000] = { 3, [9999] = 5 };
int get_foo(int idx) { return foo[idx]; }
EOF
$CC -B. -shared -o $t/b.so $t/a.o
cat <<EOF | $GCC -fPIC -c -o $t/c.o -xc - $tlsdesc_opt
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
void *handle = dlopen(argv[1], RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s: %s: \n", argv[1], dlerror());
exit(1);
}
int (*get)(int) = dlsym(handle, "get_foo");
assert(get);
printf("%d %d %d\n", get(0), get(1), get(9999));
}
EOF
$CC -B. -o $t/exe $t/c.o -ldl
$QEMU $t/exe $t/b.so | grep -q '3 0 5'
+3 -9
View File
@@ -1,15 +1,9 @@
#!/bin/bash
. $(dirname $0)/common.inc
if [ $MACHINE = x86_64 -o $MACHINE = arm ]; then
dialect=gnu2
elif [ $MACHINE = aarch64 ]; then
dialect=desc
else
skip
fi
supports_tlsdesc || skip
cat <<EOF | $GCC -fPIC -mtls-dialect=$dialect -c -o $t/a.o -xc -
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc - $tlsdesc_opt
#include <stdio.h>
extern _Thread_local int foo;
@@ -21,7 +15,7 @@ int main() {
}
EOF
cat <<EOF | $GCC -fPIC -mtls-dialect=$dialect -shared -o $t/b.so -xc -
cat <<EOF | $GCC -fPIC -shared -o $t/b.so -xc - $tlsdesc_opt
_Thread_local int foo = 5;
_Thread_local int bar;
EOF
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_tlsdesc || skip
cat <<EOF | $GCC -fPIC -fPIC -c -o $t/a.o -xc - $tlsdesc_opt
_Thread_local int foo = 5;
EOF
$CC -B. -shared -o $t/b.so $t/a.o
cat <<EOF | $GCC -fPIC -fPIC -c -o $t/c.o -xc - $tlsdesc_opt
extern _Thread_local int foo;
int get_foo1() { return foo; }
EOF
cat <<EOF | $GCC -fPIC -fPIE -c -o $t/d.o -xc - $tlsdesc_opt
#include <stdio.h>
extern _Thread_local int foo;
int get_foo1();
int get_foo2() { return foo; }
int main() {
printf("%d %d %d\n", foo, get_foo1(), get_foo2());
}
EOF
$CC -B. -o $t/exe1 $t/c.o $t/d.o $t/b.so
$QEMU $t/exe1 | grep -q '^5 5 5$'
readelf -Wr $t/exe1 > $t/log1
! grep -Eq 'TLS.?DESC' $t/log1 || false
$CC -B. -o $t/exe1 $t/c.o $t/d.o $t/b.so -Wl,--no-relax
$QEMU $t/exe1 | grep -q '^5 5 5$'
readelf -Wr $t/exe1 > $t/log2
grep -Eq 'TLS.?DESC' $t/log2
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_tlsdesc || skip
cat <<EOF | $GCC -fPIC -ftls-model=local-dynamic -c -o $t/a.o -xc - $tlsdesc_opt
extern _Thread_local int foo;
int get_foo() {
return foo;
}
static _Thread_local int bar = 5;
int get_bar() {
return bar;
}
EOF
cat <<EOF | $GCC -fPIC -ftls-model=local-dynamic -c -o $t/b.o -xc - $tlsdesc_opt
#include <stdio.h>
_Thread_local int foo;
int get_foo();
int get_bar();
int main() {
foo = 42;
printf("%d %d\n", get_foo(), get_bar());
return 0;
}
EOF
$CC -B. -o $t/exe1 $t/a.o $t/b.o
$QEMU $t/exe1 | grep -q '42 5'
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,--no-relax
$QEMU $t/exe2 | grep -q '42 5'
+7 -14
View File
@@ -1,17 +1,10 @@
#!/bin/bash
. $(dirname $0)/common.inc
supports_tlsdesc || skip
test_cflags -static || skip
if [ $MACHINE = x86_64 -o $MACHINE = arm ]; then
dialect=gnu2
elif [ $MACHINE = aarch64 ]; then
dialect=desc
else
skip
fi
cat <<EOF | $GCC -fPIC -mtls-dialect=$dialect -c -o $t/a.o -xc -
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc - $tlsdesc_opt
#include <stdio.h>
extern _Thread_local int foo;
@@ -22,12 +15,12 @@ int main() {
}
EOF
cat <<EOF | $GCC -fPIC -mtls-dialect=$dialect -c -o $t/b.o -xc -
cat <<EOF | $GCC -fPIC -c -o $t/b.o -xc - $tlsdesc_opt
_Thread_local int foo;
EOF
$CC -B. -o $t/exe $t/a.o $t/b.o -static
$QEMU $t/exe | grep -q 42
$CC -B. -o $t/exe1 $t/a.o $t/b.o -static
$QEMU $t/exe1 | grep -q 42
$CC -B. -o $t/exe $t/a.o $t/b.o -static -Wl,-no-relax
$QEMU $t/exe | grep -q 42
$CC -B. -o $t/exe2 $t/a.o $t/b.o -static -Wl,-no-relax
$QEMU $t/exe2 | grep -q 42
+18 -22
View File
@@ -1,53 +1,49 @@
#!/bin/bash
. $(dirname $0)/common.inc
if [ $MACHINE = x86_64 -o $MACHINE = arm ]; then
dialect=gnu2
elif [ $MACHINE = aarch64 ]; then
dialect=desc
else
skip
fi
supports_tlsdesc || skip
cat <<EOF | $GCC -fPIC -mtls-dialect=$dialect -c -o $t/a.o -xc -
cat <<EOF | $GCC -fPIC -c -o $t/a.o -xc - $tlsdesc_opt
extern _Thread_local int foo;
_Thread_local int bar = 3;
int get_foo() {
return foo;
}
static _Thread_local int bar = 5;
static _Thread_local int baz = 5;
int get_bar() {
return bar;
int get_baz() {
return baz;
}
EOF
cat <<EOF | $GCC -fPIC -mtls-dialect=$dialect -c -o $t/b.o -xc -
cat <<EOF | $GCC -fPIC -c -o $t/b.o -xc - $tlsdesc_opt
#include <stdio.h>
_Thread_local int foo;
extern _Thread_local int bar;
int get_foo();
int get_bar();
int get_baz();
int main() {
foo = 42;
printf("%d %d\n", get_foo(), get_bar());
printf("%d %d %d\n", get_foo(), bar, get_baz());
return 0;
}
EOF
$CC -B. -o $t/exe $t/a.o $t/b.o
$QEMU $t/exe | grep -q '42 5'
$CC -B. -o $t/exe1 $t/a.o $t/b.o
$QEMU $t/exe1 | grep -q '42 3 5'
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-no-relax
$QEMU $t/exe | grep -q '42 5'
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,-no-relax
$QEMU $t/exe2 | grep -q '42 3 5'
$CC -B. -shared -o $t/c.so $t/a.o
$CC -B. -o $t/exe $t/b.o $t/c.so
$QEMU $t/exe | grep -q '42 5'
$CC -B. -o $t/exe3 $t/b.o $t/c.so
$QEMU $t/exe3 | grep -q '42 3 5'
$CC -B. -shared -o $t/c.so $t/a.o -Wl,-no-relax
$CC -B. -o $t/exe $t/b.o $t/c.so -Wl,-no-relax
$QEMU $t/exe | grep -q '42 5'
$CC -B. -o $t/exe4 $t/b.o $t/c.so -Wl,-no-relax
$QEMU $t/exe4 | grep -q '42 3 5'
Loaded 100 of 936 files, more files were not shown because too many files have changed in this diff. Show more