Compare commits

..
1898 Commits
Author SHA1 Message Date
Rui UeyamaandClaude Opus 4.8 5e95e7a88a Remove orphaned comments and tidy ScriptExpr/ScriptCmd enums
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-13 14:40:42 +09:00
Rui UeyamaandClaude Fable 5 7e68606f78 Make the reader state optional for the Script class
Script needs the command line reader's state only for commands that
read other input files, such as GROUP or INPUT. Version scripts and
dynamic lists share the class just for the lexer, and their callers
had to fabricate a meaningless ReaderContext to satisfy the
constructor. The state is now passed as a nullable pointer and
fetched through an accessor that fails with a clear message if a
script uses a file-reading command where none is available.

Also assert that the task group is present where parse tasks are
spawned; a fabricated reader state reaching that path used to be a
null pointer dereference.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 12:49:56 +09:00
Rui UeyamaandClaude Fable 5 ffc170652f Update stale comments
The script engine has long outgrown "evaluates only a subset".

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 12:21:30 +09:00
Rui UeyamaandClaude Fable 5 8366016955 Merge mergeable sections under linker scripts
String merging used to be disabled when a linker script described the
layout, trading away deduplication. Now mergeable sections are merged
exactly as usual and the script lays out the result.

Input sections are matched against the script's patterns before they
are split into section pieces, since the patterns refer to the
original per-file sections. Sections matched by different output
section descriptions merge separately, which also gives each output
section its own deduplication domain, as in GNU ld and LLD. A merged
section that belongs to a script-described output section is then
laid out and written as a part of it — inside its boundary symbols
and segment assignments — rather than as a chunk of its own, and
symbols in it get the containing section's index.

Moving the matching earlier also means /DISCARD/ now applies to
mergeable sections, which used to be skipped because they were
already split by the time discards ran.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 14:03:04 +09:00
Rui UeyamaandClaude Fable 5 ebd2865fea Remove --section-order and --physical-image-base
These options were mold-specific substitutes for linker scripts,
added back when supporting the real language was off the table. Now
that SECTIONS, PHDRS and MEMORY are supported, a script expresses
everything they could and more, so they can go, along with the
third layout mode and the __phys_start_/__phys_stop_ symbols that
existed only for them.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 13:28:28 +09:00
Rui UeyamaandClaude Fable 5 cce86d402a Remove ctx.arg.entry_given
The entry symbol now starts out null and is filled in by -e, by the
first ENTRY command in a linker script, or by the _start default once
all input files have been read. This makes the precedence fall out of
the order in which the sources are consulted instead of a side flag,
and the gc-root and undefined-list registration that was duplicated
between the option and the script now happens in one place.

As a side effect, the first ENTRY command now wins over later ones,
as in LLD; it used to be the last.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 12:13:07 +09:00
Rui UeyamaandClaude Fable 5 187583f489 Support MEMORY regions
A MEMORY command defines named memory regions, the address spaces of
a microcontroller's flash and RAM. Each region has a single cursor:
a section placed with >region is allocated at it, and a load image
directed with AT>region consumes it too, which is how a RAM section's
initialization image is stacked after the code in flash. ORIGIN and
LENGTH are usable in expressions, REGION_ALIAS gives a region another
name, and a section that names no region and no address — including
an orphan — goes to the first region whose attributes accept it. A
section that doesn't fit its region is an error, reported only if it
persists once the layout has converged.

Since a script can give sections addresses in any order, the layout
no longer assumes monotonically increasing addresses: the ELF and
program headers are placed in front of the lowest-addressed section
rather than the first-laid one, and after convergence the engine
verifies that no two sections overlap, in virtual or in load address
space. (File offsets and program headers already handled arbitrary
address order.) The overlap check immediately caught orphans
colliding with region-placed sections, hence the orphan rule above.

With this, the stock STM32 vendor script links: vectors and code in
flash, data in RAM with its load address in flash, and the classic
_estack/_sidata symbols all come out as the startup code expects.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 9ffcd8453f Remove --dump-script
It was a debug aid for developing the linker script parser and has
outlived its purpose. It was also the source of a flaky test failure:
the flag called exit(0) while input files were still being parsed by
other threads, which occasionally crashed in static destructors and
produced an empty dump.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 ba77281c46 Support SORT_NONE
SORT_NONE(pattern) simply keeps the input order. GNU ld's own default
script uses it for .init and .fini, so any script derived from
`ld --verbose` output contains it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 53cbf48020 Make script-laid userland executables actually run, with tests
A new test that links an ordinary C program with a linker script and
executes it exposed a series of gaps that only freestanding programs
like the Linux kernel could tolerate:

- The ELF and program headers were never mapped, so AT_PHDR pointed
  at unmapped memory and glibc crashed in _dl_aux_init. They are now
  mapped if they fit in front of the first section, as in LLD; with
  an explicit PHDRS command, FILEHDR/PHDRS keywords decide instead.

- Orphan sections were anchored by an ad-hoc attribute-similarity
  score, which tore .tdata and .tbss apart (producing two PT_TLS
  segments) and placed .plt.got between data sections (creating an
  executable island in a page shared with data). An orphan is now
  inserted where the default section order would put it: after the
  last script section that the default order ranks no higher, with a
  preference for one of exactly the same class. Orphans sharing an
  insertion point are ordered among themselves by the default order.

- Laying out an orphan now first executes the script commands up to
  its insertion point, so orphans respect preceding location counter
  assignments.

- A TLS NOBITS section consumes no address space in script layout,
  matching the regular layout.

- A fill pattern now starts anew at each gap it fills, as in LLD,
  consistently between member gaps and data command gaps.

New tests: a vDSO-shaped shared library whose script places the
synthesized dynamic sections with FILEHDR/PHDRS segments; SIZEOF/ADDR
of declared-but-empty sections (the kernel's GOT assertions); a C
program laid out by a script, executed under qemu, both static and
dynamic; and fill patterns in location counter gaps.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 5f87604373 Evaluate symbol assignments during the address walk
The script engine used to have three separate evaluation timelines:
parse-time --defsym conversion, layout-time location counter bindings
recorded in three side fields of ScriptAssignment, and a post-layout
evaluation pass. Section bodies were additionally sized by a second,
restricted expression evaluator that worked in offset space and kept
its own map of body symbols. The deferred bindings also required a
heuristic to decide which section a boundary address belongs to.

Symbol assignments within SECTIONS are now executed by the address
walk itself, in script order, with the live location counter and the
section it lies in. This kills the offset-space evaluator, the
binding side fields and the boundary heuristic: a symbol defined
between two sections is naturally associated with the section just
laid out, which is also what GNU ld and LLD do. Since an expression
may refer to an address or a symbol that is computed later, the walk
runs repeatedly until the layout converges, with a bound to reject
divergent scripts like `. = sym;` where sym depends on `.`.

Assignments outside SECTIONS are still evaluated after the layout is
fixed, now also iterating to handle chains like
`PROVIDE(a = b); PROVIDE(b = 0x1000);` in either order.

SECTIONS is now rejected in relocatable links (it used to crash) and
in combination with --section-order (it used to silently win).

New tests cover bodies of sections that never materialize, transitive
PROVIDE chains, the `. = ASSERT(...)` idiom and KEEP(*(SORT(...))).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 d49fd79114 Add tests for script-driven layout and fix three bugs they found
New tests cover PHDRS (segment creation, FLAGS, :phdr references and
their inheritance), AT() load addresses and LOADADDR, data commands
with fill patterns, KEEP under --gc-sections, SORT_BY_NAME and
SORT_BY_INIT_PRIORITY, location counter arithmetic in section bodies,
(NOLOAD) and (INFO), orphan placement and --orphan-handling, the
association of a boundary symbol with the preceding section, and
per-file input section patterns.

They caught three bugs:

- AT() was honored only when a PHDRS command was present. The default
  program header logic now takes a section's load address from the
  script and starts a new PT_LOAD when the distance between virtual
  and load addresses changes.

- An output section materialized only for its data commands (e.g. the
  Linux kernel setup's .signature) skipped the finalization step, so
  its fill pattern was dropped.

- On architectures with range extension thunks, an executable section
  described by a script was laid out twice: once by the thunk-creation
  pass and once by the script engine. The stale thunks were then
  written at offsets that no longer existed, corrupting neighboring
  sections. Script-described sections now skip thunk creation; a
  branch out of range is reported as a relocation error. Proper thunk
  support within script layout is left for future work.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 2376604619 Fix the first-match-wins rule for linker script section patterns
MultiGlob::find returns the match with the largest value because
version script matching, its original use, gives the highest priority
to the last pattern. The script section matcher assumed the opposite,
so when two patterns matched the same input section, the later one
won. In the Linux kernel's compressed boot stub, the 1 MiB
.rodata..compressed payload was claimed by `*(.rodata.*)` instead of
its own output section, which shifted the stub's layout and made the
kernel triple-fault during decompression.

Ranks are now stored negated relative to INT32_MAX so that the
earliest pattern in the script has the largest value.

With this fix, a mold-linked Linux kernel boots: QEMU runs it to a
working PID 1, and the kernel banner duly credits `mold 2.41.0` as
the linker.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 73770696cd Lay out output sections as described by SECTIONS and PHDRS
mold can now execute the core of the linker script language: output
sections are formed by first-match-wins wildcard matching with KEEP,
EXCLUDE_FILE and nested SORT specifiers; bodies run their location
counter arithmetic, symbol assignments and BYTE/SHORT/LONG/QUAD data
commands; addresses follow SECTIONS order with AT() load addresses
tracked per chunk; orphan sections are placed after the most similar
script section; and a PHDRS command, including FILEHDR/PHDRS and
:phdr references with inheritance, takes over program header creation.

The engine lives almost entirely in the linker's serial passes. Input
section matching is a pure function applied in parallel, reusing the
MultiGlob machinery whose insertion order gives both the first-match-
wins rule and member ordering. Address assignment is a single forward
walk over the SECTIONS commands with explicit location counter and
LMA states; it re-runs whenever set_osec_offsets() does, so it
composes with section-size convergence. An output section body is
re-laid out once its address is known, which makes in-body ALIGN
exact without inflating the section alignment.

Some semantic points worth noting:

- PROVIDE now defines a symbol only if it is referenced by an input
  file or, transitively, by an evaluated script expression; an
  unreferenced PROVIDE's value expression is not evaluated at all.
- A symbol defined between two sections is associated with the
  preceding section, as in GNU ld. The Linux kernel depends on this:
  `_end` must belong to .brk, or objcopy --remove-section=.modinfo
  would strip it.
- SIZEOF()/ADDR() of a script-declared section that didn't
  materialize evaluate to zero, and our always-created placeholder
  .got/.got.plt are not placed by script descriptions, so assertions
  like the kernel's ASSERT(SIZEOF(.got) == 0) behave as with GNU ld.
- Global symbols now keep their binding in the output symbol table as
  in GNU ld and LLD, instead of being demoted to local when not
  exported. The kernel build reads nm output of a previous link, so
  the bindings are load-bearing. Hidden visibility and a version
  script's `local:` still demote.
- A mergeable section's size is no longer padded up to its alignment
  if that would make it indivisible by sh_entsize; libelf-based tools
  such as objtool reject such sections.
- SHF_MERGE sections are not deduplicated when a script drives the
  layout, so that the script's patterns place their contents.
- --orphan-handling=place|warn|error is supported, and the
  --[no-]warn-rwx-segments flags are accepted.

With this change, mold builds a booting Linux kernel (x86-64
defconfig, QEMU-tested) with the kernel's unmodified linker scripts:
vmlinux, the vDSO, the compressed boot stub and the 16-bit setup code
all link with mold.

Constructs we cannot faithfully execute yet, such as MEMORY region
placement and OVERLAY, are still rejected with an error.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 abf35a55c0 Support /DISCARD/ in linker scripts
A SECTIONS command whose output section descriptions consist only of
/DISCARD/ can now be executed: input sections matching the discard
patterns are removed from the output before garbage collection. File
patterns and EXCLUDE_FILE are honored.

This introduces the first piece of the script section-matching engine.
All patterns are compiled into a MultiGlob whose insertion order gives
the first-match-wins semantics, the same machinery version scripts
use. Sections are matched in parallel; the per-candidate file
constraint check happens only for the rare patterns that have one.

Discarding a mergeable section is rejected for now because its
contents have already been split into section pieces by the time
scripts are evaluated.

SECTIONS commands that lay out output sections are still rejected.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 103348b4a5 Evaluate ENTRY, symbol assignments, PROVIDE and ASSERT in linker scripts
This change implements the linker script commands that do not require
script-driven section layout:

ENTRY(sym) sets the entry point. The -e option takes precedence.

Symbol assignments may now use arbitrary expressions, e.g.
`text_end = ADDR(.text) + SIZEOF(.text);`. Such expressions can only
be computed once the output file layout is fixed, so they are
evaluated at the end of fix_synthetic_symbols(), piggybacking on the
existing --defsym machinery for symbol creation. An expression value
tracks which output section it is relative to, so a symbol defined
this way remains correct in position-independent executables.

PROVIDE and PROVIDE_HIDDEN define a symbol only if no input file
defines it. They are handled by a new pass that runs between symbol
resolution and compute_import_export(), so version scripts and symbol
export treat them exactly like any other defined symbol.

ASSERT conditions are checked after the layout is finalized.

The expression evaluator supports the C operators with their usual
precedence and ABSOLUTE, ADDR, ALIGN, ALIGNOF, ASSERT, BLOCK,
CONSTANT(MAXPAGESIZE), DEFINED, LOG2CEIL, MAX, MIN, SEGMENT_START,
SIZEOF and SIZEOF_HEADERS. The ternary operator and && and || are
lazily evaluated to support idioms like `DEFINED(foo) ? foo : 0x1000`.

Commands that do require script-driven layout, such as SECTIONS and
MEMORY, are still rejected with an error.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 5d2c0306f0 Parse the entire linker script language
This change makes mold's parser understand the GNU linker script
language in its entirety: SECTIONS with all output section attributes
and input section patterns, MEMORY, PHDRS, OVERLAY, INSERT,
REGION_ALIAS, ASSERT, data commands, INCLUDE and the full expression
grammar. Scripts are parsed into ScriptCmd/ScriptExpr trees, which are
plain data structures that can be printed with a new hidden
--dump-script flag for debugging.

Parsing and evaluation are now separate. We still evaluate only the
commands we have always supported (INPUT, GROUP, VERSION and defsym-
style assignments) plus a few new trivial ones (EXTERN, SEARCH_DIR,
INCLUDE). Everything else is a hard error pointing at the offending
command. The principle is that mold must never silently create an
output file whose layout differs from what a script demands; an
unsupported command is reported loudly instead of being ignored.
Evaluation of SECTIONS and friends will be built on top of this parser
in subsequent changes.

Since the language's lexical rule is context-dependent (`*` is a
wildcard in a pattern but an operator in an expression), the parser
switches the lexer between three modes. If lookahead tokens exist when
the mode changes, they are simply un-lexed and re-lexed in the new
mode, which is possible because tokens are string_views into the
mapped file.

As a bonus, `foo = 0x1000;` in a script now defines an absolute symbol
as --defsym does; it previously defined foo as an alias for a symbol
literally named "0x1000".

GNU ld's own 250-line internal default script (`ld --verbose` output)
parses and round-trips through --dump-script.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 748f35f4f2 Make the linker script lexer lazy
We used to tokenize an entire linker script upfront. That doesn't work
for the full linker script grammar because the language is context-
dependent; for example, `*` is a wildcard in an input section pattern
but a multiplication operator in an expression, so `foo*bar` must be
lexed as one token in the former context and three in the latter.

GNU ld and LLD handle this with a lexer whose behavior is switched by
the parser. As a first step toward that, this change makes the lexer
produce tokens on demand as the parser consumes them. No behavior
change.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 d7df43a38e Refactor the linker script parser to use a cursor
The linker script parser used to thread a span of remaining tokens
through every function, with each function returning what it didn't
consume. That style worked for the tiny grammar we currently support,
but it doesn't scale to the full SECTIONS/MEMORY/PHDRS grammar with
an expression language, which needs deeply recursive descent.

This change converts the parser to a conventional stateful cursor
with peek()/next()/consume()/skip() primitives. No behavior change.

This is a groundwork commit for full linker script support.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 85c115e1be Sign-extend in-place addends of i386 relocations
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:37 +09:00
Rui UeyamaandClaude Opus 4.8 d0aa95abe9 Support the SFrame v3 stack-unwinding format
SFrame is a compact alternative to .eh_frame used for asynchronous stack
unwinding by profilers and debuggers. As of binutils 2.46, gas emits
.sframe (Version 3) by default with -Wa,--gsframe, so object files now
routinely carry it. Like .eh_frame, .sframe can't be treated as opaque
bytes: an output section is a single header followed by a PC-sorted index
of Function Descriptor Entries (FDEs) and a blob of Frame Row Entries
(FREs), so the linker has to parse the inputs and rebuild it.

This adds SFrame Version 3 support. We parse each input .sframe into its
FDEs, drop the entries for garbage-collected or ICF-folded functions,
concatenate the survivors, sort the index by function address and rewrite
the header. The FRE data carries no relocations and is position
independent, so it is copied verbatim; only each FDE's func_start is
relocated, which we resolve to a PC-relative offset (the output always
sets SFRAME_F_FDE_FUNC_START_PCREL). A PT_GNU_SFRAME segment is emitted so
the runtime can find the section.

For a relocatable output we can't resolve addresses yet, so the merged
index is left unsorted and each func_start is emitted as a relocation in
.rela.sframe for the final link to resolve, matching GNU ld -r.

We support only Version 3. A .sframe with the wrong magic is reported as
an error, whereas a section written in an older version is ignored.

Fixes https://github.com/rui314/mold/issues/1511

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-09 20:16:15 +09:00
Rui Ueyama eb273d1c1c Refactor 2026-06-07 19:58:57 +09:00
Rui Ueyama 3e3687e64e Merge pull request #1577 from bamo/fix/no-interp-on-shared
Don't emit PT_INTERP for -shared outputs
2026-06-07 19:37:50 +09:00
Rui Ueyama 8f598a0f96 Add a missing dependency 2026-06-07 14:25:14 +09:00
Rui Ueyama 6a2f747bf9 Fix -Wformat-truncation 2026-06-07 14:25:03 +09:00
Rui Ueyama 9c24d197c1 Merge pull request #1597 from minhbq-99/fix-align
Fix PT_LOAD alignment issue
2026-06-07 12:55:57 +09:00
Rui UeyamaandClaude Opus 4.8 5beb02714c Increase thunk batch size and tie max_thunk_size to it
Large C++ debug libraries on ARM64 (e.g. deal.II's libdeal_II.g.so) can
have a single batch reference enough distinct PLT-routed DSO imports that
the first-pass thunk group exceeds the old fixed 1 MiB max_thunk_size,
tripping the assertion in create_range_extension_thunks().

Replace the branch_distance/5 formula with explicit per-target batch
sizes (32/8/16 MiB for ARM64/ARM32/PPC) and derive max_thunk_size from
the batch as batch_size/2 (16/4/8 MiB). The reservation in d_thunk_end()
already accounts for max_thunk_size, so a larger cap is safe by
construction, and batch_size/2 is about the largest reservation ARM32's
16 MiB branch range can accommodate without throttling the batch.

Add a regression test that calls 50,000 imported functions from a single
function, which on ARM64 produces a first-pass thunk group well over the
old 1 MiB limit.

Fixes https://github.com/rui314/mold/issues/1538

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-07 00:20:34 +09:00
Rui UeyamaandClaude Opus 4.8 4afb95ac5b Support SPARC64's large PLT format
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-07 00:20:30 +09:00
Rui UeyamaandClaude Opus 4.8 2d45a8eab7 Make the HWASAN test work with older Clang
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 19:49:37 +09:00
Rui UeyamaandClaude Opus 4.8 a0c46e95d3 Support R_AARCH64_MOVW_PREL_G* relocations
Fixes https://github.com/rui314/mold/issues/1592

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 18:50:54 +09:00
Rui UeyamaandClaude Opus 4.8 2a90660aa0 Fix invalid artifact name for distros containing / or :
actions/upload-artifact rejects names with a forward slash or colon, so
the test-results upload failed for the opensuse/tumbleweed matrix entry
(and would have failed for gentoo/stage3, debian:11, and the ubuntu:*
entries on a test failure). Sanitize matrix.distro by mapping / and :
to - before using it as the artifact name.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 18:13:22 +09:00
Rui UeyamaandClaude Opus 4.8 f147d78a56 Make --emit-relocs reflect relaxed instructions on RISC-V and LoongArch
After relaxation, the relocations attached to the relaxed instructions no
longer match the new, shorter instructions. With --emit-relocs we copied the
input relocations verbatim, emitting stale types at stale offsets.

Rewrite each relocation's offset and type in RelocSection::copy_buf to match
the relaxed code, recomputing the outcome the same way apply_reloc_alloc
recomputes its instruction rewrites. -r output is not relaxed, so its
relocations are passed through unchanged.

Fixes https://github.com/rui314/mold/issues/1584

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 17:31:57 +09:00
Rui Ueyama aa52f90d69 Simplify 2026-06-06 13:15:32 +09:00
Rui UeyamaandClaude Opus 4.8 7a66f71796 Set sh_entsize and sh_addralign correctly for Android packed relocs
The Android packed relocation section (SHT_ANDROID_RELA/REL) holds a
variable-length SLEB128-encoded delta stream, not a table of fixed-size
entries, so sh_entsize should be 0 and the byte stream needs no word
alignment. We were inheriting sizeof(ElfRel) and sizeof(Word) from the
plain .rela.dyn path. These fields aren't consulted at runtime (bionic
locates the blob via DT_ANDROID_RELA/RELASZ), so this only corrects the
metadata and lets the section pack without spurious padding.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 12:52:17 +09:00
Rui Ueyama 2708ba6201 Fix LTO COMDAT owner selection race 2026-06-06 12:52:17 +09:00
Rui Ueyama 400dfad97c Fix Android packed reloc size oscillation
Fixes https://github.com/rui314/mold/issues/1595
2026-06-06 12:52:17 +09:00
Rui Ueyama 6841e77230 Merge pull request #1568 from gruenich/feature/update-github-actions
[ci] Update GitHub Actions to their latest major releases
2026-06-05 15:48:38 +09:00
Rui Ueyama e9c3d1629f Merge pull request #1599 from agrieve/icf-plt
icf: Do not fold relocations pointing to preemptible symbols
2026-06-04 21:33:52 +09:00
Rui Ueyama b91f3363e1 Merge pull request #1600 from agrieve/icf-alignment
icf: Merge alignments of folded sections
2026-06-04 16:17:22 +09:00
Andrew Grieve 899655b2c1 icf: Merge alignments of folded sections
When folding identical sections, ICF was discarding the alignment of the
folded sections and only keeping the alignment of the chosen leader. If
a section with stricter alignment (e.g. 4-byte) was folded into a
section with looser alignment (e.g. 1-byte), the folded function could
be placed at an odd address.

On x86/x86_64, member function pointers use the lowest bit of the
address for virtual/non-virtual distinction. If a non-virtual member
function is folded and placed at an odd address, it is incorrectly
interpreted as virtual, leading to crashes. This fix ensures that we
update the leader's alignment to be the maximum alignment of all folded
sections. We do this in a global pass over all sections (both leaf and
non-leaf merged sections) before the sweep phase.

Fixes #1596
2026-06-03 15:13:31 -04:00
Andrew Grieve 17e705efaf icf: Do not fold relocations pointing to preemptible symbols
If a relocation points to a preemptible (imported) symbol, it should not
be considered identical to a relocation pointing to a local symbol, even
if their targets currently have identical bodies. Preemptible symbols
can be overridden at runtime, so we must preserve their unique identity.

Fixes #1598
2026-06-03 12:45:16 -04:00
Bui Quang Minh ad4a683ff6 Fix PT_LOAD alignment issue
With a PT_LOAD segment, we need to make sure p_offset % p_align ==
p_vaddr % p_align. In set_file_offsets, we try to assign contiguous file
offsets when sections are contiguous in memory. But when the alignment
of section changes and it's bigger than page size, we cannot do that.
The offset might need to be adjusted to follow the alignment rule.

A test binary with
__attribute__((section(".sum"))) __attribute__((aligned(8192))) void sum() {}

Section Headers:

[16] .plt.got          PROGBITS        00000000000015c0 0005c0 000008 00  AX  0   0 16
[17] .sum              PROGBITS        0000000000002000 001000 00000b 00  AX  0   0 8192

.sum section is assigned contiguous offset to its previous section. As a
result, in create_phdr, .sum is mapped to the same PT_LOAD segment as
previous sections.

Program Headers:

LOAD           0x000588 0x0000000000001588 0x0000000000001588 0x000b80 0x000b80 R E 0x2000

p_offset = 0x588
p_vaddr = 0x1588
p_align = 0x2000

So p_offset % p_align != p_vaddr % p_align, it violates the alignment
rule. As a result, if this scenario happens to a shared library, it
cannot be dlopen() on a system with glibc < 2.35 which is before this
commit:
https://github.com/bminor/glibc/commit/163f625cf9becbb82dfec63a29e566324129c0cd.

Fix it by not assigning contiguous offset when section's alignment is
different and it's bigger than page size.
2026-06-02 22:08:51 +07:00
Christoph Grüninger d557daa268 [ci] Update GitHub Actions to their latest major releases
Fixes most deprecation warnings that Node20.js will stop working
in June.
2026-05-30 22:02:11 +02:00
Rui Ueyama 4f08de1147 Simplify 2026-05-20 20:53:55 +09:00
Rui Ueyama bdb00bc558 Simplify 2026-05-20 20:27:15 +09:00
Bennett Amodio abcbd451b4 perf: cache symbol names to avoid per-call strlen
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
2026-05-20 19:40:13 +09:00
Bennett Amodio f1b4be87f6 perf: cache InputSection name to avoid per-call strlen
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.

Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
2026-05-20 19:40:09 +09:00
Rui Ueyama 441f231dbf Revert "perf: cache symbol names to avoid per-call strlen"
This reverts commit 5f272c4184 as it
broke tests.
2026-05-20 10:22:36 +09:00
Rui Ueyama 1f44c6c6df Revert "perf: cache InputSection name to avoid per-call strlen"
This reverts commit c32946dfab as it
broke tests.
2026-05-20 10:22:25 +09:00
Rui Ueyama 154e99b2ff Merge pull request #1588 from bamo/perf/cache-symbol-names
perf: cache symbol names to avoid per-call strlen
2026-05-20 10:03:35 +09:00
Rui Ueyama 048b7b2dfd Merge pull request #1587 from bamo/perf/cache-input-section-name
perf: cache InputSection name to avoid per-call strlen
2026-05-20 09:56:50 +09:00
Rui Ueyama 8b5eeb84fa Merge pull request #1589 from bamo/perf/concurrentmap-prefault
perf: pre-fault ConcurrentMap pages with MAP_POPULATE
2026-05-20 08:54:55 +09:00
Rui Ueyama 3c018ca56f Merge pull request #1586 from andreas-schwab/main
Fix ambiguous readelf option in gdb-index-dwarf5 test
2026-05-20 08:07:14 +09:00
Bennett Amodio 4a9e30defd perf: pre-fault ConcurrentMap pages with MAP_POPULATE
ConcurrentMap::insert reads ent.key before writing, so on an empty
slot the read first installs a PTE pointing at the system zero page,
then the CAS triggers wp_page_copy and a cross-CPU TLB shootdown.
Under heavy parallel insertion this shows up as ~3% of link time in
smp_call_function_many_cond plus a much larger inclusive cost in
asm_exc_page_fault.

Pass MAP_POPULATE to pre-fault the whole map in the mmap() thread.
Reads no longer install zero-page PTEs and writes no longer trigger
CoW. Linking clang-23 Debug is ~5% faster, llvm-tblgen ~7%,
clang-tblgen ~9%; max RSS rises by 0.1-0.6%. Falls back cleanly on
platforms that don't define MAP_POPULATE.
2026-05-19 14:09:59 -07:00
Bennett Amodio 5f272c4184 perf: cache symbol names to avoid per-call strlen
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
2026-05-19 09:58:36 -07:00
Bennett Amodio c32946dfab perf: cache InputSection name to avoid per-call strlen
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.

Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
2026-05-19 08:34:17 -07:00
Andreas Schwab e77e9d52b8 Fix ambiguous readelf option in gdb-index-dwarf5 test
The binutils readelf command now accepts a --debug-dir=<DIR> option which
makes --debug=gdb_index ambiguous.
2026-05-19 17:06:06 +02:00
Rui Ueyama a2e670e2a4 Merge pull request #1581 from marxin/test-permissions
chore(tests): add missing +x for 2 tests
2026-05-18 10:20:07 +09:00
Rui UeyamaandClaude Opus 4.7 019fb43e8c Simplify test
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-18 10:14:50 +09:00
Rui UeyamaandClaude Opus 4.7 61933bfd79 Fix relative response file paths failing after -C on emulation redo
Commit 6d6a128b moved the -C chdir(2) into parse_nonpositional_args(),
which is invoked by both the speculative X86_64 mold_main() and the
re-run via redo_main(). When -C is in effect, the second invocation
re-entered expand_response_files() from the post-chdir directory, so
@rsp with a relative path could no longer be opened.

Save the cwd before parsing and restore it before redo_main() so the
re-run resolves relative paths from the original directory. Also
switch the chdir call itself to std::filesystem::current_path().

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-18 10:13:42 +09:00
Rui Ueyama b4bb6a856d Merge pull request #1579 from bamo/fix/gdb-index-rnglistx
--gdb-index: use DW_FORM_rnglistx index instead of iterating all offset entries
2026-05-18 10:12:02 +09:00
Rui Ueyama b7bbf9c15c Merge pull request #1580 from bamo/fix/msys-windows-include
Include <windows.h> directly in mold.h
2026-05-18 10:11:29 +09:00
Martin Liska a3affe8789 chore(tests): add missing +x for 2 tests 2026-05-13 22:05:58 +02:00
Bennett Amodio 87b2889254 Include <windows.h> directly where Windows APIs are used
src/mold.h (HANDLE in MappedFile) and lib/perf.cc (FILETIME /
GetProcessTimes / GetCurrentProcess) both use Windows APIs inside
_WIN32 blocks but neither included <windows.h>. The MSYS2 UCRT64 build
worked because <tbb/enumerable_thread_specific.h> (pulled in via
lib/lib.h) used to #include <windows.h> for its TLS code.

oneTBB 2023.0.0 replaced that bulk include with forward declarations
of just the TLS functions ("declare prototypes of just the needed TLS
functions, instead of including whole windows.h"). When MSYS2 picked
up 2023.0.0, build-msys started failing with "'HANDLE' does not name
a type" at src/mold.h:224 (and the same shape of error in lib/perf.cc
after the mold.h fix landed).

Include <windows.h> explicitly in both translation units so the build
doesn't rely on transitive paths through third-party headers.

Last passing run: https://github.com/rui314/mold/actions/runs/25585325507/job/75112599227
First failing run: https://github.com/rui314/mold/actions/runs/25767054228/job/75681837767
2026-05-13 10:24:06 -07:00
Bennett Amodio 70ca8ffa53 --gdb-index: use DW_FORM_rnglistx index instead of iterating all offset entries
A compile unit's DW_AT_ranges attribute in DW_FORM_rnglistx form holds an
index into the offset table at DW_AT_rnglists_base; the corresponding
entry locates the range list for that DIE. mold was ignoring the index
and iterating every offset, picking up range lists belonging to child
DIEs (subprograms, lexical_blocks, inlined_subroutines) that use
DW_FORM_rnglistx as well.

The resulting .gdb_index address table contained child-DIE ranges nested
inside the CU's range. GDB 17 detects the overlap, prints "warning:
.gdb_index address table has a range (...) that overlaps with an earlier
range, ignoring .gdb_index", and discards the index. Older GDBs accept
the bloated table without complaint -- the duplicate entries all point
to the same CU, so lookups still resolve correctly -- which is why this
went undetected for years.

The bug only triggers when a CU contains a non-contiguous lexical_block,
which clang emits for scopes with EH landing pads from throwing RAII
destructors. Replace the loop with offsets[ranges.value], matching the
DW_FORM_sec_offset branch and GDB's own read_rnglist_index.

The new test invokes clang++ directly since DW_FORM_rnglistx is a
clang-specific behavior; gcc through 13 uses DW_FORM_sec_offset, which
mold has always handled correctly. The test skips cleanly if clang++
isn't installed or doesn't emit rnglistx for the source.

Reported by @MikeWang000000.

Fixes https://github.com/rui314/mold/issues/1578
2026-05-12 15:53:46 -07:00
Bennett Amodio 10b37a81fb Don't emit PT_INTERP for -shared outputs
When --dynamic-linker is passed alongside -shared, mold currently emits
a .interp section and a PT_INTERP program header. Both lld and BFD ld
silently drop --dynamic-linker for -shared outputs because shared
libraries are loaded by the dynamic linker rather than bootstrapped by
the kernel, so PT_INTERP serves no purpose there.

Gate .interp creation on (!shared || pie) so we keep it for regular
executables, -pie executables, and -shared -pie runnable shared
libraries (the libc.so.6-style case), and drop it only for the pure
shared-library case. The PT_INTERP program header at
output-chunks.cc:219 is conditional on ctx.interp being non-null, so
the segment vanishes automatically with no second edit.

Fixes https://github.com/rui314/mold/issues/1572
2026-05-08 16:28:51 -07:00
Rui Ueyama 45970e661d Update mold.1 (automated commit) 2026-05-08 08:06:27 +00:00
Rui Ueyama 155a8106c9 Merge pull request #1576 from bamo/feat/cli-no-warnings
Support -w / --no-warnings to suppress warnings
2026-05-08 17:05:35 +09:00
Bennett Amodio 51b9683ce7 Support -w / --no-warnings to suppress warnings
The Warn() machinery has consulted ctx.arg.suppress_warnings since
6d2dbd42 ("Preparation to support Apple ld64's -w option", March
2023), but no cmdline flag ever set it. Wire -w (the GNU/BFD
spelling) and --no-warnings (the lld long form) to set the field.

Because Warn::Warn early-returns when the flag is true, before the
ctx.arg.fatal_warnings branch sets has_error, -w also cancels
--fatal-warnings — matching lld's documented behavior.

The "warnings only" semantics match GCC, clang, and lld: real errors
still print and still fail the link. BFD ld's -w is broader and
suppresses error messages too, which would hide the cause of link
failures.

Fixes https://github.com/rui314/mold/issues/1493
2026-05-07 15:44:49 -07:00
Rui UeyamaandClaude Opus 4.7 e2d0c1a93b Accept ! as a bracket-expression negation marker in glob patterns
Linker scripts produced for lld (e.g., the version script Chrome's
build emits) use the POSIX/shell convention `[!...]` to negate a
bracket character class. mold previously accepted only `[^...]`, so
those scripts failed to match. Accept both, treating `!` and `^`
identically at the start of a bracket expression.

Fixes https://github.com/rui314/mold/issues/1575

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-07 12:18:41 +09:00
Rui UeyamaandClaude Opus 4.7 6b0ecea236 Don't drop DSO undef references when versym is VER_NDX_LOCAL
Commit 6b8293bf changed the early `ver` computation in
SharedFile::parse so that undef symbols use the raw versym instead
of being forced to VER_NDX_GLOBAL. Some BFD ld versions (notably
binutils 2.35 on Debian 11) emit VER_NDX_LOCAL (0) as the versym
for unversioned undefined dynamic symbols, so the subsequent
`if (ver == VER_NDX_LOCAL) continue;` check silently dropped those
undef references. With the DSO's reference invisible to mold, the
matching object-file definition was never marked as exported, and
at runtime the dynamic loader failed with "undefined symbol".

Restore the old behavior: defined symbols with VER_NDX_LOCAL are
still skipped (they aren't really exposed for dynamic linking),
but undef references are always processed.

Also fix the verdef/verneed merge in the same path. vd_ndx and
vna_other share a single versym index space (a producing linker
assigns them from one counter), so a single vector covers both.
read_verdef and read_verneed are folded into one read_version_strings
that populates that shared-index vector directly, replacing the
buggy `append` concatenation.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 23:32:27 +09:00
Rui UeyamaandClaude Opus 4.7 6b8293bf8c Use .gnu.version_r when registering undefined DSO symbols
For an undefined symbol in a shared library, mold treated the versym
as VER_NDX_GLOBAL and registered the symbol under its bare name. That
meant an undef foo@VER_1.0 in libbar.so was looked up as just foo, so
a non-default foo@VER_1.0 in another DSO did not satisfy it and
--no-allow-shlib-undefined wrongly reported "undefined symbol: foo".

Parse .gnu.version_r and, when an undef has a versym indexing into it,
register the symbol as name@VERSION so the existing versioned-symbol
resolution path picks up matching definitions.

Fixes https://github.com/rui314/mold/issues/1571

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 18:29:32 +09:00
Rui UeyamaandClaude Opus 4.7 82b5ed48bf Fix flaky compress-debug-sections-{zlib,zstd}-level tests on Alpine
`readelf | grep -q` plus `set -o pipefail` is fragile: when grep -q
matches it stops reading immediately, readelf gets SIGPIPE on its next
write, and pipefail propagates the resulting 141. Alpine (musl) hits
this where glibc happens to escape it. Drop -q so grep drains stdin.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 15:57:23 +09:00
Rui UeyamaandClaude Opus 4.7 4d313a269c Rename gc-sections.cc visit() to avoid std::visit ADL clash on gcc 10
The new StartStopMap parameter contains std::string_view, so the
unqualified call to visit() inside mark() and the recursive call inside
visit() itself were resolved to std::visit by ADL on gcc 10 (Debian 11),
breaking the build.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 14:40:33 +09:00
Rui UeyamaandClaude Opus 4.7 384527fd29 Don't unconditionally retain C-identifier-named sections in --gc-sections
A section whose name is a valid C identifier was treated as a GC root,
keeping it (and everything reachable from it) alive even when nothing
referenced its synthesized __start_/__stop_ marker symbols. That could
pull in unrelated code with undefined references and cause spurious
"undefined symbol" errors that lld and ld.bfd don't produce.

Build a multimap of c-identifier-named sections up front and, during
marking, when a relocation targets __start_<name> or __stop_<name>,
mark every section named <name> alive.

Fixes https://github.com/rui314/mold/issues/1571

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-05 17:50:33 +09:00
Rui Ueyama d947b1a6d3 Update mold.1 (automated commit) 2026-05-04 14:00:41 +00:00
Rui UeyamaandClaude Opus 4.7 9e32fce011 Fix CI
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:57 +09:00
Rui UeyamaandClaude Opus 4.7 b7102d26ca Add hidden --use-android-relr-tags option
Pre-Android-12 (API 30 and earlier) bionic loaders predate the official
ELF gABI assignment of DT_RELR/DT_RELRSZ/DT_RELRENT and use legacy
OS-specific tag values DT_ANDROID_RELR (0x6fffe000), DT_ANDROID_RELRSZ
(0x6fffe001), DT_ANDROID_RELRENT (0x6fffe003), and section type
SHT_ANDROID_RELR (0x6fffff00). With --use-android-relr-tags, mold emits
those instead of the standard ones so binaries linked with
--pack-dyn-relocs=relr can be loaded on those older devices. The flag
is undocumented (not listed in --help) since it's only relevant to
shipping to legacy Android targets.

https://github.com/rui314/mold/issues/1544

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:56 +09:00
Rui UeyamaandClaude Opus 4.7 59957fce3b Add --pack-dyn-relocs=android support
Implements the Android Packed Relocations Format (APS2) used by Android
binaries to compress dynamic relocations. Two new modes are accepted:
`--pack-dyn-relocs=android` encodes all dynamic relocations as APS2 in
.rela.dyn (SHT_ANDROID_RELA) and emits DT_ANDROID_RELA(SZ) instead of
DT_RELA(SZ); `--pack-dyn-relocs=android+relr` is the same but with
R_RELATIVE relocations diverted to .relr.dyn via the existing RELR path.

The encoder groups consecutive relocations sharing offset_delta and
r_info to amortize SLEB128 overhead. The encoded bytes are decoded by
Android's bionic loader and by llvm-readelf, which the new test uses to
verify correctness.

main.cc gains a final set_osec_offsets() call after fix_synthetic_symbols.
Symbol values assigned there change addends for relocations that
reference synthetic symbols, which can shift the SLEB128-encoded section
size; re-running the layout convergence keeps sh_size consistent with
the final encoding.

Fixes https://github.com/rui314/mold/issues/1544

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:56 +09:00
Rui UeyamaandClaude Opus 4.7 c36c5a64fc Centralize dynamic relocation generation in RelDynSection
Previously each chunk wrote its dynamic relocations into a magic offset
in .rel.dyn (tracked via per-chunk reldyn_offset), with a separate
construct_relr pass building per-chunk relr vectors. Now each chunk
returns its dynrels via a virtual collect_dynrels(), and RelDynSection
aggregates them into a single relocs vector that it owns and writes.
RELR-eligibility is determined by checking r_offset alignment of the
collected ElfRel entries (replacing the ABS_REL_RELR kind).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:55 +09:00
Rui Ueyama e71cef7448 Update mold.1 (automated commit) 2026-04-27 06:01:09 +00:00
Rui UeyamaandClaude Opus 4.7 6695f13dce Add --compress-debug-sections=zlib:N for configurable zlib compression level
Generalize the zstd:N syntax added in the previous commit so that the
zlib compression level can also be specified. zlib levels 0 through 9
are accepted, where 0 means no compression. Plain `zlib` remains
equivalent to `zlib:1` (the previous hardcoded default).

Also drop the objcopy/zstdcat round-trip from the zstd-level test in
favor of a simpler readelf check, matching the new zlib-level test, and
add error-message coverage for out-of-range levels.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-27 15:00:26 +09:00
Rui Ueyama ab704cf476 Update mold.1 (automated commit) 2026-04-27 05:47:52 +00:00
Rui Ueyama c028765bb7 Merge pull request #1569 from pierluigilenoci/feat/zstd-compression-level
Add --compress-debug-sections=zstd:N for configurable zstd level
2026-04-27 14:39:37 +09:00
Pierluigi Lenoci 04209593c1 refactor: use zstd:<level> syntax for consistency with lld
Change the compression level syntax from zstd-<level> to zstd:<level>
as requested by @rui314 for consistency with lld.

Signed-off-by: Pierluigi Lenoci <[email protected]>
2026-04-26 20:23:28 +02:00
Pierluigi Lenoci 56fc57198a Add --compress-debug-sections=zstd-N for configurable zstd compression level
Allow users to specify a zstd compression level (1-22) for debug section
compression via --compress-debug-sections=zstd-N, where N is the desired
level. Higher levels achieve better compression ratios at the cost of
compression speed. The plain 'zstd' option remains equivalent to 'zstd-3'
(the previous hardcoded default).

Closes: https://github.com/rui314/mold/issues/1290
Signed-off-by: Pierluigi Lenoci <[email protected]>
2026-04-24 17:15:01 +02:00
Rui UeyamaandClaude Opus 4.7 84c411b9a2 [LoongArch] Don't relax GOT load to pcaddi when symbol is misaligned
pcaddi computes PC + (imm << 2), so the relaxation from a GOT load
(pcalau12i + ld.d) to pcaddi is valid only when the symbol's address
is 4-byte aligned. Without this check, a reference to a misaligned
symbol was relaxed to a pcaddi that resolves to the address with its
low two bits cleared, producing a wrong pointer.

Fixes https://github.com/rui314/mold/issues/1567

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-19 17:23:40 +09:00
Rui UeyamaandClaude Opus 4.7 6f8d62e21c Fix non-deterministic --gdb-index output with multiple threads
The parallel writer that fills (type, cu_idx) tuples for each name
appended entries in thread-scheduling order, so names referenced from
multiple compilation units produced different .gdb_index bytes across
runs. Sort each name's tuple array after the parallel append so the
output is reproducible while preserving parallelism.

Fixes https://github.com/rui314/mold/issues/1566

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-18 11:28:03 +09:00
Rui Ueyama 1b2306a71b Fix test failure on SPARC
We need to ignore SPARC-specific symbol with STT_REGISTER.

Fixes https://github.com/rui314/mold/issues/1564
2026-04-14 20:50:28 +09:00
Rui Ueyama a91381b1b1 Bump mold version to 2.41.0 2026-04-13 18:31:07 +09:00
Rui Ueyama 5b8c7dc023 Merge pull request #1547 from sicherha/fix_textrel2_test_on_Fedora_44
Fix `textrel2` test on Fedora 44
2026-04-07 16:46:48 +09:00
Rui Ueyama a1ae8da0c4 Fix --omagic to pack all sections into a single PT_LOAD segment
Previously, --omagic only flipped segment permissions to RWX but
still page-aligned PT_LOAD segments and emitted PT_GNU_RELRO, so
text and data ended up in separate page-padded segments. This
mismatched the documented behavior ("makes the first data segment
not aligned to a page boundary") and GNU ld's --omagic, which also
disables shared linking.

Make --omagic imply --Bstatic, disable RELRO, and stop forcing
PT_LOAD's p_align to the page size. Also gate chunk->is_relro on
z_relro so a disabled RELRO no longer leaks into address layout.

Fixes https://github.com/rui314/mold/issues/1562
2026-04-07 16:41:35 +09:00
Rui UeyamaandClaude Opus 4.6 632a0eead1 Fix spurious "duplicate symbol" errors with LTO
When linking with LTO, archive members extracted pre-LTO to satisfy
references from IR objects could cause false duplicate symbol errors.
This happened in two ways:

1. The pre-LTO check_duplicate_symbols flagged conflicts between LTO IR
   objects and regular objects. These conflicts are handled by the LTO
   backend and should not be reported.

2. After LTO compilation, archive members that were extracted pre-LTO
   retained stale is_reachable flags even when the LTO output superseded
   all their symbols. The post-LTO duplicate check then saw both the
   archive member and the LTO output defining the same symbol.

Fix both issues:
- In check_duplicate_symbols, skip errors where one side is an LTO IR
  object and the other is not. Only IR-vs-IR duplicates need catching.
- In do_lto, reset reachability for archive members before the post-LTO
  resolve_symbols so that only genuinely needed members are re-extracted.

Fixes https://github.com/rui314/mold/issues/1421

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-05 19:55:33 +09:00
Rui Ueyama 04430ade4b Merge pull request #1546 from jwakely/linker-script-group-as-needed
Fix getting output type from linker scripts that use AS_NEEDED
2026-04-05 14:35:21 +09:00
Rui UeyamaandClaude Opus 4.6 91a5f3c4bf Skip nightly build-all workflow when there are no new commits
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-05 14:28:23 +09:00
Rui Ueyama e804c8caff Fix a compiler warning 2026-04-05 13:47:07 +09:00
Rui Ueyama f378b18369 Support R_RISCV_GOT32_PCREL
This change should unbreak our riscv64 buildbot by fixing
test/relative-vtables.sh test.
2026-04-05 13:47:07 +09:00
Rui Ueyama ff83f60fbc Merge pull request #1557 from akarle/ak/fix-separate-debug-file-corruption
Fix separate-debug-file strtab corruption
2026-04-04 15:58:17 +09:00
Rui Ueyama f8b49ee83e Fix FreeBSD buildbot 2026-04-04 15:54:06 +09:00
Rui Ueyama 18ea70c696 Merge pull request #1554 from karesansui-u/fix/redo-main-abort-message
Replace abort() with diagnostic message in redo_main()
2026-04-04 15:48:29 +09:00
Rui Ueyama fd95adda95 "#undef ERROR" before use for MinGW compatibility
https://github.com/rui314/mold/pul/1558
2026-04-04 15:01:32 +09:00
Rui Ueyama 4e58828670 Simplify and add tests 2026-04-04 13:55:53 +09:00
Rui Ueyama 027111315e Merge pull request #1559 from shivendra02467/fix-shlib-undefined-visibility
[ELF] Fix --no-allow-shlib-undefined for hidden static providers and LTO
2026-04-04 13:45:12 +09:00
Rui Ueyama d4d5eae43d Do not report an error on LTO unclaimed files in archives
Previously, mold eagerly report an "not claimed by the LTO plugin" error
if it finds an LTO object file that is not claimed by an LTO plugin.
2026-04-04 11:34:42 +09:00
Rui Ueyama 274a46b9f7 Revert "defer incompatible archive LTO member errors until selected"
This reverts commit 6c4701955c.
2026-04-04 11:09:05 +09:00
Rui Ueyama 906af24d25 Merge pull request #1560 from tobim/push-rpuotyoounvk
Various LTO compatibility fixes
2026-04-04 11:07:20 +09:00
Rui UeyamaandClaude Opus 4.6 be28db1241 Support R_AARCH64_GOTPCREL32 relocation for relative vtables
This relocation is used by clang's -fexperimental-relative-c++-abi-vtables
to emit 32-bit GOT-relative PC-relative offsets in vtable entries (e.g. for
RTTI pointers). It resolves to G + GOT + A - P as a 32-bit value.

Fixes https://github.com/rui314/mold/issues/1561

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-03 19:46:19 +09:00
Tobias Mayer 6c4701955c defer incompatible archive LTO member errors until selected 2026-03-21 14:21:00 +01:00
Tobias Mayer ce0ee76550 Add regression test for mixed LLVM/GCC slim LTO archive link 2026-03-21 14:18:31 +01:00
Tobias Mayer 2a1e5c6e8d Fall back to native code for GCC fat LTO objects with LLVM plugin 2026-03-21 14:18:31 +01:00
Tobias Mayer 487b83e581 Add a test for mixed toolchain fatLTO
LTO links where the main driver is LLVM but some objects
are GCC fatLTO should succeed.
2026-03-21 14:18:31 +01:00
Tobias Mayer a660037f95 Preserve non-lto comdat members when section is shared with LTO objects 2026-03-21 10:24:17 +01:00
Tobias Mayer 10342f7c2a Add a regression test for mixed lto and assembly comdat 2026-03-21 10:02:05 +01:00
Shivendra Sharma f6bb1175eb [ELF] Fix --no-allow-shlib-undefined for hidden static providers and LTO
This patch fixes two distinct blind spots in how `mold` handles
--no-allow-shlib-undefined checks during the final link phase:

1. The Hidden Static Provider Bug (False Negative)
Previously, if a shared library requested a symbol that was provided by a
static object file with STV_HIDDEN, `mold` would silently overwrite the
global symbol visibility to STV_DEFAULT and rubber-stamp the binary,
resulting in a guaranteed `symbol lookup error` at runtime.
Fix: We now interrogate the raw `elf_syms` array of the static object
provider. If the original visibility is hidden/internal, we flag it as an
invalid provider and throw the error, matching GNU ld/lld behavior.

2. The LTO Shadowing Bug (False Positive)
When LTO is heavily utilized, GCC's plugin may optimize away symbols that
are theoretically provided by static objects if they aren't used internally.
If a shared library still needs that symbol, `mold`'s static state
registered the provider as NULL and threw an error, ignoring the fact
that a loaded DSO (like libc.so) offered a valid public fallback.
Fix: Introduced `has_public_dso_fallback()`. Before throwing an undefined
symbol error, `mold` performs a rapid scan of `ctx.dsos`. If a public
fallback exists dynamically, the error is safely suppressed.
2026-03-20 00:07:44 +05:30
Alex Karle 4cd4d5693e Fix separate-debug-file strtab corruption
Originally discussed in #1535, the debug data generated by
--separate-debug-file is being corrupted. In the best case scenario,
there's many blank/nameless undefined symbols in the symtab, but in
most production scenario I've encountered, the file is corrupt / gdb
refuses to load it:

    $ gdb main
    Reading symbols from $PWD/main.dbg...
    BFD: /$PWD/main.dbg: invalid string offset 4130582 >= 1345 for section `.strtab'
    BFD: /$PWD/main.dbg: invalid string offset 16777228 >= 1345 for section `.strtab'

    $ readelf -Ws main.dbg
    Symbol table '.symtab' contains 139 entries:
    [...]
    38: 0000000000000000     0 SECTION LOCAL  DEFAULT   38 .strtab
    39: 0000000000000000     0 SECTION LOCAL  DEFAULT   39 .symtab
    40: 0018a80200010000 0xb10000003f071500 NOTYPE  LOCAL  DEFAULT bad section index[3232] <corrupt>
    41: 0000003f07140000 0xfd02000100000cc2 NOTYPE  LOCAL  HIDDEN  bad section index[11500] <corrupt>
    42: 02000100000cd300 0x3f0712000008a3 <unknown>: 7 LOCAL  PROTECTED [<other>: 3c]   UND <corrupt>
    43: 3f07110000056202 0x100000cf5000000 NOTYPE  LOCAL  DEFAULT    1 <corrupt>

This appears to be happening because the strtab size is set to be larger
than its true value, causing garbage data to be placed in it, which
breaks the symtab.

This regression occurred in 5b71029, which fixed a different form of
corruption (the PHDR symbol escaping into the section headers, shifting
the section offsets), but created this strtab corruption. The root cause
is that `compute_section_headers` was moved prior to the wiping of
irrelevant chunks. `compute_section_headers` computes the strtab size by
adding up individual chunk strtab contributions. It needs to occur _after_
the wiping so that the strtab doesn't account for the data before the
wipe.

The core of this patch is really just a partial revert of 5b71029--the
compute_section_headers call appears to have been in the correct place
prior to this change; I've added a test that catches the issue by
intentionally bloating the size of the `.plt` section with many external
symbols. This section is wiped in the dbg data, so by bloating it, the
test can inspect for corruption.

Fixes #1535

Signed-off-by: Alex Karle <[email protected]>
2026-03-16 12:13:09 -04:00
karesansui 93e1aeaec6 Replace abort() with diagnostic message in redo_main()
When mold auto-detects an unsupported target from ELF headers,
redo_main() previously called abort() with no context. This replaces
it with a descriptive error message matching the style used by the
-m flag path in cmdline.cc.

Fixes #1553
2026-03-12 02:24:00 +09:00
Rui Ueyama fc96c1b9ef Do not call fallocate on tmpfs
Interestingly, calling fallocate on tmpfs is very slow. It took
almost a second for a fresh ~5 GiB file, even though intuititvely
it should finish very quickly.
2026-03-05 08:40:16 +09:00
Rui Ueyama 0c0c42b31d Add an entry for fedora-asahi-remix 2026-03-01 13:23:17 +09:00
Jonathan Wakely e0a6f8e1d3 Fix getting output type from linker scripts that use AS_NEEDED
Make Script<E>::get_script_output_type support AS_NEEDED inside an INPUT
or GROUP group, so that the libatomic_asneeded.so linker script used by
GCC 16 works.

Fixes #1545

Signed-off-by: Jonathan Wakely <[email protected]>
2026-02-28 09:08:49 +00:00
Rui Ueyama f8da34575f Unbreak Windows buildbot 2026-02-27 16:10:33 +09:00
Rui Ueyama a4db2382a5 Improve --gdb-index performance
This change eliminates the cost of zero-initialization on vector
resizing by using malloc/realloc instead. We also parallelize the
loops to write adddress areas and types to .gdb_index.
2026-02-27 15:45:51 +09:00
Rui Ueyama 6d6a128bf9 Handle -C and --chroot early
We read version script files in cmdline.cc, so we need to handle -C
and --chroot early. Otherwise, a version script passed by command line
can cause a "file not found" error.
2026-02-27 15:45:51 +09:00
Rui Ueyama aeb1041f1a Refactor 2026-02-27 15:45:51 +09:00
Christoph Erhardt 6e40a87aad Fix textrel2 test on Fedora 44
Tell the GNU linker to not report dynamic relocations in read-only
sections as an error.
2026-01-30 22:09:29 +01:00
Rui Ueyama 17956fdfa1 Merge pull request #1541 from mati865/push-qlyqwkunnpmx
Fix typo in test name
2025-12-12 09:43:18 +09:00
Rui Ueyama 97b43aaf0d Fix CI 2025-12-10 13:57:55 +09:00
Rui Ueyama c3bf8fd803 Attempt to fix CI 2025-12-10 12:59:41 +09:00
Rui Ueyama 4e51cfaa67 Update mold.1 (automated commit) 2025-12-10 03:29:09 +00:00
Rui Ueyama 87873cad57 Change the man page to reflect reality 2025-12-10 12:15:40 +09:00
Rui Ueyama 9f463a62ac Align the end of RELRO to a page boundary even if -z separate-loadable-segment is given
Otherwise the last page of a relro segment is not protected by the
runtime, as the runtime aligns down the end address of a RELRO segment.
2025-12-10 12:11:45 +09:00
Rui Ueyama 5b710298fc d1;10;0cAttempt to fix an issue with --separate-debug-file
https://github.com/rui314/mold/issues/1535
2025-11-17 16:01:14 +09:00
Rui Ueyama 4798f43358 Do not emit a bogus "PHDR" section for --separate-debug-file
Previously, we emit a bogus section with name "PHDR" for a separate
debug file.

Fixes https://github.com/rui314/mold/issues/1535
2025-11-15 12:42:53 +09:00
Rui Ueyama fcd5391981 Adapt to a recent readelf output format change
Fixes https://github.com/rui314/mold/issues/1533
2025-11-11 22:39:36 +09:00
Rui Ueyama ad08999d03 Refactor 2025-11-10 19:55:51 +09:00
Rui Ueyama 016bb8af6e Refactor 2025-11-05 20:13:14 +09:00
Rui Ueyama 3a5985ca14 Refactor 2025-11-04 22:06:18 +09:00
Rui Ueyama e99551c9f0 Refactor 2025-11-04 16:38:39 +09:00
Rui Ueyama 56f8eb3b92 Refactor 2025-11-04 15:54:44 +09:00
Rui Ueyama e459ed59c6 [SPARC64] Do not assume the dest reg of R_SPARC_GOTDATA_OP is always %g1
https://github.com/rui314/mold/issues/1529
2025-11-04 15:25:33 +09:00
Rui Ueyama 4d94410ad4 [SPARC64] Preserve %rd when rewriting R_SPARC_TLS_GD_ADD
https://github.com/rui314/mold/issues/1529
2025-11-04 15:21:58 +09:00
Rui Ueyama 069f4da96a [SPARC64] Change how we swap instructions for TLS_GD_CALL
https://github.com/rui314/mold/issues/1529
2025-11-04 15:21:52 +09:00
Rui Ueyama 01923e4b4b Refactor 2025-11-02 17:33:40 +09:00
Rui Ueyama 5cac744adf Merge pull request #1528 from koachan/main
[SPARC64] Preserve registers used during optimization transforms
2025-11-02 17:29:08 +09:00
Koakuma a88a2163b2 [SPARC64] Decouple rs1 and rd in R_SPARC_TLS_GD_LO10 transform
The compiler might place the two values on different registers,
so don't modify them during transforms.
2025-11-02 15:18:15 +07:00
Koakuma ca0b5973a2 [SPARC64] Preserve rd of TLS *_ADD relocations during optimization
GCC might decide to put the result of the operation in another register,
so remove hardcodings of %o0 as the destination register (`rd` field).
2025-11-02 00:40:20 +07:00
Rui Ueyama 5fb6d8aa63 Fix CI 2025-10-31 16:28:42 +09:00
Rui Ueyama 3ad34d4ec3 Do not create a PDE IFUNC entry if the ifunc is not referenced within the exe
Fixes https://github.com/rui314/mold/issues/1527
2025-10-31 15:47:06 +09:00
Rui Ueyama dce9ac1e5c Allow building mold for a specific set of targets
By popular request, this commit adds a new cmake variable that allows
building mold with a limited set of supported targets, which can help
reduce build time.

The use of this flag is discouraged unless you build mold frequently
for personal use. Otherwise, build mold with the default settings for
simplicity and maximum compatibility. In particular, distributions
should not build mold with a limited set of supported targets.

Fixes https://github.com/rui314/mold/issues/1364
2025-10-31 12:20:47 +09:00
Rui Ueyama 7cc59cc71a Fix a compiler warning 2025-10-30 15:39:21 +09:00
Rui Ueyama 98d7691c21 Refactor 2025-10-25 11:24:50 +09:00
Rui Ueyama 92adbb1d61 [SPARC64] Fix TLS GD with GCC -O2
Fixes https://github.com/rui314/mold/issues/1525
2025-10-25 11:13:21 +09:00
Rui Ueyama 8634570e56 Refactor 2025-10-23 21:05:50 +09:00
Rui Ueyama fce89c24e5 Respect --no-threads with --compress-debug-sections
Fixes https://github.com/rui314/mold/issues/1524
2025-10-23 12:28:39 +09:00
Rui Ueyama 2b36f8a982 Merge pull request #1523 from koachan/main
[SPARC64] Fix R_SPARC_TLS_GD_ADD handling
2025-10-23 10:04:45 +09:00
Rui Ueyama 7f8c50bfa7 [SPARC] Honor --no-apply-dnamic-relocs for R_SPARC_UA64
Related to: https://github.com/rui314/mold/pull/1523
2025-10-23 10:03:17 +09:00
Koakuma a16bb13a01 [SPARC64] Fix R_SPARC_TLS_GD_ADD handling
R_SPARC_TLS_GD_ADD can use any register for the base address (ELF Handling
For Thread-Local Storage, pg. 21), so remove the hardcoded %l7 usage.
2025-10-22 20:16:41 +07:00
Rui Ueyama f55ad918d4 [SPARC64] Emit dynamic relocations for R_SPARC_UA64
Related to: https://github.com/rui314/mold/pull/1523
2025-10-22 18:08:22 +09:00
Rui Ueyama 5164da8ad2 Fix CI 2025-10-09 17:20:06 +09:00
Rui Ueyama 7cc21c7657 Refactor 2025-10-09 16:30:37 +09:00
Rui Ueyama f8089ca121 Merge pull request #1522 from bmilanich/pr/eh_icf
Consider .gcc_except_table* sections for ICF.
2025-10-09 12:44:27 +09:00
Rui Ueyama 04a99003c2 Add missing newlines 2025-10-09 11:45:21 +09:00
Basil Milanich fd8659027b Consider .gcc_except_table* sections for ICF. 2025-10-08 14:12:07 -05:00
Rui Ueyama 0b20faa0cd [PPC32] Support R_PPC_DTPREL32 in non-alloc sections
Fixes https://github.com/rui314/mold/issues/1521
2025-10-03 10:47:27 +09:00
Rui Ueyama 7c82a6a00e Fix typo 2025-09-21 11:45:04 +09:00
Rui Ueyama 5148754c14 Refactor 2025-09-20 17:29:38 +09:00
Rui Ueyama 91439beea5 --section-order: Report an error if address goes backward
https://github.com/rui314/mold/issues/1514
2025-09-20 12:16:25 +09:00
Rui Ueyama 311da914d3 Refactor 2025-09-20 11:09:43 +09:00
Rui Ueyama dfb18afd69 Fix CI 2025-09-19 19:19:23 +09:00
Rui Ueyama 8dee52318b Run MemorySanitizer tests on GitHub Actions 2025-09-19 19:08:04 +09:00
Rui Ueyama 9d7f20b34e Avoid unnecessary zero-initializations 2025-09-19 19:08:04 +09:00
Rui Ueyama aa80487536 Merge pull request #1517 from etwoo/feature/msan-build
support MemorySanitizer w/ instrumented C++ stdlib
2025-09-19 15:16:32 +09:00
Eric Woo 13cec07aa3 fix msan error in compress.cc
==70801==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x56218bab14b3 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>) /home/ewoo/mold/lib/compress.cc:89:13
1) 0x56218bab14b3 in mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0::operator()(long) const /home/ewoo/mold/lib/compress.cc:106:17
2) 0x56218bab14b3 in decltype(std::declval<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&>()(std::declval<long&>())) std::__1::__invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
3) 0x56218bab14b3 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type std::__1::invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
4) 0x56218bab14b3 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type tbb::detail::d0::invoke<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
5) 0x56218bab14b3 in tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>::operator()(tbb::detail::d1::blocked_range<long> const&) const /usr/include/tbb/../oneapi/tbb/parallel_for.h:206:13
6) 0x56218bab14b3 in decltype(std::declval<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&>()(std::declval<tbb::detail::d1::blocked_range<long>&>())) std::__1::__invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
7) 0x56218bab14b3 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type std::__1::invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
8) 0x56218bab14b3 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type tbb::detail::d0::invoke<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
9) 0x56218bab14b3 in tbb::detail::d1::start_for<tbb::detail::d1::blocked_range<long>, tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>, tbb::detail::d1::auto_partitioner const>::run_body(tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/parallel_for.h:117:9

Uninitialized value was created by an allocation of 'nbits.i.i.i.i.i.i.i.i.i' in the stack frame
0) 0x56218bab11e6 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>) /home/ewoo/mold/lib/compress.cc:87:3
1) 0x56218bab11e6 in mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0::operator()(long) const /home/ewoo/mold/lib/compress.cc:106:17
2) 0x56218bab11e6 in decltype(std::declval<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&>()(std::declval<long&>())) std::__1::__invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
3) 0x56218bab11e6 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type std::__1::invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
4) 0x56218bab11e6 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type tbb::detail::d0::invoke<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
5) 0x56218bab11e6 in tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>::operator()(tbb::detail::d1::blocked_range<long> const&) const /usr/include/tbb/../oneapi/tbb/parallel_for.h:206:13
6) 0x56218bab11e6 in decltype(std::declval<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&>()(std::declval<tbb::detail::d1::blocked_range<long>&>())) std::__1::__invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
7) 0x56218bab11e6 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type std::__1::invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
8) 0x56218bab11e6 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type tbb::detail::d0::invoke<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
9) 0x56218bab11e6 in tbb::detail::d1::start_for<tbb::detail::d1::blocked_range<long>, tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>, tbb::detail::d1::auto_partitioner const>::run_body(tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/parallel_for.h:117:9

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/lib/compress.cc:89:13 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>)
2025-09-18 04:05:30 +00:00
Eric Woo 921649fc7d fix msan error in passes.cc
Uninitialized bytes in fwrite at offset 880 inside [0x724000002000, 7968)
==60359==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x561279ddfd59 in mold::MallocOutputFile<mold::X86_64>::close(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/mold.h:1587:5

Uninitialized value was stored to memory at
0) 0x56127521426e in __msan_memcpy.part.0 msan_interceptors.cpp.o
1) 0x56127701a499 in void mold::write_vector<unsigned char>(void*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char>> const&) /home/ewoo/mold/src/../lib/lib.h:246:5
2) 0x56127701a499 in mold::BuildIdSection<mold::X86_64>::copy_buf(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/output-chunks.cc:2745:3

Uninitialized value was stored to memory at
0) 0x56127521412e in __msan_memmove.part.0 msan_interceptors.cpp.o
1) 0x561277df5cca in unsigned char* std::__1::__constexpr_memmove[abi:nn220000]<unsigned char, unsigned char>(unsigned char*, unsigned char*, std::__1::__element_count) /home/ewoo/llvm-project/build/include/c++/v1/__string/constexpr_c_functions.h:228:5
2) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_trivial_impl[abi:nn220000]<unsigned char, unsigned char>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy_move_common.h:64:3
3) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_impl::operator()[abi:nn220000]<unsigned char, unsigned char, 0>(unsigned char*, unsigned char*, unsigned char*) const /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:234:12
4) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_move_unwrap_iters[abi:nn220000]<std::__1::__copy_impl, unsigned char*, unsigned char*, unsigned char*, 0>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy_move_common.h:94:19
5) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy[abi:nn220000]<unsigned char*, unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:241:10
6) 0x561277df5cca in unsigned char* std::__1::copy[abi:nn220000]<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:247:10
7) 0x561277df5cca in unsigned char* std::__1::__uninitialized_allocator_copy_impl[abi:nn220000]<std::__1::allocator<unsigned char>, unsigned char, unsigned char, 0>(std::__1::allocator<unsigned char>&, unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__memory/uninitialized_algorithms.h:574:12
8) 0x561277df5cca in unsigned char* std::__1::__uninitialized_allocator_copy[abi:nn220000]<std::__1::allocator<unsigned char>, unsigned char*, unsigned char*, unsigned char*>(std::__1::allocator<unsigned char>&, unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__memory/uninitialized_algorithms.h:582:28
9) 0x561277df5cca in void std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::__construct_at_end<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:954:17
10) 0x561277df5cca in void std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::__init_with_size[abi:nn220000]<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:602:7
11) 0x561277df5cca in std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::vector[abi:nn220000]<unsigned char*, 0>(unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:214:5
12) 0x561277df5cca in void mold::write_build_id<mold::X86_64>(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/passes.cc:3270:29

Uninitialized value was created by an allocation of 'buf' in the stack frame
0) 0x561277df597e in void mold::write_build_id<mold::X86_64>(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/passes.cc:3266:5

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/src/mold.h:1587:5 in mold::MallocOutputFile<mold::X86_64>::close(mold::Context<mold::X86_64>&)
2025-09-18 04:04:25 +00:00
Eric Woo 33545031d7 fix msan error in input-sections.cc
==45321==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x56205a24a8f8 in char const* std::__1::__constexpr_memchr[abi:nn220000]<char const, char>(char const*, char, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__string/constexpr_c_functions.h:149:30
1) 0x56205a24a8f8 in std::__1::char_traits<char>::find[abi:nn220000](char const*, unsigned long, char const&) /home/ewoo/llvm-project/build/include/c++/v1/__string/char_traits.h:135:12
2) 0x56205a24a8f8 in unsigned long std::__1::__str_find[abi:nn220000]<char, unsigned long, std::__1::char_traits<char>, 18446744073709551615ul>(char const*, unsigned long, char, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__string/char_traits.h:353:23
3) 0x56205a24a8f8 in std::__1::basic_string_view<char, std::__1::char_traits<char>>::find[abi:nn220000](char, unsigned long) const /home/ewoo/llvm-project/build/include/c++/v1/string_view:509:12
4) 0x56205a24a8f8 in mold::find_null(std::__1::basic_string_view<char, std::__1::char_traits<char>>, long, long) /home/ewoo/mold/src/input-sections.cc:363:17
5) 0x56205a24a8f8 in mold::MergeableSection<mold::X86_64>::split_contents(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/input-sections.cc:401:20

Uninitialized value was created by a heap allocation
0) 0x562058af198b in operator new(unsigned long) (/home/ewoo/mold/build/mold+0x23a98b) (BuildId: 29d9e90237b4b68ceff9639a3d9599d70f774d75)
1) 0x56205a2430fa in mold::InputSection<mold::X86_64>::uncompress(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/input-sections.cc:65:13

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/src/input-sections.cc:363:17 in mold::find_null(std::__1::basic_string_view<char, std::__1::char_traits<char>>, long, long)
2025-09-18 04:03:09 +00:00
Eric Woo f800f5226c support MemorySanitizer w/ instrumented C++ stdlib 2025-09-18 04:02:39 +00:00
Rui Ueyama 8cf0b59e8f Update --help message 2025-09-15 08:04:28 +09:00
Rui Ueyama 729ceabd8c Update mold.1 (automated commit) 2025-09-14 14:34:10 +00:00
Rui Ueyama 131b5a9e89 Update man page 2025-09-14 23:32:00 +09:00
Rui Ueyama 056286d741 Partially revert 7dd7db0559
so that the output from --print-gc-sections and --print-icc-sections
remains the same as they were.

https://github.com/rui314/mold/pull/1507
2025-09-14 23:12:42 +09:00
Rui Ueyama 79ad0d2b4a Refactor 2025-09-14 19:13:35 +09:00
Rui Ueyama 6454b6c85a Merge pull request #1507 from pedronavf/print_sections_to_file
--print-gc-sections and --print-icf-sections can now take =FILE to s…
2025-09-14 19:11:30 +09:00
Rui Ueyama 49f9c5964a Do not convert read-only data to bss with --zero-to-data
Read-only data sections containing uninitialized variables are essentially
useless because reading from them always returns 0 at any time, so if they
exist, they are there for a reason. We do not want to automatically
convert it to a BSS.

https://github.com/rui314/mold/issues/1514
2025-09-14 10:36:03 +09:00
Rui Ueyama 3fb153e2ee Fix CI 2025-09-12 18:13:05 +09:00
Rui Ueyama 847341d208 Add --zero-to-bss
https://github.com/rui314/mold/pull/1515
2025-09-12 15:26:31 +09:00
Rui Ueyama 34c39e39f0 Refactor 2025-08-30 10:18:46 +09:00
Rui Ueyama 05827319da Fix test for system that doesn't support -static 2025-08-27 09:11:47 +09:00
Rui Ueyama ffbcfdb880 Merge pull request #1508 from PowellNGL/patch-1
Fix GCC package name for RHEL-based distros in install-build-deps.sh
2025-08-27 09:11:32 +09:00
Nick Powell 415176f530 Use correct GCC C++ package name for RHEL-based distros 2025-08-26 19:04:19 +02:00
Pedro Navarro 7dd7db0559 --print-gc-sections and --print-icf-sections can now take =FILE to save their output to a file 2025-08-26 02:26:41 -07:00
Rui Ueyama f5658e8bb4 Refactor 2025-08-26 11:24:12 +09:00
Rui Ueyama 8825ed7004 Merge pull request #1506 from minhbq-99/fix-pop-state
Correctly track --static value inside --push-state/--pop-state
2025-08-26 10:54:45 +09:00
Bui Quang Minh 001f718647 Correctly track --static value inside --push-state/--pop-state
Currently, when using --static inside --push-state and --pop-state, we
incorrectly mark the global state as static. This commit changes to the
correct behavior where global state is marked as static only when
--static is used at the outer most (outside of any --push-state,
--pop-state).
2025-08-25 23:02:40 +08:00
Rui Ueyama 4aa1a32d72 Bump required clang version from 12 to 16
Closes https://github.com/rui314/mold/issues/1505
2025-08-21 11:05:15 +09:00
Rui Ueyama 083901f42d Bump mold version to 2.40.4 2025-08-17 10:32:35 +09:00
Rui Ueyama 0a36282062 Revert "Choose the last file if we have more than one -l for the same DSO"
This reverts commit 2783e82436.

I don't want to take too much risk for 2.40.4, so I'll revert this change
for now.

https://github.com/rui314/mold/issues/1494
2025-08-16 12:21:41 +09:00
Rui Ueyama 754949f118 Revert "Upgrade bundled mimalloc to 3.0.8"
This reverts commit 1b76a11af2.

Closes https://github.com/rui314/mold/issues/1502
2025-08-15 11:48:32 +09:00
Rui Ueyama 05f5cdcf29 Add Alt Linux 2025-08-15 07:29:03 +09:00
Rui Ueyama 0ef640c95d Add Oracle Linux
Fixes https://github.com/rui314/mold/issues/1501
2025-08-15 06:58:13 +09:00
Rui Ueyama 7e0b728378 [ppc64v2] Support R_PPC64_GOT16
Fixes https://github.com/rui314/mold/issues/1497
2025-08-11 11:32:55 +09:00
Rui Ueyama d9b20a1580 [PPC64v2] Support R_PPC64_REL14
Fixes https://github.com/rui314/mold/issues/1496
2025-08-09 22:48:53 +09:00
Rui Ueyama 2783e82436 Choose the last file if we have more than one -l for the same DSO
Fixes https://github.com/rui314/mold/issues/1494

Based on https://github.com/rui314/mold/issues/1494
2025-08-04 20:33:44 +09:00
Rui Ueyama 9f5b7064dc Handle .data.rel.ro.{hot,unlikely} RELRO sections 2025-07-31 10:44:13 +09:00
Rui Ueyama f83747d6f6 Change the filename of Windows release binary 2025-07-31 06:33:29 +09:00
Rui Ueyama 282ca44e90 Bump mold version to 2.40.3 2025-07-30 13:42:59 +09:00
Rui Ueyama 201bc71b42 Revert f0b933820d
Emitting multiple zlib streams in a single .debug_info section broke
a few tools.

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

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

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

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

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

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

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

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

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

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

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

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

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

This commit mitigates the issue.

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

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

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

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

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

Fixes https://github.com/rui314/mold/issues/1169
2025-04-22 12:21:50 +09:00
Rui Ueyama 3d6aad65b0 Simplify 2025-04-21 13:55:31 +09:00
Rui Ueyama f7b1920f84 Make dist.sh reproducible again
mimalloc 2.2.2 started using __DATE__ and __TIME__ macros which make
the output non-reproducible. We can set the SOURCE_DATE_EPOCH environment
variable to fix the "current" timestamp.
2025-04-16 16:17:28 +09:00
Rui Ueyama b42f978212 Bump mold version to 2.38.0 2025-04-16 11:57:48 +09:00
Rui Ueyama ac6f1ec18e Do not hide foo@@VER if both foo@@VER and foo are defined
Fixes https://github.com/rui314/mold/issues/1442
2025-04-15 12:28:31 +09:00
Rui Ueyama d19f822ec0 Simplify 2025-04-15 11:51:11 +09:00
Rui Ueyama a97a628ee8 Resolve versioned symbols early
Fixes https://github.com/rui314/mold/issues/1440
2025-04-11 14:46:27 +09:00
Rui Ueyama 2888aade4c Refactor 2025-04-10 12:59:13 +09:00
Rui Ueyama 0172f22ab5 Refactor 2025-04-07 17:25:28 +09:00
Rui Ueyama 418d4ff62a Fix CI 2025-04-07 11:26:18 +09:00
Rui Ueyama 5249edc57f Simplify 2025-04-07 10:37:08 +09:00
Rui Ueyama 1870845f88 Refactor 2025-04-07 09:07:12 +09:00
Rui Ueyama d15c41a8dd Process --repro early
If mold crashes before --repro is processed, no .repro.tar file is
created. So we want to process the option as early as possible.
2025-04-07 08:50:26 +09:00
Rui Ueyama b7c8a843b2 Ignore --mmap-output-file
For the sake of completeness, --mmap-output-file is ignored just like
--no-mmap-output-file is.
2025-04-03 16:57:37 +09:00
Rui Ueyama 9b77a7ea86 Fix CI 2025-04-03 16:02:20 +09:00
Rui Ueyama af396ad5d9 Add --audit and --depaudit
These are for the sake of compatibility with GNU ld.
2025-04-03 15:36:05 +09:00
Rui Ueyama a2ee7371d5 Handle -mllvm as a synonym for -plugin-opt
Related to https://github.com/rui314/mold/pull/1437
2025-04-03 09:26:17 +09:00
Rui Ueyama c9812cf09f Merge pull request #1436 from christycylee/ignore_no_mmap_output_file
Ignore no-mmap-output-file for compatibility.
2025-04-03 08:59:31 +09:00
Christy Lee e9c070a3c2 Ignore no-mmap-output-file for compatibility. 2025-04-02 11:39:53 -07:00
Rui Ueyama 02e75be9fa Simplify 2025-04-02 21:55:17 +09:00
Rui Ueyama d5609a9bb8 Fix CI 2025-04-02 18:28:37 +09:00
Rui Ueyama 5d24db5a64 Handle COMDAT symbols in LTO IR object files
Before 9306f69a61, we completely ignored
duplciate symbol errors in IR object files. After that commit, we've
been accidentally reporting duplicate comdat symbols as errors.

Fixes https://github.com/rui314/mold/issues/1434
2025-04-02 17:54:41 +09:00
Rui Ueyama aeec8baee5 Revert "Take r_delta into account when computing distance"
This reverts commit b74a771886
because it broke buildbots.
2025-04-02 10:48:57 +09:00
Rui Ueyama b74a771886 Take r_delta into account when computing distance 2025-04-01 16:56:21 +09:00
Rui Ueyama 0124058ef7 Refactor 2025-03-26 20:56:04 +09:00
Rui Ueyama e302a2509c Fix CI 2025-03-26 20:56:02 +09:00
Rui Ueyama f70db66373 Use std::ranges 2025-03-26 20:26:51 +09:00
Rui Ueyama 9c0cbc8e6c Fix R_AARCH64_PREL16 range 2025-03-25 18:01:38 +09:00
Rui Ueyama a9acbd6b6f Refactor 2025-03-24 16:03:52 +09:00
Rui Ueyama b9d65a5787 Update RV32 toolchain 2025-03-24 15:40:06 +09:00
Rui Ueyama 50766f1ad4 Remove endbr32 support
It looks like Linux is going to support CET only on x86-64 (i.e. endbr64),
and it is very unlikely that x86-32 will get CET support. So I'll remove
the instruction from the code we generate for x86-32.
2025-03-23 18:07:21 +09:00
Rui Ueyama 3496ad1291 Update comments 2025-03-22 13:39:54 +09:00
Rui Ueyama 8a3d374251 Simplify 2025-03-22 11:20:07 +09:00
Rui Ueyama 96b2710849 Fix CI 2025-03-20 16:32:29 +09:00
Rui Ueyama a43f3953f0 Do not create redundant range extension thunks
Previously, we created thunks for all PLT function calls.
2025-03-20 13:25:05 +09:00
Rui Ueyama 2bc6ddc41c Refactor 2025-03-19 11:18:15 +09:00
Rui Ueyama f6575d53c1 Report an error if CREL contains addends for REL-type targets
Fixes https://github.com/rui314/mold/issues/1430
2025-03-18 10:52:55 +09:00
Rui Ueyama bf1ec5ac8f Allow very large offsets in SHT_CREL 2025-03-14 15:32:38 +09:00
Rui Ueyama b291100cfe Refactor 2025-03-14 15:05:16 +09:00
Rui Ueyama c43a859a90 Support CREL as input section
Fixes https://github.com/rui314/mold/issues/1427
2025-03-13 15:17:11 +09:00
Rui Ueyama 6e3d0e3a3c Fix build failure on Windows 2025-03-12 14:24:11 +09:00
Rui Ueyama 790ce3f64d Downgrade mimalloc to 2.2.2
mimalloc 3.0.1 cannot be compiled on Windows, so we don't want to stick
with the 2.x series for the time being.
2025-03-12 14:05:35 +09:00
Rui Ueyama cbd7f74c2e Upgrade bundled xxhash to 0.8.3 2025-03-12 13:38:26 +09:00
Rui Ueyama 3fe25fc69d Upgrade bundled mimalloc to 3.0.1 2025-03-12 13:34:39 +09:00
Rui Ueyama 823ef09585 Upgrade bundled blake3 to 1.6.1 2025-03-12 13:32:26 +09:00
Rui Ueyama f20f57060a Upgrade bundled zstd to 1.5.7 2025-03-12 11:44:12 +09:00
Rui Ueyama 0061d53409 Merge pull request #1425 from christycylee/buck_files
Remove extraneous build files
2025-03-11 10:35:56 +09:00
Rui Ueyama 82affb94d8 Fix out-of-bound memory access
Related to https://github.com/rui314/mold/issues/1426
2025-03-10 14:54:17 +09:00
Rui Ueyama 4f782fb7dc Refactor 2025-03-08 17:13:48 +09:00
Rui Ueyama e9eb6020a6 Skip if textrel is prohibited by default 2025-03-08 10:47:29 +09:00
Christy Lee 23a662cbb9 Remove extraneous build files 2025-03-07 14:23:22 -08:00
Rui Ueyama 32c8f6dd61 Bump mold version to 2.37.1 2025-03-07 14:32:02 +09:00
Rui Ueyama c95476d2fe Fix spurious "dupilcate symbol" error for LTO
Fixes https://github.com/rui314/mold/issues/1421
2025-03-07 13:17:37 +09:00
Rui Ueyama 3981f4f3d4 Bump mold version to 2.37.0 2025-03-05 15:36:27 +09:00
Rui Ueyama d4ad627919 Fix test 2025-03-05 14:22:32 +09:00
Rui Ueyama befe780cf8 Make --discard-section a hidden experimental option 2025-03-05 14:15:19 +09:00
Rui Ueyama 879fe682ee Check if TLSDESC is actually supported 2025-03-05 14:14:38 +09:00
Rui Ueyama 805a8b2b62 Install binary packages if available 2025-03-05 14:14:38 +09:00
Rui Ueyama ec360760d6 Merge pull request #1423 from christycylee/discard-section
Discard section
2025-03-05 14:14:33 +09:00
Christy Lee 30541fc1e8 Add --discard-section feature 2025-03-04 12:49:03 -08:00
Rui Ueyama 9ebd889a73 Use actions/checkout@v4 2025-03-03 17:49:59 +09:00
Rui Ueyama f15bd7537e Fix CI 2025-03-03 11:54:00 +09:00
Rui Ueyama f2ac49824c Fix CI 2025-03-03 10:45:35 +09:00
Rui Ueyama 716fb9c06d Merge pull request #1422 from LarsGullik/lto-dep-file
Remove temporary lto files from dependency-file
2025-03-03 10:45:09 +09:00
Rui Ueyama 50231607f9 Improve comments 2025-03-03 09:53:11 +09:00
Lars Gullik Bjønnes fb7a650186 Mark lto temporary files to not be a dependency
Removes the temporary file created by the compiler
from the dependency list created with --dependency-file.

Without this build systems will never report "Nothing to do",
but always do a re-link of the final object.
2025-03-03 01:10:19 +01:00
Lars Gullik Bjønnes a399548dfa Add test for lto in combination with depencency-file
There should not be any temporary files listed
as dependencies.
2025-03-03 00:39:31 +01:00
Rui Ueyama 11a62121e3 Expand a comment 2025-02-28 16:15:07 +09:00
Rui Ueyama a067b0106b Refactor 2025-02-28 16:15:07 +09:00
Rui Ueyama 7607ad6371 Refactor 2025-02-27 09:14:37 +09:00
Rui Ueyama eab8217962 Ensure that the assembler emits desired relocations 2025-02-27 09:10:13 +09:00
Rui Ueyama f46959819b Run more tests on CI 2025-02-26 17:31:38 +09:00
Rui Ueyama a17202d2b5 Support R_X86_64_CODE_6_GOTTPOFF
R_X86_64_CODE_5_{GOTPCRELX,GOTTPOFF,GOTPC32_TLSDESC} and
R_X86_64_CODE_6_{GOTPCRELX,GOTPC32_TLSDESC} are defined just for
completeness (*), so we've implemented all APX relocations with
this change.

* https://gitlab.com/x86-psABIs/x86-64-ABI/-/merge_requests/53
2025-02-26 16:29:32 +09:00
Rui Ueyama 83152acc8b Support R_X86_64_CODE_4_{GOTPCRELX,GOTTPOFF,GOTPC32_TLSDESC}
These relocations are for Intel APX (Advanced Performance Extension)
that extends the number of general-purpose registers from 16 to 32.
GNU assembler that comes with GCC 14 or newer can compile the test code.
2025-02-26 15:59:04 +09:00
Rui Ueyama 88ea3a1aa5 Update LoongArch cross toolchain 2025-02-25 19:58:50 +09:00
Rui Ueyama 0302affc11 Attempt to fix CI 2025-02-25 13:39:17 +09:00
Rui Ueyama 34986d26df Attempt to fix CI 2025-02-25 13:04:26 +09:00
Rui Ueyama 1fbbcec3da Support R_ARM_THM_JUMP8 2025-02-25 11:32:44 +09:00
Rui Ueyama 9ae7e700fa Use gcr.io instead of docker.io 2025-02-23 22:18:23 +09:00
Rui Ueyama f496c31008 Refactor 2025-02-23 21:52:59 +09:00
Christy Lee 48a84176ac Add --discard-section feature 2025-02-21 13:52:16 -08:00
Rui Ueyama 7fe4bee499 Merge pull request #1418 from christycylee/centos 2025-02-21 08:00:36 +09:00
Christy Lee 015d5388a0 Allow installing dependencies on centos 2025-02-20 13:39:04 -08:00
Rui Ueyama 9306f69a61 Report a "symbol duplication" error for LTO
Fixes https://github.com/rui314/mold/issues/1414
2025-02-17 17:03:42 +09:00
Rui Ueyama b82028d45d Merge pull request #1277 from perseoGI/conan/update-readme 2025-02-13 19:43:17 +09:00
PerseoGI 123faa82dc Update readme redirecting to conan website with mold instructions 2025-02-13 11:23:14 +01:00
Rui Ueyama 93f540a10a Use x86 GitHub runners
It looks like Qemu on ARM64 is unstable on ARM64 runners. I observed
occasional failures with SEGV.
2025-02-13 09:31:32 +09:00
Rui Ueyama 143d447a66 Take sentinel into account when estimating section size 2025-02-09 14:34:56 +09:00
Rui Ueyama 8b089859ee Improve tests 2025-02-09 14:22:54 +09:00
Rui Ueyama 4b754fd944 [ARM32] Emit the sentinel in .ARM.exidx 2025-02-09 13:40:07 +09:00
Rui Ueyama b9f938e801 Improve tests 2025-02-09 11:47:12 +09:00
Rui Ueyama 6e50f3a5e7 Remove a redundant test 2025-02-09 11:38:48 +09:00
Rui Ueyama c2df589e05 Skip a test 2025-02-09 11:18:05 +09:00
Rui Ueyama 247ae9500c Fix tests 2025-02-09 10:43:34 +09:00
Rui Ueyama 85e8d4179e Use ARM64 runners because they are faster than x86 runners
I believe this is just due to the lower number of users on ARM64
runners compared to x86. However, based on my measurements, ARM64
builds all binaries in 1h30m, whereas x86 takes 2h30m.
2025-02-09 10:14:05 +09:00
Rui Ueyama 9185ee4b05 [PPC64V1] Garbage-collect text sections 2025-02-09 10:11:45 +09:00
Rui Ueyama 16f75990f9 [ARM32] Garbage-collect .ARM.exidx sections 2025-02-09 10:11:45 +09:00
Rui Ueyama 34053cc52e Fix tests 2025-02-09 10:11:45 +09:00
Rui Ueyama 810946130d Collect generated files on test failure 2025-02-09 08:48:18 +09:00
Rui Ueyama 33b6f3dee9 Rename a test 2025-02-09 07:35:44 +09:00
Rui Ueyama 14a276ecde Set pipefail to test rigorously
We need to stop using `grep -q` because grep terminates immediately after
finding the first match with `-q`, causing SIGPIPE to the process on the
other side of the pipe.
2025-02-09 07:33:54 +09:00
Rui Ueyama 245d265b4b Simplify 2025-02-08 19:28:39 +09:00
Rui Ueyama 0619541234 Simplify 2025-02-08 18:41:01 +09:00
Rui Ueyama 8f57d2affd Simplify 2025-02-08 18:15:07 +09:00
Rui Ueyama 501f66b24c Report duplicate symbol errors even if --gc-sections is given
Fixes https://github.com/rui314/mold/issues/1414
2025-02-08 13:50:06 +09:00
Rui Ueyama 6ef9ffb81a Fix CI 2025-02-08 13:50:06 +09:00
Rui Ueyama 9ab2c31086 Improve tests 2025-02-08 13:35:43 +09:00
Rui Ueyama 182bd7b68c Refactor 2025-02-08 13:00:08 +09:00
Rui Ueyama 09e205d11e Make $plt symbols local
Related to https://github.com/rui314/mold/issues/1413
2025-02-08 11:46:23 +09:00
Rui Ueyama 7e5e396aae Add a missing entry 2025-02-07 23:14:27 +09:00
Rui Ueyama b8914d0a94 Avoid cache line false sharing 2025-02-07 09:46:20 +09:00
Rui Ueyama 359e5134c9 Refactor 2025-02-05 22:16:03 +09:00
Rui Ueyama 4ed6f48f55 Refactor 2025-02-05 18:53:11 +09:00
Rui Ueyama 48beed9fdf Refactor 2025-02-05 18:20:10 +09:00
Rui Ueyama 42cc55e6d9 Update mold.1 (automated commit) 2025-02-03 02:33:40 +00:00
Rui Ueyama 04103ae60a Fix typo 2025-02-03 11:24:09 +09:00
Rui Ueyama 0966f1858c Refactor 2025-02-03 11:24:05 +09:00
Rui Ueyama 56748ed199 Refactor 2025-02-02 18:54:27 +09:00
Rui Ueyama 11387b3c19 Fix relocations for the large code model 2025-02-02 18:53:46 +09:00
Rui Ueyama 04d71affd1 Define __rela_iplt_{start,end} as absolute symbols if not necessary
Previously, these symbols were defined relative to a section. That is
logically correct, because the runtiem is supposed to apply IREL
relocs between `__rela_iplt_start` and `__rela_iplt_end`, so their
actual locations shouldn't matter if there's no IREL relocs (i.e. they
refer to the same location.)

However, under some circumstances, glibc reads `__rela_iplt_start`
before applying base relocations and `__rela_iplt_end` after applying
them due to compiler instruction reordering.

Therefore, we need to make them absolute symbols that don't require
base relocations.

Fixes https://github.com/rui314/mold/issues/1407
2025-02-02 10:54:33 +09:00
Rui Ueyama 6cb2f31236 Refactor 2025-02-02 10:54:33 +09:00
Rui Ueyama 01392bd53a Use Debian 9 as the base image
It's year of 2025, so a distro from 2017 should be old enough to build
our binary distribution.
2025-01-31 20:25:14 +09:00
Rui Ueyama 7cb0b817f5 Fully quailify container image URLs for better compatibility 2025-01-31 16:00:03 +09:00
Rui Ueyama ea38c1544f Upgrade upload-artifcat action 2025-01-31 11:23:14 +09:00
Rui Ueyama 80a2d24948 Compress debug sections in background
If both --separate-debug-info and --compress-debug-info are given, we want to
compress debug sections in background so that the delay caused by compression
is not visible to users.
2025-01-31 10:26:35 +09:00
Rui Ueyama ded9292ba9 Merge pull request #1409 from 0x2Adr1/compress_debug_sections_separate_debug_file
Honor --compress-debug-sections with --separate-debug-file
2025-01-31 10:21:39 +09:00
Adrien Garin 6d1c96f34b Add --no-detach 2025-01-30 07:31:00 -06:00
Rui Ueyama 0849789fd5 Build loongarch64 binaries 2025-01-30 18:06:31 +09:00
Rui Ueyama 7f771d6ed9 Fix test 2025-01-30 18:05:56 +09:00
Rui Ueyama b8a46ede02 Merge pull request #1406 from sicherha/skip_s390x_tests_with_buggy_gcc
[ELF][S390X] Skip tests that fail due to buggy code produced by GCC
2025-01-30 18:03:46 +09:00
Adrien Garin bab7dd1b2e Honor --compress-debug-sections with --separate-debug-file 2025-01-29 11:54:46 -06:00
Rui Ueyama ebe887f0ae Update README 2025-01-29 13:07:01 +09:00
Rui Ueyama df30516630 Unbreak build-all buildbot 2025-01-29 10:07:28 +09:00
Rui Ueyama e8101a4d8a Use the ARM64 builder to build ARM32 binary
I didn't know if Azure ARM64 servers support ARM32 instructions, but
apparently they do because running ARM32 code is much faster on ARM64
GitHub Actions runners.
2025-01-29 08:37:52 +09:00
Rui Ueyama 6291f1b86c Add an entry for Rocky Linux 2025-01-28 11:34:59 +09:00
Christoph Erhardt a570e8953c [ELF][S390X] Skip tests that fail due to buggy code produced by GCC
`bno-symbolic` and `canonical-plt` still fail with GCC 15.
`rodata-name` started failing with GCC 14.
2025-01-24 21:03:07 +01:00
Rui Ueyama 5cc0e9d0a1 Build ARM64 binary on push 2025-01-24 09:47:34 +09:00
Rui Ueyama 4bb8e03b24 Build an ARM64 binary on a native ARM64 host
This change speeds up building an ARM64 binary on a GitHub Actions runner.
2025-01-22 20:56:10 +09:00
Rui Ueyama bcadb4e30f Refactor 2025-01-21 13:43:28 +09:00
Rui Ueyama a2eda6acc4 [RISC-V] Do not leave a useless ld instruction
ld may cause a fault at runtime, so we want to make sure that that is
replaced with a nop if relaxed.
2025-01-21 13:25:09 +09:00
Rui Ueyama e60e04feb2 Fix Windows build
Cherrypick https://github.com/microsoft/mimalloc/commit/5764845c4d3d9fe77ae3c2fbfcc2beedce368b1d
to fix Windows CI.
2025-01-21 08:01:50 +09:00
Rui Ueyama 3651dba6ef Upgrade bundled mimalloc to 2.1.9 2025-01-20 20:18:10 +09:00
Rui Ueyama c20d027441 Refactor 2025-01-20 20:18:10 +09:00
Rui Ueyama 66b5ed66c6 [ARM32] Increase the default page size to 64 KiB
... to maximize compatibility with broader hardware.
2025-01-20 20:16:38 +09:00
Rui Ueyama 0b747d8334 Merge pull request #1404 from kei-gnu/upgrade-checkout-actions
Upgrade checkout actions
2025-01-20 18:49:48 +09:00
kei-gnu 73f86aeadd upgrade checkout actions 2025-01-20 10:52:05 +09:00
Rui Ueyama ceee495584 Fix R_ARM_THM_JUMP19 2025-01-20 09:22:58 +09:00
Rui Ueyama 3d89c1181b Refactor 2025-01-19 21:31:34 +09:00
Rui Ueyama f014a1d015 Read R_ARM_THM_JUMP19 relocation addends 2025-01-19 21:27:01 +09:00
Rui Ueyama f25e450f2b Show "LTO plugin missing" error correctly
Fixes https://github.com/rui314/mold/issues/1403
2025-01-19 12:09:42 +09:00
Rui Ueyama 1fbc6f3dec Refactor 2025-01-19 10:38:38 +09:00
Rui Ueyama 4679573654 Refactor 2025-01-19 10:32:40 +09:00
Rui Ueyama af608f5651 Fix CI 2025-01-17 23:06:07 +09:00
Rui Ueyama 5a9c465210 Fix CI 2025-01-17 21:41:41 +09:00
Rui Ueyama 1822e47a43 Make -z dynamic-undefined-weak work for executable
Fixes https://github.com/rui314/mold/issues/1401
2025-01-17 19:44:40 +09:00
Rui Ueyama 06f20e9d69 Add new x86-64 relocations
These relocations are for Intel Advanced Performance Extensions.
The real implementation will be added once major compilers start
supporting APX.
2025-01-17 08:49:06 +09:00
Rui Ueyama 8929bd4a8d Fix test
Fixes https://github.com/rui314/mold/issues/1400
2025-01-17 08:40:58 +09:00
Rui Ueyama 3b1e2a9aa3 Rename a function 2025-01-15 11:22:11 +09:00
Rui Ueyama 721eafc7dd Refactor 2025-01-15 09:38:55 +09:00
Rui Ueyama 4de8daf34f Fix typo 2025-01-15 09:38:55 +09:00
Rui Ueyama f7624ee1f9 Simplify 2025-01-15 08:15:35 +09:00
Rui Ueyama 500416f8f6 Improve test 2025-01-15 07:50:56 +09:00
Rui Ueyama c6c12d43f5 Fix CI 2025-01-14 14:08:22 +09:00
Rui Ueyama 7fc2a6e935 Fix CI 2025-01-14 13:25:32 +09:00
Rui Ueyama 9fe8831db3 Fix CI 2025-01-14 12:41:29 +09:00
Rui Ueyama 2bb42badde Do not emit bad dynamic relocations when sections are shrunk 2025-01-14 11:42:40 +09:00
Rui Ueyama 48077dd91a Refactor 2025-01-11 21:28:01 +09:00
Rui Ueyama cf0d750734 Set the basename of an output path to .gnu.version_d 2025-01-11 19:49:36 +09:00
Rui Ueyama 48dd767ad3 Refactor 2025-01-11 19:47:50 +09:00
Rui Ueyama 3c89ad66a2 Refactor 2025-01-11 17:44:55 +09:00
Rui Ueyama 8cd5574bfb Use shrink_to_fit instead of = {} assignment
I'm no C++ expert, but it looks like `vec = {}` is not guaranteed to
release memory.
2025-01-11 14:01:10 +09:00
Rui Ueyama 0ca543e104 Optimize create_output_section() 2025-01-11 13:12:53 +09:00
Rui Ueyama e5f80fd822 Refactor 2025-01-11 13:12:53 +09:00
Rui Ueyama fe8e061427 Refactor
This new logic was found by ChatGPT o1 pro.
2025-01-10 17:34:00 +09:00
Rui Ueyama 05ceb7da0d Revert "Build daily binary for loongarch64"
This reverts commit 12a141b03b to unbreak
buildbots.
2025-01-10 17:26:34 +09:00
Rui Ueyama 3cec2e68be Upgrade Clang 2025-01-10 10:50:44 +09:00
Rui Ueyama 12a141b03b Build daily binary for loongarch64 2025-01-10 09:37:43 +09:00
Rui Ueyama 562361345b Use the latest version of clang 2025-01-10 08:09:58 +09:00
Rui Ueyama 432ffeabf5 Fix typo 2025-01-10 07:59:05 +09:00
Rui Ueyama be0870719f Check if test system supports TLSDESC 2025-01-09 22:12:24 +09:00
Rui Ueyama a66750b1ec Update README.md 2025-01-09 21:06:26 +09:00
Rui Ueyama c3a957c4dc Bump mold version to 2.36.0 2025-01-09 12:53:32 +09:00
Rui Ueyama f193d2b603 Format 2025-01-09 12:53:32 +09:00
Rui Ueyama d2859f597c Simplify 2025-01-09 11:51:58 +09:00
Rui Ueyama 06a21efbdb Improve a test 2025-01-08 17:40:52 +09:00
Rui Ueyama 13aa1685c9 Update mold.1 (automated commit) 2025-01-08 07:00:45 +00:00
Rui Ueyama 55fcd3facf Document --package-metadata 2025-01-08 15:58:11 +09:00
Rui Ueyama 5d454308f2 Remove BitVector and use std::vector<bool>
Even though std::vector<bool> is often critisized for being inconsistent
with other std::vector<T> where T != bool, it suffices for our purpose.
2025-01-08 13:51:21 +09:00
Rui Ueyama a01fe171d0 Refactor 2025-01-08 11:11:16 +09:00
Rui Ueyama 207c4e99f0 Pass --pids-limit=-1 to podman
Without it, fork(2) may fail with EAGAIN inside podman containers.
2025-01-07 16:13:48 +09:00
Rui Ueyama 0dfdc014e2 Change the path of mold binary 2025-01-07 13:24:06 +09:00
Rui Ueyama eec3f6b716 [RISC-V] Fix R_RISCV_HI20 relaxation
We didn't compensate LO12's sign extension when computing the range.
This resulted in a wrong relaxation that breaks the output binary.
2025-01-07 11:41:22 +09:00
Rui Ueyama a483530a3c Refactor 2025-01-07 11:41:22 +09:00
Rui Ueyama 0d1068e460 Refactor 2025-01-06 22:31:06 +09:00
Rui Ueyama fd0fc9a3cf Refactor 2025-01-06 22:27:33 +09:00
Rui Ueyama 40b881b7bb Simplify 2025-01-06 16:41:48 +09:00
Rui Ueyama 35cde381b7 Refactor 2025-01-06 15:41:25 +09:00
Rui Ueyama 6d8c5ca90b Fix symbol size computation 2025-01-06 13:50:15 +09:00
Rui Ueyama aa4d065fde Do not call get_r_delta() unless necessary
get_r_delta performs a binary search on a vector, so it's not cheap.
If we can avoid calling that function, we should.
2025-01-06 13:36:24 +09:00
Rui Ueyama 0af930ae28 Refactor 2025-01-06 11:07:00 +09:00
Rui Ueyama d29fabf084 Refactor 2025-01-05 22:40:46 +09:00
Rui Ueyama 2ee1d47ca8 Do not add useless entries to r_deltas 2025-01-05 20:52:59 +09:00
Rui Ueyama 8e27616934 Include the number of code-shrinking relocations into the output of `--stats' 2025-01-05 20:12:31 +09:00
Rui Ueyama a7b8c6061a Ensure that the first byte of .dynsym is '\0' 2025-01-05 19:48:46 +09:00
Rui Ueyama aef5f3231c Refactor 2025-01-05 15:31:25 +09:00
Rui Ueyama 38ae2990a0 Refactor 2025-01-05 14:53:50 +09:00
Rui Ueyama e6345d5b14 Reflect the result of code-shrinking relaxation to symbol sizes 2025-01-05 13:49:07 +09:00
Rui Ueyama ba35344bd0 Add a comment 2025-01-05 12:50:50 +09:00
Rui Ueyama c33ca0b001 Refactor 2025-01-05 11:09:45 +09:00
Rui Ueyama 77935f1807 Accept very large input text sections 2025-01-04 22:11:24 +09:00
Rui Ueyama 3234d88600 [RISC-V][LoongArch] Reduce memory usage for shrinking sections
Previously, 4 bytes were allocated for each relocation in a text
section to store the number of bytes removed before each relocation.
Now, only the locations where bytes are removed are stored.

This commit improves performance by ~4% when linking clang-19 for RISC-V.
2025-01-04 21:12:02 +09:00
Rui Ueyama 1e8cee85bb Name each thunk symbol unique name
Previously, if symbol `foo` appears more than once in multiple thunks,
they got the same name `foo$thunk`. Now, they are named `foo$thunk0`,
etc.
2025-01-03 17:37:17 +09:00
Rui Ueyama 2352ec647d Fix a range extension thunk creation issue
Previously, we failed to create range extension thunks if we have
a very large input section.
2025-01-03 17:37:17 +09:00
Rui Ueyama 88d5e74825 Refactor 2025-01-03 17:37:17 +09:00
Rui Ueyama d884620f9d Attempt to fix buildbots 2025-01-03 14:28:04 +09:00
Rui Ueyama e55363ec8a Attempt to fix CI 2025-01-03 13:32:00 +09:00
Rui Ueyama a297859c53 Emit an IRELATIVE relocation instead of GLOB_DAT for an ifunc in DSO
Fixes https://github.com/rui314/mold/issues/1385
2025-01-03 12:53:34 +09:00
Rui Ueyama 0a112d0aa9 Refactor 2025-01-03 10:52:37 +09:00
Rui Ueyama 6a342f2c64 Refactor 2025-01-03 09:15:55 +09:00
Rui Ueyama d0bcd35bf5 Add a safety margin when computing branch offsets 2025-01-02 21:34:47 +09:00
Rui Ueyama f609510e3c Refactor 2025-01-02 21:21:25 +09:00
Rui Ueyama 9fc0ace817 Reduce memory usage of range extension thunks
Previously, we allocate 4 byte struct for each relocation to store a
reference to a thunk in case the relocation needs to branch to a thunk.
Most references are null because most relocations don't need thunks.

This commit changes how we manage references to range extension thunks.
Now, thunk addresses are stored to symbols rather than to relocations.

Linking clang-19 for ARM64 is now ~7% faster than before.
2025-01-02 18:09:45 +09:00
Rui Ueyama 89f3ba89da Refactor 2025-01-02 12:05:00 +09:00
Rui Ueyama 09e7616186 Refactor 2025-01-01 08:43:38 +09:00
Rui Ueyama 2342938cea Simplify 2024-12-31 21:43:28 +09:00
Rui Ueyama 047675b762 Unbreak ARM32 buildbot 2024-12-31 17:15:17 +09:00
Rui Ueyama 531e43eddf Do not mark DSOs as needed if they are referenced only indirectly
Fixes https://github.com/rui314/mold/issues/1395
2024-12-31 17:15:15 +09:00
Rui Ueyama 50a75c25e3 Refactor 2024-12-31 17:15:14 +09:00
Rui Ueyama 7a69bd45ac Refactor 2024-12-30 23:18:25 +09:00
Rui Ueyama 006de74706 Merge .rodata.cst.<symbol-name>.cst<N> sections to .rodata.cst<N> 2024-12-30 14:43:09 +09:00
Rui Ueyama 952fdde7ae Add a relocation overflow check 2024-12-30 11:12:17 +09:00
Rui Ueyama dfce2fc8fa [s390x] Reserve _GLOBAL_OFFSET_TABLE_[2]
ELF Application Binary Interface s390x Supplement Version 1.6.1, p.58 says
that "The doublewords at _GLOBAL_OFFSET_TABLE_[1...2] are reserved for
system use."

We had previously reserved GOT[0] and GOT[1], but we did not reserve GOT[2].

It's most likely that GOT[2] is not actually in use, as it appears that
mold-linked programs work without it. But we need to adhere to the psABI.
2024-12-30 09:05:10 +09:00
Rui Ueyama 50fd4beb3e [s390x] Relax GOT-loading LGRL instructions 2024-12-30 07:55:55 +09:00
Rui Ueyama 0b8090994b Simplify 2024-12-29 10:02:29 +09:00
Rui Ueyama 37e6d2f4cb Merge pull request #1394 from sicherha/fix_ifunc-alias_test_on_RHEL10
Fix `arch-x86_64-ifunc-alias` test on RHEL 10
2024-12-28 19:26:21 +09:00
Christoph Erhardt 0c06bef82b Fix arch-x86_64-ifunc-alias test on RHEL 10
On RHEL 10, GCC is configured with the `--with-arch_64=x86-64-v3`
option, which makes it implicitly use the `-march=x86-64-v3`
command-line flag by default.

This breaks the `arch-x86_64-ifunc-alias` test: since the `x86-64-v3`
microarchitecture level includes SSSE3 and AVX2, GCC appears to
interpret the `default` and `ssse3,avx2` targets as the same thing, and
omits generating a resolver function.

Fix the test by forcing GCC's target architecture to the `x86-64`
baseline.
2024-12-28 10:47:07 +01:00
Rui Ueyama 53dbfd96de Make Ctrl-C work for dist.sh 2024-12-28 18:40:15 +09:00
Rui Ueyama 938f145150 Attempt to fix CI 2024-12-27 22:35:00 +09:00
Rui Ueyama 930ba5a7f1 Do not compress .tar.gz with zip 2024-12-27 22:35:00 +09:00
Rui Ueyama 4107c336a8 Simplify 2024-12-27 22:15:25 +09:00
Rui Ueyama d4db31ceaf Merge pull request #1389 from sicherha/improvement_lambdas_and_ranges
Small improvements: lambdas and ranges
2024-12-27 19:33:03 +09:00
Rui Ueyama 30f68106db Merge pull request #1391 from sicherha/debian_11_CI_pipeline
Add CI pipeline for Debian 11
2024-12-27 19:32:24 +09:00
Rui Ueyama e56fc35820 Attempt to unbreak GitHub Actions 2024-12-27 19:27:36 +09:00
Rui Ueyama b8fa19bf24 Merge pull request #1392 from chinrw/dev
Try again to use Podman instead of Docker
2024-12-27 18:30:27 +09:00
Ruowen Qin d9bd1468d0 Try again to use Podman instead of Docker
Switch from Docker to Podman for container builds and remove unused
buildx steps. Also this commit updates GitHub Actions workflows to use
redhat-actions/podman-login and docker/setup-qemu-action@v3, removing
references to Docker Buildx.

Fixes: ec1d7120aa ("Use docker only on GitHub Actions")
Signed-off-by: Ruowen Qin <[email protected]>
2024-12-27 17:11:22 +08:00
Christoph Erhardt b5dfab17da Add CI pipeline for Debian 11
Debian 11 ships GCC 10 and CMake 3.18, which happen to be precisely the
minimum versions required for building mold from source.

Add a CI pipeline to prevent future commits from inadvertently breaking
compatibility with older toolchains.
2024-12-27 08:50:42 +01:00
Christoph Erhardt 7d1c970a39 Make non-capturing lambdas actually non-capturing 2024-12-27 08:37:48 +01:00
Rui Ueyama e6e1cfac31 Handle "L0^A" symbol as local
I don't know about the details, but it looks like "L0^A" is sometimes
used as a local symbol name.
2024-12-27 16:35:43 +09:00
Rui Ueyama 38bca63747 Make the source directory read-only for the build script
So that even a malicious container cannot silently modify our
source files.
2024-12-27 06:29:01 +09:00
Rui Ueyama 606ce43c5b Add loongarch64 support 2024-12-27 06:15:29 +09:00
Rui Ueyama 41413f9d7c Refactor 2024-12-26 20:42:39 +09:00
Rui Ueyama ec1d7120aa Use docker only on GitHub Actions
I don't know how to fix the build breakage on GitHub, so let's keep
using Docker there.
2024-12-26 19:56:26 +09:00
Rui Ueyama bcce7091e7 Run build-x86 GitHub Action on push to any branch 2024-12-26 18:03:55 +09:00
Rui Ueyama e3cb2aa09e Use podman instead of docker
I prefer podman over docker because podman works without the daemon
and doesn't need the root priviledge.
2024-12-26 17:10:03 +09:00
Rui Ueyama 7e7d75fb4b Format 2024-12-26 17:08:52 +09:00
Rui Ueyama e333e435bd Merge pull request #1388 from sicherha/improvement_cleanup
Miscellaneous small cleanups
2024-12-26 17:08:43 +09:00
Christoph Erhardt afd2fab630 Replace putenv() with setenv()
The use of `putenv()` is discouraged for portability reasons:
https://pubs.opengroup.org/onlinepubs/9799919799/functions/putenv.html
2024-12-25 19:17:17 +01:00
Christoph Erhardt 26493c0ca8 Replace stat() with equivalent std::filesystem calls 2024-12-25 19:17:17 +01:00
Christoph Erhardt 9402ca357c Construct std::vector elements in place
This is slightly more efficient than creating the objects and
subsequently copying them into the vector.
2024-12-25 19:17:17 +01:00
Christoph Erhardt 5572679c2d Remove duplicate if-case
Reported by clang-tidy: the same code already exists 50 lines below this
one.
2024-12-25 13:49:42 +01:00
Rui Ueyama 179d5d4b5c Merge pull request #1387 from sicherha/improvement_includes 2024-12-25 20:32:40 +09:00
Christoph Erhardt f169f1c45c Reorganise TBB #includes
Add missing `#include`s and remove unused ones.
2024-12-25 11:41:04 +01:00
Christoph Erhardt e05f3b5362 Include the C++ variant of C headers 2024-12-25 11:41:02 +01:00
Christoph Erhardt a5b3a22517 Remove unused #includes 2024-12-25 11:40:29 +01:00
Rui Ueyama 82263dce4d Merge pull request #1386 from jinghao-jia/main
Restore executable overwriting behavior on Linux 6.13 or later
2024-12-25 18:12:38 +09:00
Jinghao Jia 1a012dda4d Restore executable overwriting behavior on Linux 6.13 or later
Linux upstream has reverted commit 2a010c412853 ("fs: don't block
i_writecount during exec") and the change is merged in the 6.13 cycle.
This means the executable overwriting behavior can be restored on Linux
kernel version 6.13 or later.

Note that there is probably not much we can do for 6.11 / 6.12 kernels.
Although the reversion is backported, many distros do not use upstream
sub-version numbers which means we cannot determine whether the kernel
has this reversion based on its version number.

Signed-off-by: Jinghao Jia <[email protected]>
2024-12-25 01:55:07 -06:00
Rui Ueyama dfe02f41cb Add a test 2024-12-24 17:16:34 +09:00
Rui Ueyama b582ad3589 Format 2024-12-24 17:16:32 +09:00
Rui Ueyama 2d7b6b23e3 [LoongArch] Increase the max page size from 16 KiB to 64 KiB
Even though 16 KiB is the common page size, the Linux kernel for
LoongArch supports 64 KiB pages. Currently, mold-generated binaries
run only on systems with <=16 KiB page.

Note that unlike GNU ld, increasing the maximum page size doesn't
increase the on-disk binary size of generated binaries.
2024-12-22 17:44:53 +09:00
Rui Ueyama 7ddc8f4654 Rename --encoded-package-metadata to --package-metadata
This is an alternative to https://github.com/rui314/mold/pull/1383

The upcoming version of GNU binutils's ld now treats arguments for
`--package-metadata` as percent-encoded strings and decodes them before
writing to .note.package.

Additionally, GNU ld will support `%[<charname>]`, where `<charname>` can be
`comma`, `lbrace`, `rbrace`, `quot`, or `space`. This feature could improve
readability when manually writing command-line arguments.

That said, I don't believe we need to support these metacharacters, for the
following reasons:

1. This option is specifically designed for distribution package maintainers
   to embed metadata into binaries, and is not intended for general use.

2. Command-line arguments for this option are always generated by build
   scripts, not written manually.

If we need these character names, we can add that later.

Furthermore, GNU ld does not appear to report errors for orphan '%'
characters that are not followed by valid hex values; instead, it simply
passes through such characters. I find this behavior fragile. We will report
an error for invalid '%' characters.

With these considerations in mind, I'll just rename the existing
`--encoded-package-metadata` option to `--package-metadata`.
2024-12-22 10:07:23 +09:00
Rui Ueyama 4bbd93f06f Refactor 2024-12-22 10:07:23 +09:00
Rui Ueyama a76d259c0b Merge pull request #1384 from bdrung/ctest-with-logs 2024-12-22 05:27:57 +09:00
Benjamin Drung a557ea1012 ci: run ctest with --output-on-failure
In case ctest fails in the CI, no error logs are printed. This makes
debugging harder.

So run ctest with `--output-on-failure` to ease debugging in case of
failures.
2024-12-21 20:37:31 +01:00
Rui Ueyama 06f6a56043 Merge pull request #1380 from sicherha/fix_system_mimalloc
Add missing `#include "common.h"` - fixes broken `MOLD_USE_SYSTEM_MIMALLOC`
2024-12-20 10:39:39 +09:00
Christoph Erhardt ac9568f17b Disable mimalloc when a sanitizer is used
Combining mimalloc and AddressSanitizer triggers a segmentation fault in
many tests.

Combining mimalloc and ThreadSanitizer leads to duplicate symbols for
the `new` and `delete`
operators, causing the build to fail.
Example message from a failed GitHub Actions build:
```
mold: error: duplicate symbol:
third-party/mimalloc/libmimalloc-debug.a(alloc.c.o):
/usr/lib/llvm-18/lib/clang/18/lib/linux/libclang_rt.tsan_cxx-x86_64.a(tsan_new_delete.cpp.o):
operator delete(void*, std::align_val_t)
```
2024-12-20 00:03:47 +01:00
Christoph Erhardt 9c9145eb6e Add missing #include "common.h"
This allows the compiler to make sure that the implementation of
`set_mimalloc_options()` matches its declaration.

More importantly, it indirectly pulls in `config.h`, where the macros
`MOLD_USE_SYSTEM_MIMALLOC` and `MOLD_USE_MIMALLOC` are conditionally
defined. Without these, the build configuration is ignored.
2024-12-19 22:06:23 +01:00
Rui Ueyama 3cb551424b Fix CI 2024-12-16 17:27:33 +09:00
Rui Ueyama ee8e50a3f7 Bump mold version to 2.35.1 2024-12-15 13:26:50 +09:00
Rui Ueyama 4bbf85c2ea Refactor 2024-12-13 11:13:06 +09:00
Rui Ueyama 6257f12c1f Simplify 2024-12-12 12:45:53 +09:00
Rui Ueyama ea460af09c Fix function prototypes
Fixes https://github.com/rui314/mold/issues/1370
2024-12-12 12:13:29 +09:00
Rui Ueyama a0f9641ae1 Upgrade BLAKE3 to 1.5.5 2024-12-11 20:13:03 +09:00
Rui Ueyama 9204e1dc12 Upgrade OneTBB to 2022.0.0 2024-12-11 20:13:03 +09:00
Rui Ueyama 396666fd48 Preserve the original relocation order as much as possible 2024-12-11 20:13:03 +09:00
Rui Ueyama 74024ea0df Refactor 2024-12-11 15:22:22 +09:00
Rui Ueyama 79aac78581 Refactor 2024-12-09 18:37:01 +09:00
Rui Ueyama 04066d1c1a [RISC-V] Remove GP-relative relocations
These relocations were deleted from the psABI in
https://github.com/riscv-non-isa/riscv-elf-psabi-doc/commit/ad025461defeb610c684427719ce5ac5e8d8d857.
2024-12-09 18:33:30 +09:00
Rui Ueyama 2a78b1b333 Make ICF deterministic
Fixes https://github.com/rui314/mold/issues/1377
2024-12-09 15:13:20 +09:00
Rui Ueyama 27162691b7 Bump mold version to 2.35.0 2024-12-08 14:58:53 +09:00
Rui Ueyama fe6090efff Revert "Upgrade RV32 toolchain"
This reverts commit c7042f416e because it broke CI.
2024-12-05 18:29:47 +09:00
Rui Ueyama c7042f416e Upgrade RV32 toolchain 2024-12-05 16:44:09 +09:00
Rui Ueyama e56b649968 Increase the maximum retry count for our concurrent hashmap
Fixes https://github.com/rui314/mold/issues/1365
2024-12-05 14:17:56 +09:00
Rui Ueyama 0f56a792c3 Upgrade LoongArch toolchain 2024-12-04 16:06:01 +09:00
Rui Ueyama 5bb914553f Fix typo 2024-11-26 13:21:41 +09:00
Rui Ueyama 26d898e9bd Use macos-latest instead of macos-12
macos-12 has been deprecated.
2024-11-26 07:45:07 +09:00
Rui Ueyama eec3bb4478 Simplify 2024-11-26 07:01:50 +09:00
Rui Ueyama fa3026611b Merge pull request #1373 from bl4ckb0ne/test-x86-variants
test: match more x86 arch name variants
2024-11-26 06:59:21 +09:00
Simon Zeni a649156e0f test: match more x86 arch name variants 2024-11-25 11:06:25 -05:00
Rui Ueyama e2e1146a0b [s390x] Fix .hash section
For whatever reason, the entry size for .hash is 8 bytes insetad of 4 on s390x.

Fixes https://github.com/rui314/mold/issues/1372
2024-11-23 13:20:07 +09:00
Rui Ueyama baf4f76f53 Report an error even without NDEBUG=1 if a ConcurrentMap becomes full 2024-11-02 17:05:01 +09:00
Rui Ueyama 85be067813 Try to silence benign mimalloc warning messages
https://github.com/rui314/mold/issues/1367
2024-10-30 13:29:57 +09:00
Rui Ueyama 8e4f7b5383 Do not ovewrite an existing file if running on Linux 6.11.0 or later
https://github.com/rui314/mold/issues/1361
2024-10-29 16:33:45 +09:00
Rui Ueyama a066efa995 Use madvise(2) if available 2024-10-29 16:01:04 +09:00
Rui Ueyama f184021eca Refactor 2024-10-27 16:42:36 +09:00
Rui Ueyama 0841ffcb20 Refactor 2024-10-20 11:33:38 +09:00
Rui Ueyama 9cf1165a34 Fix CI 2024-10-19 11:33:06 +09:00
Rui Ueyama 9aa4ded614 Remove LICENSE.third-party
We don't need to create a copy of license files for third-party software
as we keep an original license file for each subdirectory. It is probably
a bit risky to create copies of license files because it is easy to become
out-of-sync. So I'll just remove this file.
2024-10-19 11:08:12 +09:00
Rui Ueyama e7e764ccf0 Revert "Refactor"
This reverts commit 5ba5b4960f to fix CI.
2024-10-19 09:47:53 +09:00
Rui Ueyama 5ba5b4960f Refactor 2024-10-18 20:57:07 +09:00
Rui Ueyama bb57289385 Refactor 2024-10-18 18:46:23 +09:00
Rui Ueyama 0e28522b5c Fix filler instruction for sh4be
If running in big-endian mode, SH4 processors expects both data and
instructions to be encoded in big-endian. Therefore, nop need to be
reversed for sh4be.
2024-10-17 16:13:46 +09:00
Rui Ueyama 882e7eb15b Support big-endian ARM64 2024-10-17 12:27:12 +09:00
Rui Ueyama 3a0bb87dfe Refactor 2024-10-17 12:27:10 +09:00
Rui Ueyama 832973cef5 Run a test for -z rewrite-endbr on GitHub Actions 2024-10-16 16:22:51 +09:00
Rui Ueyama 0cb9fc613c Support big-endian SH4
This commit adds experimental big-endian SH4 support. Since we already
support little-endian SH4, it wasn't too difficult to support its big-
endian variant.

Some tests related to C++ exceptions fail due to mysterious errors.
I don't have time to debug it, so I'll disable them for now.

Closes https://github.com/rui314/mold/issues/1273
2024-10-15 14:23:54 +09:00
Rui Ueyama 8931cf81f4 Update mold.1 (automated commit) 2024-10-15 04:49:05 +00:00
Rui Ueyama ecdd0f71b6 Update the man page 2024-10-15 13:48:07 +09:00
Rui Ueyama cb9344fab8 Run tests 2024-10-15 13:48:05 +09:00
Rui Ueyama 382e45ea30 Align .gnu.version_r and .gnu.version_d to 4 byte boundaries
They don't contain 8 byte date items, so they don't need to be aligned
to 8 byte boundaries.
2024-10-11 19:51:52 +09:00
Rui Ueyama fdaa13c374 Merge .gnu.build.attributes sections into a single section
It looks like gold does this.
2024-10-11 19:42:14 +09:00
Rui Ueyama fa2411cc08 Refactor 2024-10-10 10:14:36 +09:00
Rui Ueyama a69b332f86 Enable GC-sections and ICF not only for Release but also for RelWithDebInfo 2024-10-09 21:11:55 +09:00
Rui Ueyama 20dccf2b77 Refactor 2024-10-09 15:59:15 +09:00
Rui Ueyama ffd10dd447 Do not include mimalloc-new-delete.h if mimalloc is disabled
Including mimalloc-new-delete.h overrides new and delete operators
with mimalloc. If a user choose not to use mimalloc, we shouldn't
include that file.

https://github.com/rui314/mold/issues/1355
2024-10-09 14:41:36 +09:00
Rui Ueyama ce9cdb1bae [ICF] Handle RELRO sections correctly
When control reaches here, `output_section` is always a nullptr.
2024-10-08 19:35:03 +09:00
Rui Ueyama 979084b593 Use .llvm_addrsig to find address significance for executable sections
This change should allow more executable sections to be merged by ICF.
2024-10-08 17:47:41 +09:00
Rui Ueyama c562c733ce Move target-dependent data members to the end of class definitions
So that the data layouts for other data members become the same.
This makes ICF work better.
2024-10-08 17:45:25 +09:00
Rui Ueyama 3c6f57cc27 Refactor 2024-10-08 14:57:32 +09:00
Rui Ueyama c16d6e8cc0 Remove to_abs_path
This function is not necessary.

I don't know how it could leak memory, but it was reported
that it does in https://github.com/rui314/mold/issues/1355.
2024-10-08 13:01:12 +09:00
Rui Ueyama 91e2daf3bf Create release binaries with Clang instead of GCC
LLVM/Clang works better with mold's --icf=safe because of the presence
of LLVM address significance table.
2024-10-07 19:29:01 +09:00
Rui Ueyama 7204c6dec8 Do not set a value to relr.dyn section's sh_link
The ELF ABI doesn't say anything about sh_link field of SHT_RELR section,
so setting a value to it is simply wrong.
2024-10-06 15:28:36 +09:00
Rui Ueyama 3441cbb636 Enable GC sections and ICF for binary distributions to reduce file size 2024-10-06 09:49:32 +09:00
Rui Ueyama 3070e798f3 Refactor 2024-10-05 20:05:43 +09:00
Rui Ueyama 754354991b Simplify 2024-10-05 15:37:03 +09:00
Rui Ueyama 86fe020558 Fix CI 2024-10-05 15:13:46 +09:00
Rui Ueyama 40e7b983d6 Fix CI 2024-10-05 14:44:22 +09:00
Rui Ueyama f69ecd616d Merge pull request #1353 from praetorian20/main 2024-10-05 13:03:17 +09:00
Rui Ueyama d683f62e01 Add a rule for KDE Neon
Fixes https://github.com/rui314/mold/issues/1354
2024-10-05 12:44:20 +09:00
Ashish Sadanandan 205a091924 Use readelf for compress-debug-sections verification
RHEL 8.7's version of dwarfdump (2018-01-29) is too old to recognize the
SHF_COMPRESSED flag in debug info, so the compress-debug-sections test
fails despite the debug sections being compressed as intended.

To fix this, we will use readelf to dump information about the
executable. The COMPRESSED flag can be verified using this.
2024-10-04 20:44:05 -06:00
Rui Ueyama 7b4d2e77ee Refactor 2024-10-05 10:08:23 +09:00
Rui Ueyama 2c9ba071bc Refactor 2024-10-04 20:47:50 +09:00
Rui Ueyama de65f6bd75 Bump mold version to 2.34.1 2024-10-04 15:00:22 +09:00
Rui Ueyama 186272a425 Handle R_ARM_TARGET1 as a synonym for R_ARM_ABS32
This fixes a regression introduced in 431fc39db0.

Fixes https://github.com/rui314/mold/issues/1351
2024-10-04 14:18:13 +09:00
Rui Ueyama 5fcbf25260 Refactor 2024-10-03 15:19:47 +09:00
Rui Ueyama 16eb5139f1 [RISC-V] Set DT_RISCV_VARIANT_CC if there's a dynamic symbol with STO_RISCV_VARIANT_CC 2024-10-02 20:41:13 +09:00
Rui Ueyama 35be7ba887 Refactor 2024-10-02 20:39:50 +09:00
Rui Ueyama 55f0289a41 Non-SHF_ALLOC sections' addresses should be displayed as zero for --Map 2024-09-30 13:28:22 +09:00
Rui Ueyama 36dd6babe1 Refactor 2024-09-29 19:04:22 +09:00
Rui Ueyama 91b6818b62 Revert "Refactor"
This reverts commit 9e9539144e because
the new code didn't compile on macOS.
2024-09-27 21:45:49 +09:00
Rui Ueyama 9e9539144e Refactor 2024-09-27 17:51:54 +09:00
Rui Ueyama feb9b3eb4e Set 0 to DT_AARCH64_VARIANT_PCS
It looks like only the presence of the entry is important, but by
convention, GNU ld and lld set 0 to a DT_AARCH64_VARIANT_PCS dynamic
entry, so we should follow suit.
2024-09-27 17:51:54 +09:00
Rui Ueyama 4b3fc4e82a Refactor 2024-09-27 17:51:54 +09:00
Rui Ueyama ed7cc1b85a Bump mold version to 2.34.0 2024-09-25 10:01:39 +09:00
Rui Ueyama 3711ddb95e Drop DEC Alpha processor support
Keeping DEC Alpha support is difficult because there are no Alpha
machines available for toolchain developers, and even if there were,
no modern Linux distros still support Alpha. For example, Debian
dropped Alpha support in Debian 6.0. As a result, I cannot test my
linker with real-world programs.

In fact, I believe mold has never been able to link real-world large
programs. Object files for Alpha are compiled with the small code
model by default, so it is not easy to support it in the linker.

There's no means to verify that my implementation is correct for Alpha
because Alpha's psABI has never been published by DEC. The most
"reliable" source of correctness is GNU ld's source code and comments.

Last but not least, I believe there are literally zero mold/Alpha
users. Unlike some other retro computers like the m68k, Alpha doesn't
seem to get much love from the community, perhaps because the
availability of Alpha machines was limited in the first place, even in
the 90s.

Therefore, I'll remove Alpha support now. If there's someone who wants
to keep it, we can resurrect and re-test it.

This change should make the mold binary size a little smaller.
2024-09-24 17:29:34 +09:00
Rui Ueyama 0ee12e4115 Do not strip linker-synthesized symbols when --retain-symbols-file is given
This is an attempt to fix https://github.com/rui314/mold/issues/1346
2024-09-21 17:08:32 +09:00
Rui Ueyama 87c9929df9 Merge pull request #1345 from andreas-schwab/main 2024-09-19 17:32:46 +09:00
Andreas Schwab f5f14d3faa test/absolute-symbols.sh: move symbol address out of the heap
Fixes #1340

Signed-off-by: Andreas Schwab <[email protected]>
2024-09-19 09:53:41 +02:00
Rui Ueyama 5be5ea9ff5 Fix CI 2024-09-18 15:18:44 +09:00
Rui Ueyama f1739bffd0 Refactor 2024-09-18 15:03:54 +09:00
Rui Ueyama 1efbe3f141 Do not lower the priority of DSO symbols than in-archive symbols
This commit reverts 0612ea41f1 and
instead solves the original issue with a different approach.

Now, if a hidden symbol was resolved to a DSO symbol, we'll redo
symbol resolution so that the symbol won't be resolved to the same one
next time.

This should fix our build-all buildbot.
2024-09-17 13:23:17 +09:00
Rui Ueyama 5ba1c36fbb Refactor 2024-09-17 12:27:08 +09:00
Rui Ueyama c217134d97 Refactor 2024-09-17 11:58:42 +09:00
Rui Ueyama fea866d910 Refactor 2024-09-15 19:18:06 +09:00
Rui Ueyama 3b3bc7b41e Make builders runnable by hand 2024-09-15 19:13:45 +09:00
Rui Ueyama 0612ea41f1 Lower the priority of DSO symbols than in-archive symbols
Assume both `foo.a` and `bar.so` define the same symbol `baz`.
If `baz`'s symbol visibility is hidden, it needs to be resolved within
the output file, i.e., from `foo.a`. However, previously, such symbol
was resolved to the one in `bar.so`.

To fix the problem, we'll lower the symbol priority for DSOs.

Fixes https://github.com/rui314/mold/issues/1342
2024-09-15 17:17:43 +09:00
Rui Ueyama 940ac8125c Refactor 2024-09-15 16:22:18 +09:00
Rui Ueyama 583ba8ade1 Fix daily builder 2024-09-15 11:13:56 +09:00
Rui Ueyama 82feeed693 Build mold binary with mold 2024-09-14 19:11:51 +09:00
Rui Ueyama e7e41329ef Refactor 2024-09-14 17:14:30 +09:00
Rui Ueyama 63d990e49a Upgrade upload-artifact github action 2024-09-14 16:00:17 +09:00
Rui Ueyama 8d5b753337 Fix buildbot 2024-09-14 15:10:50 +09:00
Rui Ueyama 431fc39db0 Change how we handle word-size absolute relocations
Word-size absolute relocations, such as R_X86_64_64, are special from
the linker's point of view because only such symbols can be promoted
to dynamic symbols. This patch handles them separately.
2024-09-14 11:59:20 +09:00
Rui Ueyama 7734a93217 Improve a test 2024-09-14 08:04:15 +09:00
Rui Ueyama 806d7409da Refactor 2024-09-07 19:47:18 +09:00
Rui Ueyama 2e326cc006 Refactor 2024-09-07 15:31:56 +09:00
Rui Ueyama e1ace5ea00 Add a FreeBSD test runner 2024-09-02 14:58:30 +09:00
Rui Ueyama 8e010c2095 Update comments 2024-09-01 13:20:50 +09:00
Rui Ueyama 6758b53bff Fix CI 2024-08-31 15:14:26 +09:00
Rui Ueyama f6822fb0f3 Make executables iwth zero-size segments loadable on FreeBSD
Fixes https://github.com/rui314/mold/issues/1338
2024-08-31 14:52:11 +09:00
Rui Ueyama f0d39b2460 Add a rule for FreeBSD 2024-08-31 13:26:52 +09:00
Rui Ueyama a3e69502b0 Refactor 2024-08-30 12:04:56 +09:00
Rui Ueyama 4645248106 Merge pull request #1337 from leleliu008/fix-netbsd-no-alloca.h 2024-08-30 04:58:24 +09:00
leleliu008 80648584cf alloca.h is missing on NetBSD
alloca(3) is defined in stdlib.h on NetBSD

Reference: https://man.netbsd.org/alloca.3

Signed-off-by: leleliu008 <[email protected]>
2024-08-29 20:22:03 +08:00
Rui Ueyama d4c2073cda Fix test
Previous test passed even without 1c321022d0.
2024-08-27 20:09:40 +09:00
Rui Ueyama fe7237e454 Merge pull request #1335 from ywgrit/main 2024-08-27 17:04:46 +09:00
Xin Wang 4da257e6fb LoongArch: change test of R_LARCH_CALL36 with bigger offset 2024-08-27 15:14:56 +08:00
Rui Ueyama ed617fef05 Merge pull request #1170 from mominul/patch-1
Update README.md
2024-08-27 13:19:16 +09:00
Xin Wang 1c321022d0 LoongArch: fix error offset for relaxation of R_LARCH_CALL36 2024-08-26 16:37:09 +08:00
Rui Ueyama ed4cae9354 Add a test 2024-08-24 14:33:13 +09:00
Rui Ueyama 2d390565c1 Fix the section-shrinking relaxation 2024-08-24 13:53:02 +09:00
Rui Ueyama 5e72d88940 Update a document 2024-08-24 13:21:38 +09:00
Rui Ueyama 213f2ecec5 Refactor 2024-08-23 13:45:02 +09:00
Rui Ueyama 23b3778383 Refactor 2024-08-22 18:25:28 +09:00
Rui Ueyama b9776af9b2 Refactor 2024-08-22 17:34:42 +09:00
Rui Ueyama 25794c6003 Refactor 2024-08-22 11:09:55 +09:00
Rui Ueyama 7fa561699e Refactor 2024-08-21 13:57:26 +09:00
Rui Ueyama 127ba25dab [LoongArch] Fix TLSDESC relaxation
R_LARCH_TLS_DESC_PC_HI20 is a GOT-loading relocation, so we need to
compute the distance to the symbol's GOT entry instead of to the
symbol's address.
2024-08-21 13:25:59 +09:00
Rui Ueyama 6a1742ea90 Refacotr 2024-08-21 12:53:16 +09:00
Rui Ueyama e013d27b54 Add a test 2024-08-21 12:40:56 +09:00
Rui Ueyama 37e175b4a4 Merge pull request #1333 from ywgrit/main
LoongArch: apply relaxation to tls desc
2024-08-21 12:12:15 +09:00
Xin Wang 982b901077 LoongArch: apply relaxation to tls desc 2024-08-21 09:44:43 +08:00
Rui Ueyama f0797bb8f2 Improve CI 2024-08-18 14:22:57 +09:00
Rui Ueyama 18f8c425dc Do not use precompiled headers
`-fpch-preprocess` doesn't seem to improve build speed that much,
so don't use it.
2024-08-18 12:56:38 +09:00
Rui Ueyama 194239f8c6 Enable some tests 2024-08-18 12:39:29 +09:00
Rui Ueyama 401e019b05 Add a test
We also don't want to skip empty string because if a user specifies
an empty string, it is their intention whatever that intention is.
2024-08-18 11:38:47 +09:00
cristian64 0c74e8274e Fix infinite loop in split_by_comma_or_colon(). (#1332)
* Fix infinite loop in `split_by_comma_or_colon()`.

The mere presence of a `,` or a `:` in the input string would make the
function enter an infinite loop, effectively freezing the program until
all available memory was exhausted. The string position in the loop was
not incremented past the `,` or the `;` character that had just been
read.

The function is currently used only in the `--exclude-libs` option. The
freeze is reproducible with:

```bash
$ mold --exclude-libs foo,bar
```

The longer the string after the first `,` or `:` is, the faster memory
exhaustion is reached.

* Skip empty parts in `split_by_comma_or_colon()`.
2024-08-18 11:34:12 +09:00
Rui Ueyama cf5d054eb4 Fix CI 2024-08-17 16:17:31 +09:00
Rui Ueyama d9bf693fb8 [LoongArch] Do not emit local symbols by default 2024-08-17 15:09:08 +09:00
Rui Ueyama cf38f81c39 Refactor 2024-08-17 15:09:08 +09:00
Rui Ueyama 2eeb7b411e Simplify 2024-08-17 06:38:31 +09:00
Rui Ueyama ecc7ce022b Skip ELF symbols whose section numbers are the reserved range
Fixes https://github.com/rui314/mold/issues/1331
2024-08-16 13:26:12 +09:00
Rui Ueyama a6b182b065 Refactor 2024-08-16 13:26:12 +09:00
Rui Ueyama e30f01fe93 Refactor 2024-08-16 12:48:16 +09:00
Rui Ueyama ac84d2cdbb Refactor 2024-08-16 11:18:12 +09:00
Rui Ueyama 742ea87d41 Merge .ARM.exidx entries
This change should make our output for ARM32 a little bit smaller.
2024-08-15 17:15:45 +09:00
Rui Ueyama fa546965ec Refactor 2024-08-15 14:11:55 +09:00
Rui Ueyama f68657d201 Refactor 2024-08-15 13:34:41 +09:00
Rui Ueyama 23bc92c66f Emit PT_GNU_PROPERTY program header if .note.gnu.property is present
It looks like Linux kernel reads this program header to enable BTI on ARM64.
2024-08-15 12:57:45 +09:00
Rui Ueyama c2956484ca Refactor 2024-08-15 12:26:29 +09:00
Rui Ueyama 3f593c62ae Remove code for macOS 2024-08-14 21:29:08 +09:00
Rui Ueyama 5b8594979b Reorganize directory structure 2024-08-14 20:52:09 +09:00
Rui Ueyama 7571305cd1 Fix CI 2024-08-14 20:06:28 +09:00
Rui Ueyama 0631f9750e Fix a Docker image build failure
Fixes https://github.com/rui314/mold/issues/1330
2024-08-14 19:10:40 +09:00
Rui Ueyama b51d6ab5cb Avoid LD_PRELOAD 2024-08-14 19:10:40 +09:00
Rui Ueyama 93c4f35615 Fix minor issues 2024-08-14 17:38:53 +09:00
Rui Ueyama 510b2d68c0 Reorganize directory structure 2024-08-14 16:23:25 +09:00
Rui Ueyama d6d8d2e494 [LoongArch] Fix symbol value computation for the extreme code model
The LoongArch extreme code model uses the following instructions to
materialize a 64-bit value in a regiter.

  pcalau12i $t1, %pc_hi20(g2)
  addi.d    $t0, $zero, %pc_lo12(g2)
  lu32i.d   $t0, %pc64_lo20(g2)
  lu52i.d   $t0, $t0, %pc64_hi12(g2)

The previous relocation formula expected that `page(pc)` would be
consistent for all the instructions, but this assumption is incorrect
if the instruction sequence crosses a 4 KiB boundary.

Now, the LoongArch psABI requires that the machine instructions must be
consecutive, and the relocations for lu32.d and lu52i.d uses
`page(pc - 8)` and `page(pc - 16)` instead of `page(pc)`.

This psABI change gave me an impression that the LoongArch's extreme code
model was poorly designed and inadequately tested. If these instructions
must be consecutive, only a single relocation referring to the beginning
of the instructions would suffice, which relocates all the following four
instructions at once.

Maybe we do not need to support the relocations for the extreme code model
because the code model was buggy at the spec level, which suggests that no
one is using them. But I'm not sure if it is safe to remove them, so let's
just follow the psABI change.
2024-08-14 13:52:12 +09:00
Rui Ueyama 9e0c1c81dd Refactor 2024-08-13 20:52:38 +09:00
Rui Ueyama ac5031198c Simplify 2024-08-13 20:18:50 +09:00
Rui Ueyama 11d5426781 Update mold.1 (automated commit) 2024-08-13 09:22:48 +00:00
Rui Ueyama afc52eea70 Accept --build-id=fast for the sake of compatibility with lld
Fixes https://github.com/rui314/mold/issues/1328
2024-08-13 18:20:56 +09:00
Rui Ueyama 6248d964fb Refactor 2024-08-13 18:15:20 +09:00
Rui Ueyama addb5f1675 [SPARC] Relax TLS GD/LD relocations 2024-08-13 17:18:01 +09:00
Rui Ueyama ed22d76ef0 Refactor 2024-08-13 14:46:53 +09:00
Rui Ueyama a73e9b2dac Update comments 2024-08-13 11:27:45 +09:00
Rui Ueyama 5dfa1cf07c Update comments 2024-08-10 09:51:13 +09:00
Rui Ueyama 4164285a13 Fix CI 2024-08-09 22:10:53 +09:00
Rui Ueyama 1f53bd5bfa Attempt to fix CI 2024-08-09 18:22:07 +09:00
Rui Ueyama a32d294df8 Update comments 2024-08-09 18:02:14 +09:00
Rui Ueyama dbaa6d7341 [LoongArch] Support TLSDESC 2024-08-09 17:49:12 +09:00
Rui Ueyama 23a9e7261d Refactor 2024-08-09 15:04:04 +09:00
Rui Ueyama 5a9919b433 Fix a crash bug
Fixes https://github.com/rui314/mold/issues/1326
2024-08-09 12:38:56 +09:00
Rui Ueyama 1d24b70996 Use crc32() instead of crc32_z()
Old zlib does not support crc32_z. The difference between the two is
that crc32() takes an input size as an unsigned int while crc32_z()
takes as size_t. For our use case, size is guranteed to be short, so
crc32() is safe to use.
2024-08-08 20:06:19 +09:00
Rui Ueyama f18d1c1ffc Attempt to fix build failure on 32 bit Alpine distros
Fixes https://github.com/rui314/mold/issues/1323
2024-08-08 10:09:41 +09:00
Rui Ueyama 1ab637cea4 Bump mold version to 2.33.0 2024-08-07 12:32:13 +09:00
Rui Ueyama 573b8a45df Update mold.1 (automated commit) 2024-08-06 05:26:45 +00:00
Rui Ueyama 0bf00a4247 Remove --encoded-package-metadata from docs
It's not clear whether or not the option would be added to the GNU linker,
so I"ll make this a hidden option for now.

https://github.com/rui314/mold/pull/1308
2024-08-06 14:25:45 +09:00
Rui Ueyama 43d9536a05 Fix an assertion failure
https://github.com/rui314/mold/issues/573
2024-08-05 20:06:45 +09:00
Rui Ueyama 893ec1b362 Format 2024-08-05 15:08:57 +09:00
Rui Ueyama 1aa966c2ad Refactor 2024-08-05 13:26:03 +09:00
ywgrit 121f917884 LoongArch: support relaxation of pcalau12i/ld.d to pcalau12i/addi.d or pcaddi (#1322)
[LoongArch] support relaxation of pcalau12i/ld.d to pcalau12i/addi.d
2024-08-05 12:26:03 +09:00
Rui Ueyama 17a9481522 Merge .sdata, .srodata and .sbss
RISC-V uses these sections for small data/rodata/bss.
2024-08-03 14:58:27 +09:00
Rui Ueyama 06e8bf8933 Update mold.1 (automated commit) 2024-08-03 03:15:37 +00:00
Rui Ueyama f7af61f80c Update man page 2024-08-03 12:14:59 +09:00
Rui Ueyama 25b34bb472 Update mold.1 (automated commit) 2024-08-02 11:02:16 +00:00
Rui Ueyama 22f85c1b6f Rename --remove-landing-pad -z rewrite-endbr 2024-08-02 19:18:03 +09:00
Rui Ueyama dd8d9714cb dSupport --dynamic-list-data
Fixes https://github.com/rui314/mold/issues/1320
2024-08-01 18:51:34 +09:00
Rui Ueyama 982f07168d Refactor 2024-08-01 11:51:28 +09:00
Rui Ueyama 218d89b005 Rename a test 2024-07-31 21:00:12 +09:00
Rui Ueyama bada67bb39 Handle R_LARCH_CALL36 as a function-calling relocation 2024-07-31 20:58:45 +09:00
Rui Ueyama db03248e72 Fix comments 2024-07-31 20:05:52 +09:00
Rui Ueyama 47c092aac0 Remove range extension support from LoongArch
It looks like emitting PCADDU18I + JIRL instead of just BL for function
calls is a way to go, so we don't want to bother to maintain range
extension thunks for the psABI.

Since no other linkers support range extension thunks for LoongArch other
than us, removing the support should be OK.
2024-07-31 19:23:03 +09:00
Rui Ueyama 4e6b4c43cd Refactor 2024-07-31 19:09:56 +09:00
Rui Ueyama 2c6f379d69 Support R_LARCH_CALL36 2024-07-31 17:51:34 +09:00
Rui Ueyama bf79ae1190 Refactor 2024-07-31 14:55:05 +09:00
Rui Ueyama 96fda95624 Add a test for LoongArch relaxation 2024-07-31 11:48:04 +09:00
Rui Ueyama b71bfccb46 Improve pcalau12i+addi.d relaxation
This should unbreak our buildbots.
2024-07-31 11:36:32 +09:00
Rui Ueyama a4dc015c0d Update comments 2024-07-31 10:39:22 +09:00
ywgrit 74b359fe62 [LoongArch] Support relax of pcalau12i/addi.d->pcaddi (#1318)
Relax pcalau12i/addi.d to pcaddi
2024-07-31 10:39:06 +09:00
Rui Ueyama 197a74ded6 Add comments 2024-07-29 16:03:35 +09:00
Rui Ueyama 9aa4fcc6cd Show skipped tests
I don't know why, but the previous regular expression didn't match with
a line consisting just with "skipped".
2024-07-29 13:41:34 +09:00
Rui Ueyama 36915416dd Run a binary under Intel SDE CPU emulator with CET enabled 2024-07-29 13:16:12 +09:00
Rui Ueyama 411dcfd0b1 Fix comments 2024-07-29 13:13:54 +09:00
Rui Ueyama 35564d5f2c Update mold.1 (automated commit) 2024-07-29 02:00:32 +00:00
Rui Ueyama bba20b3fe7 Fix typos 2024-07-29 10:59:12 +09:00
Rui Ueyama 55e516da6f Update mold.1 (automated commit) 2024-07-29 01:55:03 +00:00
Rui Ueyama 2c3dcc4370 Document --remove-landing-pads 2024-07-29 10:52:31 +09:00
Rui Ueyama 1fb6dd3b46 Rename -z rewrite-endbr --remove-landing-pads
This is because we want to support it for non-x86-64 targets.
Using the x86 instruction name as a parameter name is not desirable.
2024-07-29 10:52:21 +09:00
Rui Ueyama ed7eec5969 Fix -z rewrite-endbr
Previously, functions that are referred to only by section symbols were
not considered address-taken, and we always rewrote endbr64s with NOPs for
such functions. That caused a runtime fault when such function was called
indirectly.

Now, endbr64s for such symbols are retained.

I tried to build clang-19 as a release build with and without `-z
rewrite-endbr` and counted the number of endbr64 instructions in each
binary. Here is the result:

  Before: 110,615
  After:   91,799

So the feature reduces the number of gadgets by 17%. Both binaries worked
fine with Intel SDE CPU emulator with `-cet 1 -cet_raise 1`, so I think
it's finally working as expected.

I also tried to build mold itself with and without the feature. Here is
the result:

  Before: 27,430
  After:  17,725

This is a 35% reduction. I confirmed that mold built with `-z rewrite-endbr`
can self-host under Intel SDE.

The rewrite_endbr pass is extremely fast. It took only 7 milliseconds
for a ~210 MiB clang-19 binary on my Threadripper 7990X machine. We
may want to consider enabling it by default at some point.
2024-07-29 09:06:26 +09:00
Rui Ueyama 3f7236d268 Refactor 2024-07-28 19:42:05 +09:00
Rui Ueyama 766ce80297 Update comments 2024-07-28 17:01:13 +09:00
Rui Ueyama 61af6f3c59 Report an error for an enormously large r_addend 2024-07-27 16:14:20 +09:00
Rui Ueyama 464f7655d6 Use break instead of nop as a filler 2024-07-27 14:50:38 +09:00
Rui Ueyama d1674d86ac Add comments 2024-07-27 14:50:38 +09:00
Rui Ueyama 36e5b4b949 Relax R_LARCH_TLS_LE_{HI20_R,ADD_R,LO12_R} 2024-07-27 11:03:45 +09:00
Rui Ueyama 98a7cff0c5 Handle R_LARCH_RELAX 2024-07-27 11:03:45 +09:00
Rui Ueyama 34ed39c873 Refactor 2024-07-27 11:03:43 +09:00
Rui Ueyama 2bbba15068 Create canonical PLTs for PIEs
We cannot guarantee pointer equality for some corner cases without them.
2024-07-24 10:56:39 +09:00
Rui Ueyama a478db77c2 Do not obtain an unnecessary lock 2024-07-22 12:05:19 +09:00
Rui Ueyama d829e425df Add a missing #include
https://github.com/microsoft/mimalloc/pull/917
2024-07-22 11:06:37 +09:00
Rui Ueyama f5b2b82295 Skip a test if LD_PRELOAD doesn't work
May be related to the following issues:

  https://github.com/rui314/mold/issues/1305
  https://github.com/rui314/mold/issues/1309
2024-07-22 10:39:32 +09:00
Rui Ueyama e3876ddbd4 Refactor 2024-07-22 08:47:25 +09:00
Rui Ueyama b8556e37bb Fix CI 2024-07-21 20:18:31 +09:00
Rui Ueyama bbfaa0bb30 Fix CI 2024-07-21 19:47:12 +09:00
Rui Ueyama 5606087776 Support -z x86-64-v[234] 2024-07-21 18:51:28 +09:00
Rui Ueyama b919548b27 Refactor 2024-07-21 16:42:39 +09:00
Rui Ueyama ed7414e1c7 Update mold.1 (automated commit) 2024-07-21 06:16:56 +00:00
Rui Ueyama fa1237fc08 Document --no-allow-shlib-undefined 2024-07-21 15:14:48 +09:00
Rui Ueyama 1dc99aaade Fix CI 2024-07-21 13:26:58 +09:00
Rui Ueyama 3001f02b55 Support --no-allow-shlib-undefined
Fixes https://github.com/rui314/mold/issues/1313
2024-07-21 13:06:57 +09:00
Rui Ueyama 39442dbdf0 Refactor 2024-07-20 16:05:37 +09:00
Rui Ueyama 6c4ca4e40c Attempt to fix CI 2024-07-20 15:06:15 +09:00
Rui Ueyama bdca85e15a Make Integer's constructor constexpr 2024-07-20 14:54:58 +09:00
Rui Ueyama 1ccc77ac7f Refactor 2024-07-20 13:38:03 +09:00
Rui Ueyama b99ed6e7ae Refactor 2024-07-20 13:10:33 +09:00
Rui Ueyama 6ee6410948 Build and test on more Linux distros 2024-07-19 18:03:57 +09:00
Rui Ueyama d187dac602 Install binary packages in Gentoo 2024-07-19 18:03:57 +09:00
Rui Ueyama 2b4c365161 Run tests on Alpine Linux with musl libc 2024-07-19 15:46:15 +09:00
Rui Ueyama 70ea7ade7f Do not use the extended regexp
It looks like Busybox's grep doesn't support regexp backreferences.

Fixes https://github.com/rui314/mold/issues/1309
2024-07-19 15:23:52 +09:00
Rui Ueyama 56a94d2693 Fix a test on musl
Fixes https://github.com/rui314/mold/issues/1309
2024-07-19 14:54:33 +09:00
Rui Ueyama 14a4b05950 Always create a .eh_frame section even if it's empty
glibc's `__register_frame_info_bases` expect that it can read a terminator
if a .eh_frame is empty. Removing the section completely caused a crash bug
on Alpine Linux with musl as libc.

Specifically, mold-generated binaries crash if all the following
condition were met:

 1. musl is used as a libc
 2. .eh_frame is empty
 3. libgcc_s.so is linked

Prior to 06b592683c, condition (3) was not
usually satisfied, so we didn't notice this issue. Now, the shared library
is linked against almost all output files because the file is referenced by
`__deregister_frame_info`.

https://github.com/bminor/glibc/blob/910aae6e5a2196938fc30fa54dd1e96f16774ce7/sysdeps/generic/unwind-dw2-fde.c#L105-L107

Fixes https://github.com/rui314/mold/issues/1309
2024-07-19 14:51:00 +09:00
Rui Ueyama 14d525c1a1 Refactor 2024-07-18 21:04:55 +09:00
Rui Ueyama 5b7ae06bde Update mold.1 (automated commit) 2024-07-18 06:53:23 +00:00
Rui Ueyama e67f5e62ae Fix update-manpage workflow 2024-07-18 15:51:56 +09:00
Rui Ueyama bae9598190 Refactor 2024-07-18 13:54:16 +09:00
Rui Ueyama 9acda6f939 Make update-manpage workflow runnable by hand 2024-07-18 13:06:08 +09:00
Rui Ueyama 7b52f34c38 Make test scripts more verbose 2024-07-18 12:57:32 +09:00
Rui Ueyama 9874a9daa7 Document --encoded-package-metadata
https://github.com/rui314/mold/pull/1308
2024-07-18 11:57:48 +09:00
Rui Ueyama aecf39d459 Do not crash when an empty argument is given 2024-07-18 11:57:48 +09:00
Rui Ueyama 9eacc638f3 Improve a test 2024-07-18 11:57:48 +09:00
Rui Ueyama 0c34ec1f50 Refactor 2024-07-18 09:39:01 +09:00
Rui Ueyama a778f9c5f4 Merge pull request #1308 from bdrung/encoded-package-metadata
Add --encoded-package-metadata
2024-07-17 22:23:44 +09:00
Rui Ueyama d7b079c4fb Refactor 2024-07-17 21:26:36 +09:00
Benjamin Drung cfe60991a5 Add --encoded-package-metadata
Specifying the compiler flag `-Wl,--package-metadata=<JSON>` might not
work, because the shells might eat the quotation marks and the compiler
splits the JSON at the commas.

Ubuntu tried to using a specs file to set `--package-metadata` but that
turned out to be too fragile. autopkgtests might use the compiler flags
but the needed environment variables are not set in the test
environment. Debugging a crash of an application build with the -spec
parameter lacks the environment variables. People like to iteratively
continue building the software in the build directory while hacking on
the package and then have no environment variable set.

So introduce a `--encoded-package-metadata` linker flag that takes a
percent-encoded JSON. Percent-encoding is used because it is a
standard and simple to implement.

Bug-Ubutru: https://bugs.launchpad.net/bugs/2071468
Signed-off-by: Benjamin Drung <[email protected]>
2024-07-17 14:14:48 +02:00
Rui Ueyama 2acf634340 Fix typo 2024-07-17 18:50:48 +09:00
Rui Ueyama 09f1008186 Handle R_LARCH_TLS_LE_HI20_R and R_LARCH_TLS_LE_LO12_R 2024-07-17 18:36:12 +09:00
Rui Ueyama baf9ae9038 Fix a test on Debian
If the default linker doesn't complain, just skip the test.

Fixes https://github.com/rui314/mold/issues/1301
2024-07-16 12:00:08 +09:00
Rui Ueyama cdcb428f26 Remove dead code 2024-07-15 13:41:57 +09:00
Rui Ueyama 36bc198b9a Refactor 2024-07-15 13:36:07 +09:00
Rui Ueyama 428a9c4228 Skip a test on m68k
https://github.com/rui314/mold/pull/1304
2024-07-15 11:19:06 +09:00
Rui Ueyama 666b18b851 Use __attribute__((aligned)) instead of a C macro 2024-07-15 11:06:11 +09:00
Rui Ueyama f180ac21c8 Fix build on non-m68k hosts
https://github.com/rui314/mold/pull/1304
2024-07-14 21:49:58 +09:00
Rui Ueyama 0b1be96a13 Merge pull request #1304 from chewi/m68k-alignment
Align Chuck, InputSection, and SectionFragment to at least 4 bytes
2024-07-14 21:44:35 +09:00
James Le Cuirot 1f444a21d2 Align Chuck, InputSection, and SectionFragment to at least 4 bytes
There are already static_asserts to check this, so it is almost always
true anyway, but not on m68k, which aligns to 2 bytes by default.
2024-07-14 10:22:04 +01:00
Rui Ueyama 4eeebf0221 Define Script class and move linker script-related code there 2024-07-14 16:41:03 +09:00
Rui Ueyama b2a1e4b83e Move reader-related attributes to ReaderContext 2024-07-14 11:10:59 +09:00
Rui Ueyama 08938402c9 Rename a variable 2024-07-13 18:19:11 +09:00
Rui Ueyama 4c569c8c8c Accept --library as a synonym for -l
No one has complained about it so far, but according to the GNU ld manual,
`--library` is a synonym for `-l`.
2024-07-13 18:18:35 +09:00
Rui Ueyama 03a2105d39 Change how we resolve relocations referring to mergeable sections
Previously, we find a string in a mergeable string section by offset
after adding a relocation addend to a symbol value. Now, we find a
string just by a symbol value and then add a relocation addend later.

Assume a mergeable section containing two strings as an example.

  foo\0bar\0
  ^    ^
  .L1  .L2

Previously, a relocation referring to .L2 - 1 referred to the end of
.L1. Now, it refers to one byte before .L2.

Section symbol is an exception to this rule, because it seems it's
what the compiler is assuming. The rules seem arbitrary, but it looks
like we need them for the compatibility with existing object files.

Fixes https://github.com/rui314/mold/issues/1300
2024-07-13 17:25:44 +09:00
Rui Ueyama d2870ac574 Upgrade bundled mimalloc to 2.1.7 2024-07-11 15:38:18 +09:00
Rui Ueyama 8497b7e1dc Upgrade OneTBB in ./third-party to version 2021.13.0 2024-07-11 15:38:18 +09:00
Rui Ueyama 0e2b6b010d Update mold.1 (automated commit) 2024-07-11 04:39:27 +00:00
Rui Ueyama 84263109b1 Revert "Allow numbers other than 1 for MOLD_JOBS"
This reverts commit 863893057e because
it's unstable.
2024-07-11 13:36:55 +09:00
Rui Ueyama 162bcca5df Update mold.1 (automated commit) 2024-07-11 02:10:41 +00:00
Rui Ueyama 2fb3c9da57 Update man page 2024-07-11 11:09:03 +09:00
Rui Ueyama 83ee4962b1 Fix test flakiness for SH4 2024-07-11 09:58:16 +09:00
Rui Ueyama e03f74a357 Fix CI
Fixes https://github.com/rui314/mold/issues/1299
2024-07-10 15:52:46 +09:00
Rui Ueyama 8c1b25b3aa Obtain a file lock with flock() while creating a separate debug file 2024-07-09 17:49:39 +09:00
Rui Ueyama 315dfd3244 Fix CI 2024-07-09 15:25:53 +09:00
Rui Ueyama 02ca830f42 Refactor 2024-07-09 15:07:41 +09:00
Rui Ueyama 18da5b654e Add --no-detach to write to a separate debug file in the foreground
--detach is the default.
2024-07-09 12:06:41 +09:00
Rui Ueyama 97a1e218c5 Simplify crc32_solve()
The code was originally written by Pete Cawley
https://gist.github.com/corsix/bdfc8f2f1dc0f28de39f74de9bf4f060
2024-07-09 10:15:26 +09:00
Rui Ueyama f9e4cb1a7f Add a missing #include 2024-07-08 10:43:40 +09:00
Rui Ueyama 60760a892a Attempt to fix CI 2024-07-08 09:59:07 +09:00
Rui Ueyama 596ffa959a Add --separate-debug-info
This option is to separate debug info to a different file. The debug
info file's filename is stored to the main output file's .gnu_debuglink
section. gdb can read the section contents and followg the link to
find debug info in another file.

Fixes https://github.com/rui314/mold/issues/1294
2024-07-08 09:28:32 +09:00
Rui Ueyama cd3b817f13 Make notify_parent idempotent 2024-07-07 22:49:35 +09:00
Rui Ueyama 19de40fed4 Refactor 2024-07-07 22:45:17 +09:00
Rui Ueyama e78e12b15b Refactor 2024-07-07 22:24:42 +09:00
Rui Ueyama 5b4377842b Fix CI 2024-07-07 21:50:53 +09:00
Rui Ueyama 16ee9f48a4 Fix CI 2024-07-07 17:58:21 +09:00
Rui Ueyama 8e3679e4e5 Refactor 2024-07-06 13:11:09 +09:00
Rui Ueyama 7089304046 Simplify 2024-07-05 19:47:33 +09:00
Rui Ueyama 5ab3708e9f Merge pull request #1298 from yujincheng08/patch-1
Fix cross-compile lto on MacOS
2024-07-05 19:45:10 +09:00
Rui Ueyama aa939d7a6a Temporarily disable ASAN tests
It looks like starting with Clang 18.1.3, ASAN began reporting an
error on OneTBB.
2024-07-05 18:27:00 +09:00
Rui Ueyama a63bbcd7b1 Attempt to fix CI 2024-07-05 17:58:13 +09:00
Rui Ueyama 339f9c485a Attempt to fix CI 2024-07-05 17:42:32 +09:00
LoveSy 4905f6d3f2 Use macos-12 because macos-11 has been removed
https://github.blog/changelog/2024-05-20-actions-upcoming-changes-to-github-hosted-macos-runners/#macos-11-deprecation-and-removal
2024-07-05 15:32:37 +08:00
Rui Ueyama cb8cc1c8f0 Refactor 2024-07-05 16:00:09 +09:00
Rui Ueyama c6b54532e9 Refactor 2024-07-04 16:17:25 +09:00
Rui Ueyama 3936134823 Refactor 2024-07-04 15:08:37 +09:00
Rui Ueyama 55ca05bab6 Refactor 2024-07-04 13:59:29 +09:00
LoveSy 7305a5c5dd Fix cross-compile lto on MacOS 2024-07-03 21:55:22 +08:00
Rui Ueyama b145377c39 Merge pull request #1296 from marxin/improve-gdb-index-test
Improve --gdb-index test by using readelf --debug=gdb_index
2024-07-01 17:15:51 +09:00
Martin Liska 08f2a74be7 Improve --gdb-index test by using readelf --debug=gdb_index 2024-06-30 16:07:47 +02:00
Rui Ueyama 82fb10fe26 Fix a test 2024-06-28 15:57:10 +09:00
Rui Ueyama 34f4d654d3 Add an entry for AlmaLinnux 2024-06-28 15:54:39 +09:00
Rui Ueyama 091395df33 Link with -ldl for dlopen()
Fixes https://github.com/rui314/mold/issues/1293
2024-06-28 11:00:37 +09:00
Rui Ueyama a38be94064 Simplify 2024-06-27 19:51:06 +09:00
Rui Ueyama cf1c02d097 Bump mold version to 2.32.1 2024-06-27 14:04:26 +09:00
Rui Ueyama 3dbe2ac704 Do not write relocation addends for RISC-V
Probably due to a bug in glibc, static PIE binaries crash on startup
in some environment.

Fixes https://github.com/rui314/mold/issues/1291
2024-06-27 13:09:58 +09:00
Rui Ueyama 35d67a3fab Add a missing #include for getenv() 2024-06-26 15:10:54 +09:00
Rui Ueyama f60fada438 Refactor 2024-06-24 16:09:35 +09:00
Rui Ueyama c419dcde1b Merge pull request #1289 from yujincheng08/main
ci: add msys2
2024-06-24 09:49:34 +09:00
LoveSy ebfe4f13c6 fix mingw compiles 2024-06-23 15:40:28 +00:00
LoveSy 8f9b59cbe6 ci: add msys2 2024-06-23 23:21:50 +08:00
Rui Ueyama 63f26708fd Unbreak daily buildbot 2024-06-23 12:38:59 +09:00
Rui Ueyama 07b09a66d3 Attempt to fix CI 2024-06-22 13:51:30 +09:00
Rui Ueyama 4cc61361f3 Refactor 2024-06-22 13:08:43 +09:00
Rui Ueyama 5535e7a6b7 Style change 2024-06-22 10:56:36 +09:00
Rui Ueyama 06b592683c Make weak undefined symbols to mark shared libraries as "needed"
It is not clearly defined when undefined weak symbols are resolved.
It looks like there are two possible approaches:

 1. Promote all weak undefined symbols to dynamic ones so that they'll
    have another chance to be resolved at load-time, or

 2. Promote weak undefined symbols to dynamic ones only when there are
    definitions in other DSOs at link-time.

(1) provides the maximum flexibility. For example, consider a main program
that has a weak undefined symbol `foo` and there's no DSO that defines it
at link-time. In (1), `foo` gets promoted to a dynamic symbol, so that one
of its depending DSO is upgraded to define `foo`, the main executable's
`foo` is resolved to that symbol at load-time. On the other hand, in (2),
`foo` would have already been converted to an absolute symbol at address
zero at link-time, so you need to rebuild the main executable to use the
new definition of `foo` in the shared library.

However, (1) is not compatible with copy relocations. This is because we
need to know the size of the symbol when creating a copy relocation, but
that information is not available unless we have a definition. It's also
not compatible with canonical PLTs because canonical PLTs have non-zero
addresses and therefore weak undefined symbols would always be resolved to
non-zero addresses.

As a workaround, GNU ld promotes weak undefs to dynamic symbols only when
they don't need copy relocations or canonical PLTs. In other words, weak
undef's behavior is different between -fPIC and -fno-PIC. In the former
case, they become dynamic symbols, and vice versa.

I don't think that workaround is a good one. So, mold took the second
approach.

There is, however, another thing to consider. What if we can find a
defined symbol in a DSO that is specified as `-as-needed`? Previously,
mold did not mark the library as "needed" and converted the weak undef
into an absolute symbol.

However, libstdc++ assumes that if weak undef symbol
`__pthread_key_create` is not resolved, it assumes that multi-threading is
not used in the executable, which resulted in a mis-detection with mold.

Therefore, this patch changes the mold's behavior so that it makes weak
undefs to keep DSOs "needed".

Fixes https://github.com/rui314/mold/issues/1286
2024-06-22 10:46:17 +09:00
Rui Ueyama 0b5e5dcbb5 Fix -Wunused-result 2024-06-22 10:46:17 +09:00
Rui Ueyama b1695c6018 Merge pull request #1287 from yujincheng08/lto
Support lto on mingw
2024-06-22 07:54:25 +09:00
LoveSy 48ee8e8257 Handle stackoverflow 2024-06-21 17:42:33 +08:00
LoveSy 50bf031d49 Support lto on mingw 2024-06-21 17:42:27 +08:00
Rui Ueyama bf05198d53 Merge pull request #1285 from yujincheng08/patch-2 2024-06-20 11:17:47 +09:00
Rui Ueyama f9a37e9dd4 Remove dead code
get_self_path() is used by `mold -run`, which is not available on
macOS or Windows.
2024-06-20 09:31:44 +09:00
LoveSy cf9e59954b Use __has_include instead 2024-06-19 23:50:40 +08:00
LoveSy 7cce297318 Only define STDERR_FILENO when not defined 2024-06-19 23:48:14 +08:00
Rui Ueyama 1f94716fce Attempt to fix CI 2024-06-18 22:40:52 +09:00
Rui Ueyama 863893057e Allow numbers other than 1 for MOLD_JOBS 2024-06-18 18:28:28 +09:00
Rui Ueyama b83b1950c0 Merge pull request #1281 from ziyao233/outgoing/fix-static-flag 2024-06-17 08:10:20 +09:00
Yao Zi d2774b0da9 Use $CXX to detect cxxflags
Use $CXX instead of $CC to detect whether C++ programs could be built
statically. Add function test_cxxflags for convenience.

Fix test error in an environment where C++ programs cannot be built
statically, with error like:

  mold: fatal: library not found: c++

Signed-off-by: Yao Zi <[email protected]>
2024-06-16 20:16:34 +00:00
PerseoGI cd7287f0de Update README adding Conan usage instructions 2024-06-13 11:26:14 +02:00
Rui Ueyama cff858bad5 Attempt to fix CI 2024-06-13 16:47:50 +09:00
Rui Ueyama 7669a3ce67 Attempt to fix CI 2024-06-13 16:11:48 +09:00
Rui Ueyama 3ad387f20c Refactor 2024-06-13 13:33:45 +09:00
Rui Ueyama af6d356468 Attempt to fix CI 2024-06-11 14:49:53 +09:00
Rui Ueyama 768f1dffdd Attempt to fix CI 2024-06-11 14:43:54 +09:00
Rui Ueyama 2a25c81834 Refactor 2024-06-11 13:37:21 +09:00
Rui Ueyama 24b8941f60 Refactor 2024-06-10 20:36:40 +09:00
LoveSy 841a186346 Fix corrupted .riscv.attributes ISA string (#1276)
Fix corrupted .riscv.attributes ISA string
extension name can contain digits
2024-06-10 19:56:40 +09:00
Rui Ueyama ec04bfda9d Bump mold version to 2.32.0 2024-06-09 12:44:21 +09:00
Rui Ueyama 8cd85aaa29 Make --undefined=ignore-in-object-files to override -z defs
The interaction between these flags are unnecessarily complicated,
but it looks like `--undefined=ignore-in-object-files` needs to
share the same internal flag as `-z defs` so that they override
each other.

Fixes https://github.com/rui314/mold/issues/1270
2024-05-31 22:00:03 +09:00
Rui Ueyama 01bc0b522d Update mold.1 (automated commit) 2024-05-31 04:53:42 +00:00
Rui Ueyama 48e777ad7b Update man page 2024-05-31 13:51:59 +09:00
Rui Ueyama 0955c30852 Update mold.1 (automated commit) 2024-05-30 09:51:42 +00:00
Rui Ueyama 02ec7156c1 Update man page 2024-05-30 18:50:35 +09:00
Rui Ueyama c7c8583946 Allow larger range extension thunks
Fixes https://github.com/rui314/mold/issues/1224
2024-05-27 15:43:43 +09:00
Rui Ueyama 08b0a1629d Do not emit incorrect relocations for mergeable sections with --relocatable
Fixes https://github.com/rui314/mold/issues/1265
2024-05-24 13:01:10 +09:00
Rui Ueyama 02b439af48 Upgrade bundled mimalloc to 2.1.6
Fixes https://github.com/rui314/mold/issues/1257
2024-05-21 11:55:39 +09:00
Rui Ueyama a8cd2e84e0 Support R_PPC64_TPREL16_LO_DS
Fixes https://github.com/rui314/mold/issues/1262
2024-05-21 11:22:51 +09:00
Rui Ueyama a25226eb13 Replace our SipHash implementation with https://github.com/rui314/siphash 2024-05-18 12:15:26 +09:00
Rui Ueyama 91f9c4d167 Avoid unaligned memory access 2024-05-17 15:39:19 +09:00
Rui Ueyama 416b0d336f Optimize
Teaching the compiler that `buflen` cannot be negative and also doesn't
take a large value allows it to optimize code a little bit more than
it currently does.
2024-05-16 11:52:58 +09:00
Rui Ueyama ca4738d5e2 Use Siphash-1-3 instead of SipHash-2-4
I couldn't find any report that the reduced round version is broken
despite being used at a lot of places (e.g. Rust's hash table uses
SipHash-1-3). So I assume it's secure.
2024-05-16 11:44:25 +09:00
Rui Ueyama 4281f45f06 Do not record response files as dependency files
Fixes https://github.com/rui314/mold/issues/1258
2024-05-16 10:31:21 +09:00
Rui Ueyama 08be7ff2d2 Use SipHash-2-4 instead of SipHash-1-3
Because I wasn't not sure SipHash-1-3 is considered as a secure MAC.
2024-05-12 09:11:32 +09:00
Rui Ueyama 47030cf6ca Fix thread-unsafe code
BitVector is not therad-safe if two threads write to the same byte
at the same time.
2024-05-11 19:56:07 +09:00
Rui Ueyama a5e5ad75fe Refactor 2024-05-11 19:56:07 +09:00
Rui Ueyama 0ce1c78012 Refactor 2024-05-11 16:54:54 +09:00
Rui Ueyama a250aea2f9 Refactor 2024-05-11 16:38:13 +09:00
Rui Ueyama 1fa4e34cec Inline memcpy
Teaching the compiler that `buflen` can never be negative here allows
it to inline subsequent `memcpy` function calls.
2024-05-11 16:28:46 +09:00
Rui Ueyama 1290a02a4e Refactor 2024-05-11 15:22:56 +09:00
Rui Ueyama 10b69883bd Refactor 2024-05-11 14:39:09 +09:00
Rui Ueyama fa8e95a289 Use SipHash instead of Blake3 for Identical Code Folding 2024-05-11 14:39:09 +09:00
Rui Ueyama 6373eb05a7 Refactor 2024-05-10 15:51:22 +09:00
Rui Ueyama 3ec4b1787e Refactor 2024-05-08 16:43:42 +09:00
Rui Ueyama 74129bfc86 Fix a test
Fixes https://github.com/rui314/mold/issues/1249
2024-05-06 13:54:17 +09:00
Rui Ueyama 595f98da78 Do not attempt to run an executable built with -z rodynamic
Such executables seem to crash immediately on older systems.
2024-05-06 13:12:33 +09:00
Rui Ueyama 2649b69e95 Fix CI 2024-05-05 22:43:58 +09:00
Rui Ueyama d20dc21e2a Update mold.1 (automated commit) 2024-05-05 13:22:52 +00:00
Rui Ueyama 7ac0816aa9 Fix typo 2024-05-05 22:21:56 +09:00
Rui Ueyama 154b6b047d Update mold.1 (automated commit) 2024-05-05 13:19:41 +00:00
Rui Ueyama 9a233df7e2 Support -z rodynamic
LLVM lld supports the flag.

Fixes https://github.com/rui314/mold/issues/1253
2024-05-05 22:17:52 +09:00
Rui Ueyama 7b2cb47727 Refactor 2024-05-05 13:08:04 +09:00
Rui Ueyama d683e8aa0b Add a test for SHF_GNU_RETAIN 2024-05-05 12:41:55 +09:00
Rui Ueyama 5ec3d2dcc2 Do not use std::osyncstream 2024-05-04 20:37:28 +09:00
Rui Ueyama edcf252603 Rename SyncOut -> Out 2024-05-04 18:16:04 +09:00
Rui Ueyama 024b03791a Remove extraneous newlines from "symbol missing" errors 2024-05-04 18:09:25 +09:00
Rui Ueyama 230ef819c2 Refactor 2024-05-04 18:08:09 +09:00
Rui Ueyama 5bdf48695a Use std::osyncstream 2024-05-04 17:42:47 +09:00
Rui Ueyama 78fea38b12 Remove a use of thread-local variable 2024-05-04 17:42:46 +09:00
Rui Ueyama 4510f4af81 Use pause instruction on ARM7 and ARM8
https://github.com/rui314/mold/issues/1250
2024-05-04 13:09:36 +09:00
Rui Ueyama a3480e8a2b Merge pull request #1251 from wahjava/fix-on-freebsd
Fix build on FreeBSD
2024-05-04 12:48:30 +09:00
Ashish SHUKLA 5396667ad5 Fix build on FreeBSD 2024-05-03 15:44:26 +00:00
Rui Ueyama f0f61cc7ad Do not use yield asm instruction on arm32
`yield` may not exist on older ARM processors.

Fixes https://github.com/rui314/mold/issues/1250
2024-05-03 21:44:27 +09:00
Rui Ueyama b237fd1ae9 Refactor 2024-05-03 20:35:14 +09:00
Rui Ueyama 2b67afb8a3 Simplify
We no longer use `sold` as a command name.
2024-05-03 15:55:26 +09:00
Rui Ueyama 20fa8d56f5 Bump mold version to 2.31.0 2024-05-03 10:04:55 +09:00
Rui Ueyama ed1e3c06a2 Do not copy mold executable to a test directory
mold executable can be quite large (~600 MiB with debug info),
so we don't want to casually copy that file.
2024-05-03 10:04:01 +09:00
Rui Ueyama fdbe7eecf9 Do not set root as the file owner 2024-05-02 14:52:17 +09:00
Rui Ueyama 16ad212919 Update the build script
It looks like ftp.gnu.org started redirecting incoming HTTP requests
to HTTPS.
2024-05-02 14:03:37 +09:00
Rui Ueyama 5c0cce3d6c Merge pull request #1246 from thesamesam/which-hunt 2024-04-30 17:50:06 +09:00
Sam James ec0a9d09dd test: use command -v, not non-portable which
`which` isn't in POSIX and several Linux distributions are trying to
remove it from their base system, see e.g. https://lwn.net/Articles/874049/.

Just use `command -v` which is POSIX.

Signed-off-by: Sam James <[email protected]>
2024-04-30 07:54:40 +01:00
Rui Ueyama ea054ccced Allow a newline or a tab at the begining of a linker script
Previously, the first four byte must have been "printable" bytes,
which exclude newlines or tabs.
2024-04-29 19:41:11 +09:00
Rui Ueyama cbbe23aa46 Do not show "entry symbol not found" error when creating a DSO 2024-04-28 18:06:54 +09:00
Rui Ueyama 990b0daae2 Optimize Aho-Corasick string search
We use Aho-Corasick string search algorithm for version script.
If all symbols in a version script do not start with '*', we can
terminate the algorithm early.
2024-04-28 17:53:20 +09:00
Rui Ueyama a16316e916 Show warning messages in purple instead of red 2024-04-28 16:56:25 +09:00
Rui Ueyama 14952546a4 Do not edit binary files with sed
Fixes https://github.com/rui314/mold/issues/1244
2024-04-28 15:43:28 +09:00
Rui Ueyama 020b1a78d9 Do not set the .text address as an entry adddress if -e is missing
lld does not do that too.
2024-04-28 15:43:28 +09:00
Rui Ueyama 6b70e02bf5 Report an error if a mergeable/merged section is too large
We use u32 as an index into mergeable/merged sections, so we cannot
handle extremely large sections.
2024-04-28 15:43:28 +09:00
Rui Ueyama 31969a2756 Refactor 2024-04-28 15:43:28 +09:00
Rui Ueyama 53ebcd80d8 Reduce memory usage
This change saves ~1 GiB memory allocation when linking ~3.8 GiB clang-19
binary because the binary contains tons of mergeable strings for debug info.
2024-04-26 14:51:01 +09:00
Rui Ueyama d714301180 Optimize 2024-04-25 18:04:18 +09:00
Rui Ueyama 002d619b11 Attempt to fix a test failure
I believe some version of objcopy corrupts an object file when
renaming a section. In this change, I use sed instead of objcopy
as a workaround.

Fixes https://github.com/rui314/mold/issues/1244
2024-04-25 16:59:40 +09:00
Rui Ueyama 55bb6bc318 Refactor 2024-04-25 15:42:01 +09:00
Rui Ueyama 05066e5279 Refactor 2024-04-25 12:17:21 +09:00
Rui Ueyama 131993634f Simplify 2024-04-24 16:05:19 +09:00
Rui Ueyama 40f6b17eda Optimize 2024-04-24 14:32:44 +09:00
Rui Ueyama 7a8ebc4dc7 Refactor 2024-04-24 13:19:20 +09:00
Rui Ueyama a9346f5c82 Add a test 2024-04-24 12:54:54 +09:00
Rui Ueyama 3104ce1dd2 Update the rule for Gentoo 2024-04-23 22:14:17 +09:00
Rui Ueyama d213f2b25d Add a rule for Clear Linux 2024-04-23 21:49:15 +09:00
Rui Ueyama 813089047f Refactor 2024-04-23 21:49:15 +09:00
Rui Ueyama 0ed5a9842a Refactor 2024-04-23 13:42:46 +09:00
Rui Ueyama f037cd0740 Refactor 2024-04-22 17:55:57 +09:00
Rui Ueyama c9faf3d345 Optimize 2024-04-22 17:47:06 +09:00
Rui Ueyama db8758060a Fix CI 2024-04-21 16:40:54 +09:00
Rui Ueyama c8ac0919a9 Fix CI 2024-04-21 15:44:21 +09:00
Rui Ueyama a2432e125a Refactor 2024-04-21 15:23:56 +09:00
Rui Ueyama e498e2fd83 Do not create .copyrel.rel.ro if -z norelro is given 2024-04-21 14:52:51 +09:00
Rui Ueyama c65121a370 Revert "Upgrade OneTBB in ./third-party to version 2021.12.0"
This reverts commit ddc63ba9b2 because it
broke macOS build.
2024-04-19 14:31:27 +09:00
Rui Ueyama fdc935718d Upgrade zlib in ./third-party to v1.3.1 2024-04-19 13:03:36 +09:00
Rui Ueyama 18d3b64bfb Upgrade zstd in ./third-party to v1.5.6 2024-04-19 12:35:31 +09:00
Rui Ueyama ddc63ba9b2 Upgrade OneTBB in ./third-party to version 2021.12.0 2024-04-19 12:28:54 +09:00
Rui Ueyama 5576b88604 Upgrade blake3 in ./third-party to v1.5.1 2024-04-19 12:25:41 +09:00
Rui Ueyama 852be4a465 Refactor 2024-04-18 22:15:03 +09:00
Rui Ueyama fb3c6029d0 Do not link against libblake3.so if we built it from ./third-party
Fixes https://github.com/rui314/mold/issues/1238
2024-04-18 21:57:16 +09:00
Rui Ueyama 9acd9ae894 Refactor 2024-04-18 21:08:48 +09:00
Rui Ueyama d478addd2b Refactor 2024-04-18 20:47:14 +09:00
Rui Ueyama 6463a7c48a Fix non-deterministic behavior 2024-04-18 20:21:55 +09:00
Rui Ueyama 7434465334 Fix CI 2024-04-18 17:00:29 +09:00
Rui Ueyama f7b705959e Remove a pragma
It looks like Clang recognize `#pragma GCC unroll`. Instead of adding
more C preprocessor directives here, I'd like to rely on the default
compiler optimizations.
2024-04-18 16:11:02 +09:00
Rui Ueyama 1648f716f4 Attempt to fix CI 2024-04-18 16:00:11 +09:00
Rui Ueyama 108b08fadb Fix a test 2024-04-18 14:47:36 +09:00
Rui Ueyama 204566cd14 Fix a crash bug involving synthesized symbols
If a synthesized symbol overrides an imported symbol, the symbol's
`is_imported` flag must be reset.

Technically, `compute_import_export` is called too early, and this
function call should be moved after `create_synthetic_sections`.
However, fixing the issue that way was very hard because until we
create output sections, we don't know what __start_/__stop_ symbols
need to be synthesized, but in order to create output sections,
we need to compute import/export bits.

So, in this commit, we simply reset `is_imported` flag for
synthesized symbols.

https://github.com/rui314/mold/pull/1236
2024-04-18 14:45:58 +09:00
Rui Ueyama a3b4192e15 Revert "Give symbols in .so higher priority than those in .a"
This reverts commit dfa26d0882 because it
caused mysterious runtime issues with XWayland. I also observed that the
change affected gnupg and others.

Fixes https://github.com/rui314/mold/issues/1235
2024-04-15 21:42:52 +09:00
Rui Ueyama dfa26d0882 1;10;0cGive symbols in .so higher priority than those in .a
Previously, .so and .a were of the same priority and therefore symbols
in those were resolved based on their positions in the command line;
whichever file appears first in the command line took precedence.

Here is the problem we are trying to solve with this change: KiCad
passes unnecessary .a files to the linker along with some .so files.
Some symbols are defined both by .a and by .so.

If a symbol is resolved from .a, the linker pulls out the file from
the archive, but because the .a file does not really provide a
complete set of object files, it ended with "undefined symbol" error.

If a symbol is resolved from .so, everything is fine.

This is arguably a bug in KiCad, or at least depending on the order of
files in the command line is very fragile. But maybe this change could
fix the issue without too much side effects. So let's see how it goes.

Fixes https://github.com/rui314/mold/discussions/1234
2024-04-15 12:19:50 +09:00
Rui Ueyama 2683a07955 Refactor 2024-04-14 19:11:54 +09:00
Rui Ueyama 62bfc5fd8c Remove unnecessary dependencies 2024-04-13 21:13:13 +09:00
Rui Ueyama f666282b4e Silence -Wunused-result
Fixes https://github.com/rui314/mold/issues/1228
2024-04-13 17:30:56 +09:00
Rui Ueyama 77aefdf6a6 Refactor 2024-04-13 17:30:32 +09:00
Rui Ueyama 60ce1aaf01 Improve a test
We want to make sure that absolute symbols in DSOs are dynamically
resolved.
2024-04-12 16:36:40 +09:00
Rui Ueyama 496e2f3eb3 Refactor 2024-04-11 14:26:37 +09:00
Rui Ueyama 93bae76f49 Update mold.1 (automated commit) 2024-04-10 07:04:37 +00:00
Rui Ueyama 7d17aa83eb Add --Bsymbolic-non-weak and --Bsymbolic-non-weak-functions
GNU ld doesn't support these options, but LLVM lld do.
2024-04-10 16:00:19 +09:00
Rui Ueyama 7835e45ded Update mold.1 (automated commit) 2024-04-10 05:28:22 +00:00
Rui Ueyama 0cf209002c Update the man page 2024-04-10 14:27:50 +09:00
Rui Ueyama 57f2260301 Update mold.1 (automated commit) 2024-04-09 12:24:04 +00:00
Rui Ueyama 76445ce73c Update the man page 2024-04-09 21:23:28 +09:00
Rui Ueyama 99a5b1541e Support -z start-stop-visibility=hidden
Fixes https://github.com/rui314/mold/issues/826
2024-04-08 13:11:15 +09:00
Rui Ueyama d432e987a0 Ignore --long-plt option
Our ARM32 PLT is always long, so we can safely ignore the flag.

Fixes https://github.com/rui314/mold/issues/1230
2024-04-06 10:29:38 +09:00
Rui Ueyama 71457375e4 Simplify 2024-04-05 22:37:12 +09:00
Rui Ueyama 8dc311c2d2 Attempt to fix CI 2024-04-05 22:31:27 +09:00
Rui Ueyama faa5e57913 Simplify 2024-04-05 21:08:57 +09:00
Rui Ueyama 4396784dbd Handle STT_COMMON as a synonym for STT_OBJECT 2024-04-05 20:33:13 +09:00
Rui Ueyama 81b714c4f3 Simplify 2024-04-03 15:29:14 +09:00
Rui Ueyama cb739c62a9 Refactor 2024-04-01 23:59:08 +09:00
Rui Ueyama 58cfc6ba39 Make RISC-V build reproducible 2024-03-30 16:28:46 +09:00
Rui Ueyama fafc315dac Simplify 2024-03-29 21:52:52 +09:00
Rui Ueyama ffcd64792d Update mold.1 (automated commit) 2024-03-29 11:14:42 +00:00
Rui Ueyama 8c24b22b2d Update the man page 2024-03-29 18:03:20 +09:00
Rui Ueyama 6fe9bdc3db Simplify 2024-03-29 17:43:06 +09:00
Rui Ueyama 9d8b243e39 Keep symbols specified by --undefined-glob during garbage collection 2024-03-29 17:39:12 +09:00
Rui Ueyama 9884e19b7f Attempt to fix Windows bot 2024-03-29 17:39:12 +09:00
Rui Ueyama 5304a343e4 Simplify 2024-03-29 15:42:10 +09:00
Rui Ueyama 38e8f06def Use trap instructions instead of nop to fill gaps 2024-03-29 15:39:59 +09:00
Rui Ueyama b93b85bcd5 Refactor 2024-03-29 12:38:57 +09:00
Rui Ueyama 587360662c Refactor 2024-03-29 11:25:36 +09:00
Rui Ueyama 95431aa7cf Do not run certain tests on qemu user-mode emulation
Fixes https://github.com/rui314/mold/pull/1206
2024-03-28 23:29:53 +09:00
Rui Ueyama 7de092df28 Remove dead code 2024-03-28 18:01:03 +09:00
Rui Ueyama 03b57c2655 Simplify 2024-03-28 17:47:28 +09:00
Rui Ueyama a1c835b993 Support --undefined-glob option 2024-03-28 16:44:43 +09:00
Rui Ueyama 4ada2f52da Refactor 2024-03-28 16:25:21 +09:00
Rui Ueyama 9bb1a8207a Update README.md 2024-03-28 13:08:53 +09:00
Rui Ueyama a9e76295ff Simplify 2024-03-28 11:52:33 +09:00
Rui Ueyama 4e3364ddd3 Format 2024-03-27 18:42:55 +09:00
Rui Ueyama 6e5ae45c1a Disable test on ppc64le
Fixes https://github.com/rui314/mold/issues/1221
2024-03-27 16:27:37 +09:00
Rui Ueyama 11c9376a50 Fix tests for musl libc-based systems
Fixes https://github.com/rui314/mold/issues/1221
2024-03-27 16:27:37 +09:00
Rui Ueyama b5f2a7b1ce Do not call do_lto() if all LTO input files are dead
Fixes https://github.com/rui314/mold/issues/1226
2024-03-27 15:27:52 +09:00
Rui Ueyama 3df7c8e89c Export __global_pointer$ from executable
RISC-V psABI requires that symbol to be exported from an executable
if there's a GP-relative reference. For simplicity, we always export
it from executable as long as it has a .dynamic section.

https://github.com/rui314/mold/issues/1222
2024-03-25 17:57:52 +09:00
Rui Ueyama 0edbe29d38 Simplify 2024-03-25 17:57:52 +09:00
Rui Ueyama 8df5a76203 Attempt to fix Windows CI 2024-03-25 17:57:52 +09:00
Rui Ueyama 32c4a09deb Fix file paths in .repro tar file
Previously, all pathnames were accidentally recorded as the name of
the tar file itself.

Fixes https://github.com/rui314/mold/issues/1227
2024-03-25 17:15:51 +09:00
Rui Ueyama ee6907f678 Fix "skipping incompatible file" errors
https://github.com/rui314/mold/pull/1217
2024-03-25 14:52:54 +09:00
Rui Ueyama 03640029db Refactor 2024-03-21 15:03:45 +09:00
Rui Ueyama 7a65ed9082 Refactor 2024-03-21 11:00:13 +09:00
Rui Ueyama b10f818106 Attempt to fix Windows build 2024-03-21 10:42:30 +09:00
Rui Ueyama caad70dd6f Do not group mergeable sections in an output file
Runtime doesn't care where mergeable sections are, but it's reported
that Valgrind does. So, grouping mergeable sections just doesn't seem
to make sense.

https://github.com/rui314/mold/pull/1223
2024-03-20 22:15:05 +09:00
Rui Ueyama 6fe4f23074 Attempt to fix CI 2024-03-20 18:41:57 +09:00
Rui Ueyama fd8fb73492 Refactor 2024-03-20 17:37:57 +09:00
Rui Ueyama 7c8ed4d2e1 Refactor 2024-03-20 15:05:16 +09:00
Rui Ueyama 08fada7de0 Update README.md 2024-03-20 13:44:09 +09:00
Rui Ueyama 163975d82a Make linker script INPUT command to look for a file from the script's directory
https://github.com/rui314/mold/pull/1217
2024-03-19 17:16:38 +09:00
Rui Ueyama dda521e2bc [ARM32] Show "recompile with -fPIC" error for absolute relocations
Fixes https://github.com/rui314/mold/issues/1220
2024-03-19 13:19:05 +09:00
Rui Ueyama e7dc90dadc Refactor 2024-03-18 17:10:18 +09:00
Rui Ueyama 8375741489 Merge pull request #1201 from nga888/win-mem-map-output-file
Add "basic" support for memory mapping to Windows OutputFile
2024-03-16 11:35:25 +09:00
Rui Ueyama 042b14653e Do not update the man page for branches other than main 2024-03-16 09:18:48 +09:00
Rui Ueyama c7f6a91da5 Bump mold version to 2.30.0
libtool mistakes mold 2.4.1 as GNU ld 2.4 and wrongly concludes that our
linker does not support anonymous versioning and suppresses some uses of
version scripts. That has been causing subtle compatibility issues with
programs that use libtool to create their .so files.

Here is the code that mistakes our linker as GNU ld:
https://git.savannah.gnu.org/cgit/libtool.git/tree/m4/libtool.m4?h=v2.4.7#n5066

As a workaround, I decided to bump our linker version so that the version
number is sufficiently large. This is admittedly ugly but I think it's the
simplest solution for the problem.
2024-03-15 18:40:46 +09:00
Rui Ueyama c26260e38d Do not set symbol size to imported dynamic symbols
The dynamic linker does not seem to care about st_size of undefined
dynamic symbols, but semantically it shouldn't be an arbitrary number.
2024-03-15 11:35:48 +09:00
Rui Ueyama ac04d79ad2 Extend test timeout from 1 hour to 3 hours 2024-03-15 11:35:43 +09:00
Rui Ueyama 052b89e8d6 Copy mold to the top source directory
test/gentoo-test.sh depends on the presence of the file.
2024-03-14 16:15:29 +09:00
Rui Ueyama c395da1c54 Do not create an unnecessary gap in file for BSS
Fixes https://github.com/rui314/mold/issues/1216
2024-03-13 16:01:04 +09:00
Rui Ueyama 16b0564d49 Fix CI 2024-03-12 14:38:42 +09:00
Rui Ueyama d21207cc79 Handle sections with an unknown section type as errors
Fixes https://github.com/rui314/mold/issues/1215
2024-03-12 14:13:52 +09:00
Rui Ueyama c4b69a1077 Re-throw a signal
Previously, mold exists silently when it gets killed by SIGSEGV.
This change makes it verbose; now the parent process would report
an error.
2024-03-11 16:04:55 +09:00
Rui Ueyama c85dd8881b Refactor 2024-03-10 18:21:04 +09:00
Rui Ueyama 8eae0a33b3 [PPC32] Do not discard .got2 even if --gc-sections is given
Even though .got2 may not be referenced directly, we use that section
when processing relocations.
2024-03-10 18:16:11 +09:00
Rui Ueyama 6356fa09e9 Fix gentoo package name 2024-03-09 16:18:07 +09:00
Rui Ueyama 641e7c2b05 Use madvise instead of munmap
Using madvise(2) is clearer than munmap(2), and the madvise approach
seems to be slightly faster than mmap on my machine.
2024-03-09 13:39:59 +09:00
Rui Ueyama c60d1d0877 Use xxhash instead of gdb_hash for the internal hashmap for .gdb_index
Our ConcurrentMap uses linear probing to find unused hash table entry.
It gives up if 128 consective slots are occupied, and the whole process
dies with the "ConcurrentMap is full" error message. So our hash
function's quality must be high.

For .gdb_index, we used to use gdb_index() to compute keys for the
ConcurrentMap. It turned out that the function's quality is poor,
generating very similar output for short strings.

This commit changes the hash function to xxhash.

Fixes https://issues.chromium.org/issues/40276991#comment5
2024-03-06 13:12:53 +09:00
Rui Ueyama c45dd19d47 Do not trap SIGABRT unless older versions of TBB is in use 2024-03-05 10:04:54 +09:00
Rui Ueyama 51f871f43b Silently ignore broken .llvm_addrsig
Fixes https://github.com/rui314/mold/issues/1213
2024-03-04 16:57:12 +09:00
Rui Ueyama 89a4720575 Correctly print out error messages from signal handlers 2024-03-03 14:27:43 +09:00
Rui Ueyama 081e447a1b Simplify 2024-03-02 17:42:57 +09:00
Rui Ueyama da0ee683a3 Show sha256 checksum 2024-03-02 13:27:27 +09:00
Rui Ueyama c6e5e2c0a0 Dogfood setup-mold 2024-03-01 20:23:35 +09:00
Rui Ueyama fb3c166f80 Bump mold version to 2.4.1 2024-03-01 11:30:05 +09:00
Rui Ueyama 39643aaf94 Fix GCC warning
Fixes https://github.com/rui314/mold/issues/1208
2024-03-01 10:44:35 +09:00
Rui Ueyama 50bdf39ba5 Mark shared symbols as weak if all references are weak
Previously, mold marked an imported symbol as a strong one if the
symbol came from a DSO and was exported as a strong symbol by the DSO.
This logic resulted in a miscomputation of the weakness bit, causing a
compatibility issue with other linkers.

Now, an imported symbol is marked as strong only when there's at least
one strong reference to it. In other words, if all references to an
imported symbol are weak, the symbol will be imported as a weak one.

Fixes https://github.com/llvm/llvm-project/issues/83080
2024-02-29 12:57:11 +09:00
Rui Ueyama 1e413d4121 Fix GCC warning
Fixes https://github.com/rui314/mold/issues/1205
2024-02-28 14:03:41 +09:00
Rui Ueyama fcda712238 Attempt to fix GCC warnings
Fixes https://github.com/rui314/mold/issues/1204
2024-02-27 16:38:45 +09:00
Rui Ueyama d4ff48a07e [PPC64V2] Emit _savegpr0_*, _restgpr0_*, _savegpr1_* and _restgpr1_* symbols
Fixes https://github.com/rui314/mold/issues/1203
2024-02-27 14:35:19 +09:00
Rui Ueyama 21f25545a8 Update README 2024-02-27 12:02:44 +09:00
Andrew Ng 37f975bf82 Add "basic" support for memory mapping to Windows OutputFile
This is a "basic" implementation that does not make use of intermediate
temporary files.

Has been seen to improve linking performance by up to ~1.78x.
2024-02-26 11:34:33 +00:00
Rui Ueyama 58558e319c RISC-V: Define __global_pointer$ for DSO
Fixes https://github.com/rui314/mold/issues/1182
2024-02-26 14:38:38 +09:00
Rui Ueyama 48f6a08352 Refactor 2024-02-26 12:29:27 +09:00
Rui Ueyama 01e9f42c10 Merge pull request #1198 from nga888/win-build-tidy
Minor fixes for the Windows build
2024-02-26 12:12:11 +09:00
Rui Ueyama 8c3967886c Remove code for the sold linker 2024-02-25 14:29:53 +09:00
Rui Ueyama 8a4fb3b445 Merge pull request #1199 from nga888/win-mem-map-file-handling
Fixes and improvements to Windows file handling and memory mapping
2024-02-25 12:58:08 +09:00
Rui Ueyama 04c9b62660 Update README 2024-02-24 15:08:05 +09:00
Rui Ueyama 4790349f79 Update README 2024-02-24 15:07:25 +09:00
Rui Ueyama 3e6005a1e7 Update benchmark results 2024-02-24 15:04:34 +09:00
Andrew Ng 9e3565027a Fixes and improvements to Windows file handling and memory mapping
Prior to this change, links on Windows were failing with:

  mold: fatal: <input file>: CreateFileMapping failed: 5

"5" is the Windows error code for ERROR_ACCESS_DENIED.

Use native Windows file API in MappedFile. Create read only mapping with
FILE_MAP_COPY view because the file mapping contents may be modified in
process but should not be written back.

On Windows, it is important that files are opened in "binary" mode to
avoid automatic "text" mode conversions which can result in "corrupted"
input/output.
2024-02-23 14:40:39 +00:00
Rui Ueyama 6d8e6afbf2 Fix R_PPC64_DTPREL16_LO_DS
Fixes https://github.com/rui314/mold/issues/1192
2024-02-23 17:14:25 +09:00
Rui Ueyama 5b5f431717 Fix CI 2024-02-22 09:13:26 +09:00
Andrew Ng 796e2556b6 Minor fixes for the Windows build
Removes some Windows build warnings.

Fixes conversion of Windows FILETIME to nsec in get_usage() used by
TimerRecord.
2024-02-21 15:24:07 +00:00
Rui Ueyama 8090737d56 Merge sections with different flags into a single section
Previously, mold didn't merge sections with the same name but different
section flags into a single section. This behavior is different from other
linkers and caused a compatibility issue.

Now, mold merges input sections by their names and types.

Fixes https://github.com/rui314/mold/issues/1196
2024-02-21 18:50:47 +09:00
Rui Ueyama d39a0c7035 Refactor 2024-02-21 18:21:03 +09:00
Rui Ueyama de696ba5a4 --repro: Do not try to reopen files with different paths
Fixes https://github.com/rui314/mold/issues/1197
2024-02-21 17:11:04 +09:00
Rui Ueyama cefa2d9100 Add Red Hat Enterprise Linux
Fixes https://github.com/rui314/mold/issues/1191
2024-02-20 15:11:42 +09:00
Rui Ueyama 9f07fd035c Support R_PPC64_DTPREL16_LO_DS
Fixes https://github.com/rui314/mold/issues/1192
2024-02-20 13:07:02 +09:00
Rui Ueyama 7ead7231c9 Fix CI 2024-02-19 16:50:23 +09:00
Rui Ueyama b0a3a5ce62 Drop SHF_COMPRESSED when creating a relocatable file
We always uncompress compressed sections when copying, so we should
not propagate the flag.

Fixes https://github.com/rui314/mold/issues/1195
2024-02-19 16:22:39 +09:00
Rui Ueyama 64c2084c8a Pass GCC offload data to the LTO backend
Fixes https://github.com/rui314/mold/issues/1190
2024-02-19 15:12:43 +09:00
Rui Ueyama 4c355239ad Refactor 2024-02-19 15:12:43 +09:00
Rui Ueyama 79169ea315 Format 2024-02-19 15:12:43 +09:00
Rui Ueyama 4db866d5c3 Merge pull request #1193 from gruenich/feature/range-loop-reference 2024-02-11 03:11:50 +09:00
Christoph Grüninger 83a5bfbfb1 elf/cmdline.cc: Add missing references in range-for loops
std::string is a non-trivial type, this change ensures its
copy-ctor and dtor don't get called.
Found by Clazy (range-loop-reference)


Signed-off-by: Christoph Grüninger <[email protected]>
2024-02-10 17:42:07 +01:00
Rui Ueyama c9651d9b2b Merge pull request #1187 from aokblast/fix/fileoffset 2024-02-04 00:37:32 +09:00
aokblast 8c99dd0f2c Feat: use accumulate address on sections with SHT_ALLOC 2024-02-03 22:03:55 +08:00
Rui Ueyama 25351379ab Fix CI 2024-02-02 02:59:50 +09:00
Rui Ueyama ba67c5d977 Fix CI 2024-02-02 02:35:12 +09:00
Rui Ueyama 7f8d77d84e Do not optimize absolute symbols in DSOs 2024-02-02 01:28:09 +09:00
Rui Ueyama 91a88ee80f Merge pull request #1186 from dancrossnyc/upstream
illumos: Treat absolute symbols specially
2024-02-02 01:25:18 +09:00
Rui Ueyama 206fe8527f Do not try to use /proc/self/exe on macOS
That path does not exist on macOS.

Fixes https://github.com/rui314/mold/issues/1185
2024-02-01 16:23:24 +01:00
Dan Cross bed5b1731b illumos: Treat absolute symbols specially
illumos is a descendent of Solaris/OpenSolaris, and treats
symbols with shndx=SHN_ABS and st_value=0 specially, such that
they should be marked imported.  This change adds a special
case for this behavior, wrapped up in a function that #ifdef's
out on other platforms.  With this patch, executables linked
with mold on illumos run; without it, they do not.

The original behavior was spotted by Luqman Aden (@luqmana).
This also patches a small bogon in the third-party `mimalloc`
code.  I've sent that same fix upstream, but so far the
maintainer has not resonded, so replicate here.

Fixes #1183

Signed-off-by: Dan Cross <[email protected]>
2024-02-01 13:55:05 +00:00
Rui Ueyama c36b83fdea Merge pull request #1177 from ishitatsuyuki/icf-workaround
Ignore addrsig sections corrupted by strip
2024-01-20 20:59:56 +09:00
Tatsuyuki Ishi fede4a8102 Ignore addrsig sections corrupted by strip
When removing symbols, the indices in .llvm_addrsig also needs to be
updated, but binutils is not aware of this and leaves the table in a
corrupt state.

Use the same test as what LLD uses to reject potentially corrupted addrsig
sections.

Unfortunately, testing this seems tricky. While producing a file with OOB
addrsig index is not hard, it's hard to consistently trigger a segfault
with that access.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2024-01-20 17:28:51 +09:00
Rui Ueyama 29c0baa151 Merge pull request #1175 from filipnavara/aadwarf 2024-01-19 18:57:18 +09:00
Rui Ueyama 5d04d9c6cd Merge pull request #1176 from sicherha/fix_unit_tests_on_s390x 2024-01-19 17:58:52 +09:00
Christoph Erhardt 2415b3e0bb [ELF][S390X] Skip tests that still fail with GCC 14
Signed-off-by: Christoph Erhardt <[email protected]>
2024-01-18 21:53:12 +01:00
Filip Navara d832547ba2 Add support for .eh_frame section on arm32 2024-01-18 18:28:26 +01:00
Muhammad Mominul Huque f05e32df4a Update README.md
GCC doesn't accept a path as an argument to `-fuse-ld`. `-fuse-ld=mold` is enough.

Tested with
```
gcc (GCC) 13.2.1 20231205 (Red Hat 13.2.1-6)
```
2023-12-31 18:21:30 +06:00
Rui Ueyama fe118f6347 Fix CI 2023-12-24 14:42:01 +09:00
Rui Ueyama 9554b92c86 Allow escaped meta-characters in version script patterns 2023-12-24 14:04:51 +09:00
Rui Ueyama 669ada024e Simplify 2023-12-24 13:53:02 +09:00
Rui Ueyama e1e16bf85a Fix version script priority
Previously, if two or more VERSION clauses match to the same symbol,
the first one took precedence. This was incompatible with GNU ld, which
gives the last one the highesth priority.

This change inverted the priority so that the last one will take
precedence other the others.

Fixes https://github.com/rui314/mold/issues/1158
2023-12-23 18:20:22 +09:00
Rui Ueyama 35516a6fc2 Do not include '@' in symbol name
Fixes https://github.com/rui314/mold/issues/1168
2023-12-23 15:37:39 +09:00
Rui Ueyama 51ecb10b01 Refactor 2023-12-23 15:35:43 +09:00
Rui Ueyama 10ce09fe0c Refactor 2023-12-23 15:16:02 +09:00
Rui Ueyama 0702d4185a Refactor 2023-12-19 15:32:43 +09:00
Rui Ueyama afe57e99d1 Update comments 2023-12-15 11:55:53 +09:00
Rui Ueyama e921486008 Fix CI
It seems some versions of objdump cannot disassemble c.li instruction.
2023-12-13 13:26:56 +09:00
Rui Ueyama 70d59ca1cd Improve test 2023-12-12 22:11:35 +09:00
Rui Ueyama 2ccaa81aab Relax a GOT load into a direct address materialization
https://github.com/riscv-non-isa/riscv-elf-psabi-doc/pull/397
2023-12-12 16:41:18 +09:00
Rui Ueyama 4db2eb91e9 Do not rewrite endbr64 of exported functions
Pointed out by @MaskRay at
https://github.com/llvm/llvm-project/issues/74400#issuecomment-1848686317.
2023-12-12 15:36:43 +09:00
Badini Rachid RodrigueandBADINI Rachid Rodrigue a4daff2d83 Add entry for EndeavouOS (#1165)
---------

Co-authored-by: BADINI Rachid Rodrigue <[email protected]>
2023-12-12 04:08:42 +09:00
Rui Ueyama cc884d57ca Fix cross building of ARM64 binaries on big-endian machines
It's probably very rare to do so, but mold couldn't create ARM64
binaries on big-endian machines due to a typo in an ELF struct
definition.
2023-12-10 16:06:52 +09:00
Rui Ueyama 0c8a872c37 Avoid using the HOST environment variable
It seems HOST is a fairly common environment variable. Let's use
MACHINE instead.

Fixes https://github.com/rui314/mold/issues/1163
2023-12-07 09:45:29 +09:00
Rui Ueyama 663ac74610 Simplify 2023-12-06 19:51:22 +09:00
Rui Ueyama f04aebf1e0 Simplify 2023-12-06 13:04:58 +09:00
Rui Ueyama bcf3b48e98 Simplify 2023-12-06 11:33:46 +09:00
Rui Ueyama e7cc9455c9 Merge pull request #1161 from ishitatsuyuki/prealloc
Use fallocate when creating a new output
2023-12-06 11:05:17 +09:00
Tatsuyuki Ishi e909535684 Use fallocate when creating a new output
Using ftruncate() creates holes inside files, that on first (write) page
fault gets allocated an actual disk block.  There are several
disadvantages with this:
- Allocating block one-by-one is slower than doing it all at once.
- As we write to the output in parallel the write order is not exactly
  sequential.  The allocated blocks can also be out-of-order and make the
  on-disk representation fragmented.

Instead, use posix_fallocate() which allocates a contiguous on-disk block
(if possible).  It's arguably a better fit for linker output, since holes
are unlikely to be present after writing all the output (any contiguous
zeroes should just go into .bss).

I benchmarked this by linking mold with debug symbols.  On an 8-core
desktop, it is 5% (1.0s -> 0.95s) faster, but on a 64-core workstation it
is actually 40% slower (0.37s -> 0.52s).  The total amount of System time
is reduced (6.7s -> 5.2s), however.  I suspect that the reduced page fault
work ends up causing more lock contention.

Despite potential regression on high-core systems I still think this
change is justified because of the better on-disk layout.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2023-12-05 16:07:48 +09:00
Rui Ueyama cea54cb865 Check the runtime libtbb version to handle sigabrt 2023-12-05 12:25:52 +09:00
Rui Ueyama 3bc2e9bdb1 Revert "Set DF_SYMBOLIC if -Bsymbolic is given"
This reverts commit 9d35710419 to unbreak CI.
2023-12-04 16:07:56 +09:00
Rui Ueyama 159960c72e Fix errors in the help message; this change was made using ChatGPT 2023-12-04 15:34:56 +09:00
Rui Ueyama 9d35710419 Set DF_SYMBOLIC if -Bsymbolic is given
I don't think this would make any difference, but the ELF spec says
that the flag is set if symbols are bound locally.
2023-12-04 15:10:01 +09:00
Rui Ueyama bdcf448605 Emit TLS common symbols in .tls_common
Section names are not significant in executables or DSOs, so this is
a purely aesthetic change.
2023-12-04 13:13:33 +09:00
Rui Ueyama 72eb180028 Upgrade OneTBB to version 2021.11.0 2023-12-02 12:34:16 +09:00
Rui Ueyama fe427bfe7a Do not compute per-InputSection address-taken bit for -z rewrite-endbr
We now compute per-Symbol address-taken bit in rewrite_endbr,
so this step is not necessary anymore.
2023-12-02 12:34:16 +09:00
Rui Ueyama 348f5c7086 Merge pull request #1160 from tzussman/ci_fix 2023-12-02 07:52:41 +09:00
Tal Zussman e09b877146 Fix typos in ci.yml 2023-12-01 10:01:25 -05:00
Rui Ueyama aeb7ba4731 Add asserts 2023-12-01 21:03:15 +09:00
Rui Ueyama 421cd1c3ca Revert "Attempt to fix CI"
This reverts commit 5e8729bd6f.
2023-12-01 10:26:10 +09:00
Rui Ueyama 5e8729bd6f Attempt to fix CI 2023-12-01 10:19:48 +09:00
Rui Ueyama 76d057a418 Archive entire build directory when CI fails 2023-11-30 19:34:08 +09:00
Rui Ueyama ea9864bbd5 Do not try to demangle C++ symbols as Rust ones
Previously, we always tried to demangle a symbol name as a Rust
symbol first and then attempted to demangle it as a C++ symbol.
This resulted in an incorrect demangled result, as the Rust legacy
mangling scheme is not distinguishable from C++.

This patch fix the issue by adding the "is_rust_obj" flag to the
ObjectFile. We try to demangle a symbol as a Rust one before as a C++
only if the flag is true.

Fixes https://github.com/rui314/mold/issues/1159
2023-11-30 13:53:23 +09:00
Rui Ueyama 3d9b4d5c05 Bump mold version to 2.4.0 2023-11-29 17:58:00 +09:00
Rui Ueyama d06bfe0ac3 Update GitHub sponsor list 2023-11-29 17:48:11 +09:00
Rui Ueyama 6540294a81 Fix CI 2023-11-29 17:15:39 +09:00
Rui Ueyama 02a7640047 Fix build artifact pathnames 2023-11-29 16:59:23 +09:00
Rui Ueyama f4c5a8a42e Allow multiple .eh_frame sections in a single object file
A `.eh_frame` section contains data for exception handling. Usually,
an object file contains only one `.eh_frame` section, which explains
how to handle exceptions for all text sections in the same object file.

However, it appears that, in rare cases, we need to handle object
files containing multiple `.eh_frame` sections. An example of this is
the `/usr/lib/clang/17/lib/x86_64-redhat-linux-gnu/clang_rt.crtbegin.o`
file, which is provided by the `compiler-rt` package of Fedora 39.
Specifically, I'm using the `quay.io/fedora/fedora:39` Docker image.
The file contains two `.eh_frame` sections.

One `.eh_frame` in the file is of type `STT_X86_64_UNWIND` and the
other is of `STT_PROGBITS`. It's possible that the file was created
with `ld -r`, and the linker failed to merge the two incoming
`.eh_frame` sections into one output section due to the difference in
section types.

We did not expect such inputs and consequently produced corrupted
output files.

This commit improves our linker so that mold can handle multiple
`.eh_frame` sections in a single object file.

Fixes https://github.com/rui314/mold/issues/1157
2023-11-29 16:10:36 +09:00
Rui Ueyama 0fdbace9b2 Improve compatibility of precedences in version script patterns
Previously, symbol names or wildcard patterns in version script files
were processed strictly from top to bottom. When a single symbol
matches two or more patterns, the first one was given precedence.

However, GNU ld appears to treat exact matches differently, assigning
them higher precedence over wildcard patterns. This discrepancy led to
programs linked with Qt 6.6.1 using mold failing to launch.

Fixes https://github.com/rui314/mold/issues/1158
2023-11-28 15:43:48 +09:00
Rui Ueyama cab6014154 Merge pull request #1156 from ywgrit/main
Remove assertions to make errors exposed in a customized way
2023-11-26 18:55:40 +09:00
Rui Ueyama 000ce0ee52 Handle LLVM-generated TLSDESC code sequence
It had been assumed that the TLSDESC code seqeunce looks like this:

  lea    0(%rip), %rax
      R_X86_64_GOTPC32_TLSDESC    foo
  call   *(%rax)
      R_X86_64_TLSDESC_CALL       foo

However, LLVM seems to also emit something like this:

  lea    0(%rip), %reg
      R_X86_64_GOTPC32_TLSDESC    foo
  ...
  mov    %reg, %rax
  ...
  call   *(%rax)
      R_X86_64_TLSDESC_CALL       foo

That means when we rewrite the LEA instruction to relax the code
sequence, we need to handle destination registers other than %rax.

The wrong assumption caused a program crash as reported as
https://bugs.gentoo.org/914849.
2023-11-26 18:49:20 +09:00
ywgrit d720a2011c Remove assertions to make errors exposed in a customized way 2023-11-26 17:04:28 +08:00
Rui Ueyama c91055271b Simplify 2023-11-24 16:15:37 +09:00
Rui Ueyama 1478acd551 Do not save strings if not necessary 2023-11-24 11:48:19 +09:00
Rui Ueyama b3404c07d7 Add a timer prober 2023-11-24 10:44:18 +09:00
Rui Ueyama 94767dc0e7 Fix a crash bug 2023-11-24 10:14:00 +09:00
Rui Ueyama f957acfe11 Add an entry for Amazon Linux 2023-11-24 09:30:56 +09:00
Rui Ueyama 7fc43ccc3a Add comments 2023-11-24 08:40:57 +09:00
Rui Ueyama dc5b9508d9 Simplify 2023-11-24 06:39:27 +09:00
Rui Ueyama ef056d8ed1 Refactor 2023-11-24 06:38:29 +09:00
Rui Ueyama 6a63671ef5 Simplify 2023-11-23 12:22:14 +09:00
Rui Ueyama d3867766af Remove redundant check
`is_ld("ld")` returns true
2023-11-23 11:47:33 +09:00
Rui Ueyama 1ae4b9571b Merge pull request #1154 from andreas-schwab/main
mold-wrapper: also wrap posix_spawnp
2023-11-23 11:39:07 +09:00
Andreas Schwab 3fd1cec20e mold-wrapper: also wrap posix_spawnp
Fixes issue 1153.

Signed-off-by: Andreas Schwab <[email protected]>
2023-11-22 14:59:11 +01:00
Rui Ueyama 28b16eea19 Rename classes 2023-11-22 20:56:21 +09:00
Rui Ueyama 37f32a9008 Simplify 2023-11-22 14:12:37 +09:00
Rui Ueyama 8415252322 Do not use std::aligned_alloc
Fixes https://github.com/rui314/mold/issues/1152
2023-11-22 11:52:33 +09:00
Rui Ueyama b3984c9580 Simplify 2023-11-21 21:23:47 +09:00
Rui Ueyama 6616995804 Refactor 2023-11-21 12:23:49 +09:00
Rui Ueyama de7d37e1fc Do not count .tbss into account when creating PT_GNU_RELRO
Fixes https://github.com/rui314/mold/issues/1151
2023-11-21 11:44:52 +09:00
Rui Ueyama 3cb8a528a5 Make -z rewrite-endbr to work without -ffunction-sections
Now we can rewrite an endbr64 instruction even if it is not at the
beginning of a section.
2023-11-20 17:36:23 +09:00
Rui Ueyama 8bb01e1a0e Fix CI 2023-11-20 14:25:03 +09:00
Rui Ueyama 0416d7ed80 Add --nmagic
Fixes https://github.com/rui314/mold/issues/1144
2023-11-20 13:35:04 +09:00
Rui Ueyama c02c04f08a Improve dist.sh 2023-11-20 11:55:14 +09:00
Rui Ueyama efb952ef12 Use the release version of setup-mold GitHub action 2023-11-19 10:32:51 +09:00
Rui Ueyama 04b145d795 Add comments and fix SHA256 digest 2023-11-19 09:36:04 +09:00
Rui Ueyama 1830fa1d84 Pin docker images with sha256 digests for additional security 2023-11-18 12:25:37 +09:00
Rui Ueyama a2b300b863 Fix CI 2023-11-17 19:12:19 +09:00
Rui Ueyama 2d8297a562 Fix CI 2023-11-17 18:22:16 +09:00
Rui Ueyama deb7728b46 Fix CI 2023-11-17 18:08:13 +09:00
Rui Ueyama bbd6d86af4 Fix CI 2023-11-17 17:57:59 +09:00
Rui Ueyama b6361b8cad Refactor 2023-11-17 17:51:25 +09:00
Rui Ueyama 04fdae124f Refactor 2023-11-17 17:41:24 +09:00
Rui Ueyama 29df7093a8 Refactor 2023-11-17 17:18:53 +09:00
Rui Ueyama 18212a21ab Refactor 2023-11-17 17:04:43 +09:00
Rui Ueyama e13d28bfe6 Create non-x86 binaries on daily basis
Building non-native binaries using Qemu is extremely heavy.
It takes ~2 hours on a GitHub Actions runner. We don't want to
run it on each push.
2023-11-17 15:45:39 +09:00
Rui Ueyama 667204ac7c Make dist.sh to work on macOS 2023-11-17 15:09:31 +09:00
Rui Ueyama 76ea83e791 Create binary tarballs for non-x86-46 targets 2023-11-17 14:15:04 +09:00
Rui Ueyama 55daf7385d Cache Docker images for GitHub Actions 2023-11-17 13:42:54 +09:00
Rui Ueyama 0e66e4b7e9 Create distribution tarballs for each commit 2023-11-16 22:54:38 +09:00
Rui Ueyama fe485db9b4 Refactor 2023-11-16 18:30:31 +09:00
Rui Ueyama b3dc4879fd Remove a global variable 2023-11-16 18:00:41 +09:00
Rui Ueyama 25ce3738b9 Update workflow 2023-11-16 16:50:33 +09:00
Rui Ueyama 22b8ac8470 Update mold.1 2023-11-16 07:16:13 +00:00
Rui Ueyama 6a4e128ef8 Document --spare-program-headers 2023-11-16 16:15:28 +09:00
Rui Ueyama 66d68c8a98 Auto-generate docs/mold.1 2023-11-16 16:15:28 +09:00
Rui Ueyama 76867af090 Regenerate mold.1
This file is created by `ronn` command.
2023-11-16 12:54:49 +09:00
Rui Ueyama 5d02248a0b Sort entries and fix typos 2023-11-16 12:54:38 +09:00
Rui Ueyama eb6c213f2a Add --spare-program-headers
This is a new experimental flag to make room at the end of PHDR
so that post-processing tools can add more entries as needed.

Fixes https://github.com/rui314/mold/issues/1148
2023-11-16 12:07:13 +09:00
Rui Ueyama efdac9afdd Merge pull request #1147 from Saldivarcher/add-trace-symbol-help-msg 2023-11-16 05:45:29 +09:00
Miguel Saldivar c6bfff2a04 Add --trace-symbol to the help message 2023-11-14 14:24:41 -06:00
Rui Ueyama 49066ea329 Bump mold version to 2.3.3 2023-11-14 14:41:42 +09:00
Rui Ueyama 25ceab07f7 Build mold on Debian 8 (Jessie)
Debian 8 was released in April 2015, which is old enough for
binary distribution. Previously, we used to use Debian 10 (Bullseye),
which was released in July 2019.
2023-11-14 12:44:02 +09:00
Rui Ueyama 6e97393740 Fix tests 2023-11-14 12:44:02 +09:00
Rui Ueyama ff3d54d26f Fix a crash bug in --defsym
Fixes https://github.com/rui314/mold/issues/1108
2023-11-13 20:09:25 +09:00
Rui Ueyama 25c9cf3976 Refactor 2023-11-13 19:57:50 +09:00
Rui Ueyama 5f2fb08cfd Make dist.sh reproducible
dist.sh script should now produce bit-by-bit identical output files now.

Fixes https://github.com/rui314/mold/issues/1146
2023-11-13 17:14:37 +09:00
Rui Ueyama 60bc56848f Fix tests for Debian 9 2023-11-13 13:37:35 +09:00
Rui Ueyama 600ca381fd Reduce the size of Symbol by 2 bytes 2023-11-12 13:39:44 +09:00
Rui Ueyama da3f5dd4ec Fix --dynamic-list for DSOs
--dynamic-list, --export-dynamic-symbol and --export-dynamic-symbol-list
have different semantics for executables and DSOs. If the output is an
executable, they specify a list of symbols that are to be exported.
If the output is a shared object, they specify the list of symbols that
are to be interposable.

mold havne't implemented the latter semantics. This commit fixes that
issue.

Fixes https://github.com/rui314/mold/issues/1071
2023-11-12 13:00:24 +09:00
Rui Ueyama b59911b0c1 Refactor 2023-11-11 15:09:51 +09:00
Rui Ueyama 8131528305 Rename long .rodata section names 2023-11-10 15:07:19 +09:00
Rui Ueyama 6a71e7dd4b Refactor 2023-11-09 18:57:13 +09:00
Rui Ueyama a63b2f70b2 Fix CI 2023-11-09 15:47:25 +09:00
Rui Ueyama 3f88964527 Map .ctors/.dtors into .init_array/.fini_array
.init_array/.fini_array haven't completely replace .ctors/.dtors, so
we need to convert .ctors/.dtors to .init_array/.fini_array.

Fixes https://github.com/rui314/mold/issues/1113
2023-11-09 12:43:58 +09:00
Rui Ueyama e18dc70af0 Refactor 2023-11-08 22:04:14 +09:00
Rui Ueyama 0c6daad215 Add more tests 2023-11-07 15:20:31 +09:00
Rui Ueyama 8c35c80bb7 Enable qemu tests by default 2023-11-07 13:55:56 +09:00
Rui Ueyama d42ba3bae4 Fix CI 2023-11-07 13:48:48 +09:00
Rui Ueyama 8c337b25df Run power10 tests on qemu only if qemu and cross GCC suports power10 2023-11-07 12:04:22 +09:00
Rui Ueyama 8d1c08bd03 Simplify 2023-11-07 11:25:03 +09:00
Rui Ueyama 5599a6f24f Skip test only on qemu 2023-11-07 11:06:13 +09:00
Rui Ueyama ef9a610dd1 Run tests as long as qemu and cross compiler are available
So that we can catch bugs like e11d64b5c7
earlier.
2023-11-07 11:00:26 +09:00
Rui Ueyama 31c3b5397b [POWER10] Emit dynamic relocation for ifunc
Previously, we didn't emit a dynamic relocation for a statically-
initialized function pointer pointing to an ifunc on POWER10, e.g.

  int fn() __attribute__((ifunc("resolve_fn")));
  void *ptr = fn;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://github.com/rui314/mold/issues/947
2023-01-07 10:23:07 +08:00
2177 changed files with 222717 additions and 62223 deletions

No files matched your search

-10
View File
@@ -1,10 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
+93
View File
@@ -0,0 +1,93 @@
name: Build all tarballs
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
check-changes:
runs-on: ubuntu-24.04
outputs:
has_changes: ${{ github.event_name == 'workflow_dispatch' || steps.check.outputs.has_changes }}
steps:
- name: Check for new commits since last successful run
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
last=$(gh run list -R ${{ github.repository }} -w build-all.yml -b main -s success -L1 --json createdAt -q '.[0].createdAt')
if [ -z "$last" ] || [ "$last" = "null" ] || [ "$(gh api repos/${{ github.repository }}/commits?sha=main\&since=$last\&per_page=1 | jq length)" -gt 0 ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
build-linux:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
build-windows:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
cmake --install . --config Release --prefix ../mold-install
cd ..
New-Item -ItemType Directory -Force dist | Out-Null
$version = $Env:TAG -replace '^v', ''
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
+44
View File
@@ -0,0 +1,44 @@
name: Build native tarballs
on:
push:
workflow_dispatch:
jobs:
build-tarballs:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
runs-on: ${{ matrix.os }}
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
+133 -75
View File
@@ -1,114 +1,172 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
jobs:
build-clang:
build-asan-tsan:
strategy:
matrix:
target:
# Disable PCH for the default configuration. This prevents relying on implicit includes.
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
- ''
- '-DMOLD_USE_ASAN=On'
- '-DMOLD_USE_TSAN=On'
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- uses: rui314/setup-mold@staging
- name: install-build-deps
run: sudo ./install-build-deps.sh update
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: build and test
- run: sudo ./install-build-deps.sh
- name: build
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
mkdir build
cd build
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ matrix.target }} ..
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
cmake --build . -j$(nproc)
ctest . -j$(nproc)
build-gcc:
runs-on: ubuntu-20.04
container: gcc:11.1.0
steps:
- uses: actions/checkout@v3
- name: install-build-deps
run: |
# Install cross toolchains
dpkg --add-architecture i386
./install-build-deps.sh update
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
ln -sf /usr/bin/$i-linux-gnu-g++-10 /usr/bin/$i-linux-gnu-g++
done
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
# Install a RV32 toolchain from third party since it's not available
# as an Ubuntu package.
mkdir /usr/local/rv32
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.08.26/riscv32-glibc-ubuntu-20.04-nightly-2022.08.26-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-objdump /usr/bin/riscv32-linux-gnu-objdump
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
chmod 755 /usr/bin/riscv32-linux-gnu-gcc /usr/bin/riscv32-linux-gnu-g++
shell: bash
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: build and test
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
mkdir build
cd build
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On ..
cmake --build . -j$(nproc)
ctest . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v3
if: always()
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results
name: test-results-clang
path: |
build/out
build/Testing
build
!build/CMakeFiles
build-macos:
runs-on: macos-11
build-msan:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: .github/workflows/run-msan.sh
build-multi-archs:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- name: install-build-deps
run: |
sudo ./install-build-deps.sh
sudo ./install-cross-tools.sh
sudo .github/workflows/install-extras.sh
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-multi-archs
path: |
build
!build/CMakeFiles
build-distros:
strategy:
matrix:
target:
# Disable PCH for the default configuration. This prevents relying on implicit includes.
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
- '-DMOLD_USE_ASAN=On'
distro:
- alpine
- archlinux
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
- fedora
- gentoo/stage3
- opensuse/tumbleweed
- ubuntu:22.04
- ubuntu:25.04
runs-on: ubuntu-latest
timeout-minutes: 60
container: ${{ matrix.distro }}
steps:
- uses: actions/checkout@v3
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: build and test
- uses: actions/checkout@v6
- run: ./install-build-deps.sh
- name: build
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
mkdir build
cd build
cmake ${{ matrix.target }} ..
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: sanitize distro name
if: failure()
id: sanitize
run: echo "name=$(echo '${{ matrix.distro }}' | tr '/:' '--')" >> "$GITHUB_OUTPUT"
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-${{ steps.sanitize.outputs.name }}
path: |
build
!build/CMakeFiles
build-macos:
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(sysctl -n hw.physicalcpu)
build-windows:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: build
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
build-msys:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
- name: build
shell: msys2 {0}
run: |
mkdir build
cd build
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
cmake --build . -j $(nproc)
build-freebsd:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- name: Build and test
uses: vmactions/freebsd-vm@v1
with:
usesh: true
run: |
./install-build-deps.sh
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
ctest --output-on-failure -j$(nproc)
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash -x
apt-get update
apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.06.13/riscv32-glibc-ubuntu-24.04-gcc-nightly-2025.06.13-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
echo '/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
done
# Install a LoongArch toolchain
mkdir /larch
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_15.1.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
for i in gcc g++ objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
# Install ARM32 big-endian toolchain
mkdir /armeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
done
# Install ARM64 big-endian toolchain
mkdir /aarch64be
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
chmod 755 /usr/bin/sh4aeb-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /sh4aeb/bin/sh4aeb-linux-$i /usr/bin/sh4aeb-linux-gnu-$i
done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
wget -O- --progress=dot:mega https://downloadmirror.intel.com/850782/sde-external-9.53.0-2025-03-16-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
ln -s /sde/sde64 /usr/bin
+104
View File
@@ -0,0 +1,104 @@
name: Build & attach tarballs on tag push
on:
push:
tags: ['v[0-9]*']
permissions:
contents: write
packages: write
id-token: write
env:
TAG: ${{ github.ref_name }}
jobs:
build-linux:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
cmake --install . --config Release --prefix ../mold-install
cd ..
New-Item -ItemType Directory -Force dist | Out-Null
$version = $Env:TAG -replace '^v', ''
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-x86_64-windows.zip
- uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
publish:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
path: dist
pattern: '*'
merge-multiple: true
- name: Ensure release exists (create if missing)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
if gh release view "$TAG" >& /dev/null; then
echo "Existing release found for $TAG"
else
echo "Creating draft release for $TAG"
gh release create "$TAG" --draft \
--title "$TAG" \
--notes "Automated draft – fill the changelog later."
fi
- name: Upload tarballs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$TAG" dist/* --clobber
gh release view "$TAG" --json assets --jq '.assets[].name'
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
set -e -x
cd "$(dirname $0)"/../..
if [ "$GITHUB_REPOSITORY" = '' ]; then
image=mold-msan
image_build="podman build -t $image -"
else
# If this script is running on GitHub Actions, we want to cache
# the created container image in GitHub's container repostiory.
image=ghcr.io/$GITHUB_REPOSITORY/mold-msan
image_build="podman build -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
fi
cat <<EOF | $image_build
FROM mirror.gcr.io/library/ubuntu:24.04
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc g++ wget ca-certificates make cmake ninja-build python3 clang-20 llvm-20-dev libclang-20-dev libclang-rt-20-dev libtbb-dev mold && \
rm -rf /var/lib/apt/lists
RUN mkdir /llvm && \
cd /llvm && \
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-21.1.1.tar.gz | tar xzf - --strip-components=1 && \
cmake -S ./runtimes -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi;libunwind' -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DLLVM_USE_SANITIZER=MemoryWithOrigins && \
cmake --build build -j\$(nproc) && \
cmake --install build --prefix /msan && \
rm -rf /llvm
EOF
# Build mold and run its tests
podman run -it --rm --pull=never -v "$(pwd):/mold:ro" --security-opt seccomp=unconfined $image bash -c "
set -e
mkdir /build
cd /build
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DMOLD_USE_MSAN=ON -DMOLD_STDLIB_PREFIX=/msan -DMOLD_USE_MIMALLOC=0 -DMOLD_USE_SYSTEM_TBB=1 -DMOLD_USE_MOLD=1 /mold
cmake --build . -j\$(nproc)
setarch -R ctest --output-on-failure -j\$(nproc)
"
+33
View File
@@ -0,0 +1,33 @@
name: Update manpage
on:
push:
paths:
- 'docs/mold.md'
branches:
- main
workflow_dispatch:
jobs:
update-manpage:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Install ronn
run: sudo apt-get update && sudo apt-get install -y ronn
- name: Generate mold.1 from mold.md
run: ronn --roff docs/mold.md
- name: Configure Git
run: |
git config --global user.name 'Rui Ueyama'
git config --global user.email '[email protected]'
- name: Commit and push if mold.1 is updated
run: |
git add docs/mold.1
git diff --staged --quiet || (git commit -m "Update mold.1 (automated commit)" && git push)
+2 -8
View File
@@ -3,15 +3,9 @@
*~
**/.DS_Store
.cache
compile_commands.json
/mold
/test/tmp
options.inc
core
ld
ld64
ld64.mold
gentoo
out
/mold-*.tar.gz
/build*
/mold
/dist
+213 -174
View File
@@ -1,31 +1,31 @@
# You can customize a build by specifying CMake options. An option may be
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
# means true while `OFF` or `0` means false.
# means true, while `OFF` or `0` means false.
#
# Here are a couple of common cmake options:
#
# -DCMAKE_C_COMPILER=<command-name>
#
# Specifies a C compiler name to use. The default value is `cc`.
# Specifies the C compiler name to use. The default value is `cc`.
#
# -DCMAKE_CXX_COMPILER=<command-name>
#
# Specifies a C++ compiler name to use. The default value is `c++`.
# Specifies the C++ compiler name to use. The default value is `c++`.
#
# -DCMAKE_INSTALL_PREFIX=<directory>
#
# Specifies an install target directory. The default value is `/usr/local`.
# Specifies the install target directory. The default value is `/usr/local`.
#
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
#
# Specifies a build type. The default is `Release` which is the right
# Specifies the build type. The default is `Release`, which is the right
# option unless you are debugging mold.
#
# An example of a cmake command line is shown below:
#
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
#
# where `..` refers this directory.
# where `..` refers to this directory.
#
# With cmake, you may run `cmake --install .` instead of `make install` to
# install build artifacts to system directories. If you want to install
@@ -39,33 +39,62 @@
# how to build mold. However, as a policy, we do not provide a way to
# enable/disable any individual mold's feature. In other words, we do not
# provide options like `--enable-foo` or `--disable-foo`. The motivation
# behind it is build reproducibility. We want to guarantees that all builds
# of the mold linker of the same version will have the exactly same set of
# behind this is build reproducibility. We want to guarantee that all builds
# of the mold linker of the same version will have the exact same set of
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.13)
project(mold VERSION 1.9.0)
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.41.0)
include(CMakeDependentOption)
include(CheckSymbolExists)
include(GNUInstallDirs)
add_executable(mold)
target_compile_features(mold PRIVATE cxx_std_20)
if(MINGW)
target_link_libraries(mold PRIVATE dl)
else()
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
endif()
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
if(MOLD_USE_MOLD)
add_link_options(-fuse-ld=mold)
endif()
target_link_options(mold PRIVATE -fuse-ld=mold)
add_executable(mold)
target_compile_features(mold PRIVATE cxx_std_20)
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
if(CMAKE_BUILD_TYPE MATCHES "Deb")
target_link_options(mold PRIVATE -Wl,--gdb-index)
endif()
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
endif()
endif()
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
target_compile_options(mold PRIVATE
-fno-exceptions
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-ffunction-sections
-fdata-sections
-Wall
-Wextra
-Wno-sign-compare
-Wno-unused-function)
-Wno-unused-function
-Wno-unused-parameter
-Wno-missing-field-initializers
-ggnu-pubnames)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(OPENBSD ON)
endif()
# Build mold with -flto if -DMOLD_LTO=ON
@@ -88,44 +117,76 @@ if(MOLD_USE_TSAN)
target_link_options(mold PRIVATE -fsanitize=thread)
endif()
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
# Enable MemorySanitizer if -DMOLD_USE_MSAN=ON
option(MOLD_USE_MSAN "Build mold with MemorySanitizer" OFF)
if(MOLD_USE_MSAN)
set(MOLD_STDLIB_PREFIX /usr CACHE FILEPATH "prefix of instrumented stdlib")
target_compile_options(mold PRIVATE
-g -Og -fsanitize=memory -fsanitize-memory-track-origins
-nostdinc++ -isystem ${MOLD_STDLIB_PREFIX}/include/c++/v1
-DENABLE_MSAN_UNPOISON
)
target_link_options(mold PRIVATE
-fsanitize=memory
-nostdlib++ -L ${MOLD_STDLIB_PREFIX}/lib -lc++
-Wl,-rpath,${MOLD_STDLIB_PREFIX}/lib
)
endif()
# 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
# need nor want to set this to ON.
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" 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()
set(ZLIB_BUILD_EXAMPLES OFF CACHE INTERNAL "")
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
target_include_directories(zlibstatic INTERFACE third-party/zlib
$<TARGET_PROPERTY:zlibstatic,BINARY_DIR>)
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()
function(mold_add_blake3)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
target_link_libraries(mold PRIVATE blake3)
target_include_directories(mold PUBLIC third-party/blake3/c)
endfunction()
mold_add_blake3()
endif()
# Find zstd compression library. If zstd.h 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()
set(ZSTD_BUILD_PROGRAMS OFF)
set(ZSTD_BUILD_CONTRIB OFF)
set(ZSTD_BUILD_TESTS OFF)
set(ZSTD_MULTITHREAD_SUPPORT OFF)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_BUILD_STATIC ON)
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
target_compile_definitions(libzstd_static PRIVATE
ZSTD_BUILD_STATIC=1
ZSTD_BUILD_SHARED=0
ZSTD_BUILD_PROGRAMS=0
ZSTD_MULTITHREAD_SUPPORT=0
ZSTD_BUILD_TESTS=0)
target_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
@@ -137,12 +198,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; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
MOLD_USE_MIMALLOC OFF)
@@ -154,11 +213,11 @@ if(MOLD_USE_MIMALLOC)
if(MOLD_USE_SYSTEM_MIMALLOC)
find_package(mimalloc REQUIRED)
target_link_libraries(mold PRIVATE mimalloc)
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
else()
function(mold_add_mimalloc)
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
set(MI_NO_OPT_ARCH ON CACHE INTERNAL "")
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
target_link_libraries(mold PRIVATE mimalloc-static)
@@ -175,7 +234,7 @@ endif()
# to mold. If you want to link to the system's libtbb2.so, pass
# -DMOLD_USE_SYSTEM_TBB=ON.
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
if(MOLD_USE_SYSTEM_TBB)
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
find_package(TBB REQUIRED)
target_link_libraries(mold PRIVATE TBB::tbb)
else()
@@ -191,19 +250,18 @@ else()
mold_add_tbb()
endif()
# Check if this is a commercial version of mold (a.k.a. "sold")
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.md")
set(MOLD_IS_SOLD ON)
endif()
# We always use Clang to build mold on Windows. MSVC can't compile mold.
if(WIN32)
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
endif()
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
if(MINGW)
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
target_link_libraries(mold PRIVATE bcrypt)
endif()
else()
include(CheckLibraryExists)
check_library_exists(m pow "" LIBM_FOUND)
@@ -220,12 +278,7 @@ if(NOT APPLE AND NOT WIN32)
# Remove the default `lib` prefix
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
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)
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
endif()
# If atomics doesn't work by default, add -latomic.
@@ -245,27 +298,26 @@ if(NOT HAVE_FULL_ATOMIC_SUPPORT)
endif()
# Add -pthread
if(NOT APPLE AND NOT WIN32)
if(NOT APPLE AND NOT MSVC)
target_compile_options(mold PRIVATE -pthread)
target_link_options(mold PRIVATE -pthread)
endif()
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
# Create a .cc file containing the current git hash for `mold --version`.
add_custom_target(git_hash
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
DEPENDS update-git-hash.cmake
BYPRODUCTS git-hash.cc
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS mold-git-hash.h
VERBATIM)
add_dependencies(mold git_hash)
# Create config.h file
configure_file(config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
# (e.g. X86_64) as its type parameter. Since we suport more than 10
# targets, compiling a single source file for all the targets is very
@@ -274,124 +326,131 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# As a workaround, we create a .cc file for each target and spawn many
# compiler instances. This is hacky but greatly reduces compile time
# on a multicore machine.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE
PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4 ALPHA)
#
# You can build mold for a specific set of targets by passing, e.g.,
# -DMOLD_TARGETS='X86_64;ARM64LE', though this is strongly discouraged
# for build reproducibility. Use this option only if you build mold
# frequently for your personal use; otherwise, always build mold with all
# targets enabled. We provide this flag in the trust that you will not
# abuse it, so please don’t betray that trust.
set(MOLD_TARGETS
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
LOONGARCH32 LOONGARCH64
CACHE STRING "List of supported targets")
list(APPEND MOLD_ELF_TEMPLATE_FILES
elf/cmdline.cc
elf/dwarf.cc
elf/gc-sections.cc
elf/icf.cc
elf/input-files.cc
elf/input-sections.cc
elf/linker-script.cc
elf/lto.cc
elf/main.cc
elf/mapfile.cc
elf/output-chunks.cc
elf/passes.cc
elf/relocatable.cc
elf/subprocess.cc
elf/thunks.cc
list(GET MOLD_TARGETS 0 MOLD_FIRST_TARGET)
list(APPEND MOLD_TEMPLATE_FILES
src/arch-arm32.cc
src/arch-arm64.cc
src/arch-i386.cc
src/arch-loongarch.cc
src/arch-m68k.cc
src/arch-ppc32.cc
src/arch-ppc64v1.cc
src/arch-ppc64v2.cc
src/arch-riscv.cc
src/arch-s390x.cc
src/arch-sh4.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/archive-file.cc
src/cmdline.cc
src/error.cc
src/filetype.cc
src/gc-sections.cc
src/gdb-index.cc
src/icf.cc
src/input-files.cc
src/input-sections.cc
src/linker-script.cc
src/main.cc
src/mapfile.cc
src/output-chunks.cc
src/passes.cc
src/relocatable.cc
src/shrink-sections.cc
src/thunks.cc
src/tls.cc
)
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
if(WIN32 AND NOT MINGW)
list(APPEND MOLD_TEMPLATE_FILES src/lto-win32.cc)
else()
list(APPEND MOLD_TEMPLATE_FILES src/lto-unix.cc)
endif()
list(APPEND MOLD_MACHO_TEMPLATE_FILES
macho/cmdline.cc
macho/dead-strip.cc
macho/input-files.cc
macho/input-sections.cc
macho/lto.cc
macho/main.cc
macho/mapfile.cc
macho/output-chunks.cc
macho/tapi.cc
if(WIN32)
list(APPEND MOLD_TEMPLATE_FILES
src/output-file-win32.cc
src/subprocess-win32.cc
)
else()
list(APPEND MOLD_TEMPLATE_FILES
src/output-file-unix.cc
src/subprocess-unix.cc
)
endif()
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()
foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
foreach (SOURCE IN LISTS MOLD_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_TARGETS)
mold_instantiate_templates(${SOURCE} ${TARGET})
set(HAVE_TARGET_${TARGET} 1)
endforeach()
endforeach()
if(MOLD_IS_SOLD)
foreach (SOURCE IN LISTS MOLD_MACHO_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_MACHO_TARGETS)
mold_instantiate_templates(${SOURCE} ${TARGET})
endforeach()
endforeach()
endif()
# Add other non-template source files.
target_sources(mold PRIVATE
compress.cc
demangle.cc
elf/arch-alpha.cc
elf/arch-arm32.cc
elf/arch-arm64.cc
elf/arch-i386.cc
elf/arch-m68k.cc
elf/arch-ppc32.cc
elf/arch-ppc64v1.cc
elf/arch-ppc64v2.cc
elf/arch-riscv.cc
elf/arch-s390x.cc
elf/arch-sh4.cc
elf/arch-sparc64.cc
elf/arch-x86-64.cc
elf/elf.cc
filepath.cc
git-hash.cc
glob.cc
hyperloglog.cc
main.cc
multi-glob.cc
perf.cc
tar.cc
lib/aho-corasick.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/elf.cc
src/entry.cc
third-party/rust-demangle/rust-demangle.c
uuid.cc
)
if(MOLD_IS_SOLD)
if(WIN32)
target_sources(mold PRIVATE
macho/arch-arm64.cc
macho/arch-x86-64.cc
macho/yaml.cc
src/jobs-win32.cc
src/mapped-file-win32.cc
src/signal-win32.cc
)
else()
target_sources(mold PRIVATE
src/jobs-unix.cc
src/mapped-file-unix.cc
src/signal-unix.cc
)
endif()
# Add frequently included header files for pre-compiling.
# target_precompile_headers is supported by CMake 3.16.0 or newer.
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
if(MOLD_IS_SOLD)
target_precompile_headers(mold PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>"
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/macho/mold.h>")
else()
target_precompile_headers(mold PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>")
endif()
# ccache needs this flag along with `sloppiness = pch_defines,time_macros`
# to enable caching
target_compile_options(mold PRIVATE -fpch-preprocess)
endif()
# Create config.h file
configure_file(lib/config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Test configs
include(CTest)
if(BUILD_TESTING)
# Create the ld and ld64 symlinks required for testing
# Create the ld symlinks required for testing
if(NOT WIN32)
add_custom_command(
TARGET mold POST_BUILD
@@ -399,30 +458,17 @@ if(BUILD_TESTING)
BYPRODUCTS ld
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM)
if(MOLD_IS_SOLD)
add_custom_command(
TARGET mold POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
BYPRODUCTS ld64
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM)
endif()
endif()
if(${APPLE})
if(MOLD_IS_SOLD)
add_subdirectory(test/macho)
endif()
elseif(${UNIX})
add_subdirectory(test/elf)
if(${UNIX})
add_subdirectory(test)
endif()
endif()
if(NOT CMAKE_SKIP_INSTALL_RULES)
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
function(mold_install_relative_symlink OLD NEW)
install(CODE "
@@ -436,19 +482,12 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
endfunction()
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld.mold)
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
if(MOLD_IS_SOLD)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld64.mold)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld.sold)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld64.sold)
if(NOT WIN32)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
endif()
endif()
-15
View File
@@ -1,15 +0,0 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "clang_cl_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
}
]
}
-44
View File
@@ -1,44 +0,0 @@
# Contributing to mold
We request all patches to mold to be released under the dual license
of the GNU AGPLv3 and the MIT license. Please read the following and
sign-off your patch as an indication of agreement.
> ## Developer's Certificate of Origin
>
> By making a contribution to this project, I certify that:
>
> 1. The contribution was created in whole or in part by me and I
> have the right to submit it under the Dual License of the GNU
> AGPLv3 and the MIT license; or
>
> 2. The contribution is based upon previous work that, to the best
> of my knowledge, is covered under the the Dual License and I
> have the right under the Dual License to submit that work with
> modifications, whether created in whole or in part by me, under
> the Dual License (unless I am permitted to submit under
> different licenses), as indicated in the file; or
>
> 3. The contribution was provided directly to me by some other
> person who certified (1), (2) or (3) and I have not modified it.
>
> 4. I understand and agree that this project and the contribution
> are public and that a record of the contribution (including all
> personal information I submit with it, including my sign-off) is
> maintained indefinitely and may be redistributed consistent with
> this project or the open source license(s) involved.
If you agree, you just add a line saying:
```
Signed-off-by: Random J Developer <[email protected]>
```
using your real name (sorry, no pseudonyms or anonymous contributions.)
This will be done for you automatically if you use ``git commit -s``.
Reverts should also include "Signed-off-by". ``git revert -s`` does that
for you.
We assume that any contribution that's 10 lines or less does not meet
the threshold of originality and therefore copyright does not apply.
You don't need to add a "Signed-off-by" line to such small patches.
-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
+1 -1005
View File
File diff suppressed because it is too large. Load diff
+145 -193
View File
@@ -1,133 +1,117 @@
# 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)
mold is a faster drop-in replacement for existing Unix linkers. It is several
times quicker than the LLVM lld linker, the second-fastest open-source linker,
which I initially developed a few years ago. mold aims to enhance developer
productivity by minimizing build time, particularly in rapid
debug-edit-rebuild cycles.
<i>This is a repo of a free, AGPL-licensed version of the linker.
If you are looking for a commercial, non-AGPL version of the same linker,
please visit the
[repo of the sold linker](https://github.com/bluewhalesystems/sold).</i>
Here is a performance comparison of GNU ld, GNU gold, LLVM lld, and
mold when linking final debuginfo-enabled executables for major large
programs on a simulated 16-core, 32-thread machine.
mold is a faster drop-in replacement for existing Unix linkers.
It is several times faster than the LLVM lld linker, the second-fastest
open-source linker which I originally created a few years ago.
mold is designed to increase developer productivity by reducing
build time, especially in rapid debug-edit-rebuild cycles.
![Link speed comparison](docs/chart.svg)
Here is a performance comparison of GNU gold, LLVM lld, and mold for
linking final debuginfo-enabled executables of major large programs
on a simulated 8-core 16-threads machine.
| Program (linker output size) | GNU ld | GNU gold | LLVM lld | mold
|-------------------------------|--------|----------|----------|------
| MySQL 8.3 (0.47 GiB) | 10.84s | 7.47s | 1.64s | 0.46s
| Clang 19 (1.56 GiB) | 42.07s | 33.13s | 5.20s | 1.35s
| Chromium 124 (1.35 GiB) | N/A | 27.40s | 6.10s | 1.52s
![Link speed comparison](docs/comparison.png)
| Program (linker output size) | GNU gold | LLVM lld | mold
|-------------------------------|----------|----------|--------
| Chrome 96 (1.89 GiB) | 53.86s | 11.74s | 2.21s
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
mold is so fast that it is only 2x _slower_ than `cp` on the same
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
if you find mold is not faster than other linkers.
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
machine. If you find that mold is not faster than other linkers, feel
free to [file a bug report](https://github.com/rui314/mold/issues).
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit
little-endian PowerPC ELFv2, s390x, SPARC64, m68k, SH-4 and DEC Alpha.
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
mold/macOS is commercial software. For mold/macOS, please visit
https://github.com/bluewhalesystems/sold.
## Why does linking speed matter?
## Why does the speed of linking matter?
If you are using a compiled language such as C, C++, or Rust, a build consists
of two phases. In the first phase, a compiler compiles source files into
object files (`.o` files). In the second phase, a linker takes all object
files and combines them into a single executable or shared library file.
If you are using a compiled language such as C, C++ or Rust, a build
consists of two phases. In the first phase, a compiler compiles
source files into object files (`.o` files). In the second phase,
a linker takes all object files to combine them into a single executable
or a shared library file.
The second phase can be time-consuming if your build output is large. mold can
speed up this process, saving you time and preventing distractions while
waiting for a lengthy build to finish. The difference is most noticeable
during rapid debug-edit-rebuild cycles.
The second phase takes a long time if your build output is large.
mold can make it faster, saving your time and keeping you from being
distracted while waiting for a long build to finish. The difference is
most noticeable when you are in rapid debug-edit-rebuild cycles.
## Installation
## Install
Binary packages for the following systems are currently available.
Binary packages for the following systems are currently available:
[![Packaging status](https://repology.org/badge/vertical-allrepos/mold.svg)](https://repology.org/project/mold/versions)
## How to build
## How to Build
mold is written in C++20, so if you build mold yourself, you need a
recent version of a C++ compiler and a C++ standard library. GCC 10.2
or Clang 12.0.0 (or later) as well as libstdc++ 10 or libc++ 7 (or
later) are recommended.
mold is written in C++20, so if you build mold yourself, you will need a
recent version of a C++ compiler and a C++ standard library. We recommend GCC
10.2 or Clang 16.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
### Install dependencies
### Install Dependencies
To install build dependencies, run `./install-build-deps.sh` in this
directory. It recognizes your Linux distribution and tries to install
necessary packages. You may want to run it as root.
directory. It will detect your Linux distribution and attempt to install the
necessary packages. You may need to run it as root.
### Compile mold
```shell
git clone https://github.com/rui314/mold.git
mkdir mold/build
cd mold/build
git checkout v1.9.0
../install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
cmake --build . -j $(nproc)
sudo cmake --install .
git clone --branch stable https://github.com/rui314/mold.git
cd mold
./install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
cmake --build build -j$(nproc)
sudo cmake --build build --target install
```
You may need to pass a C++20 compiler command name to `cmake`.
In the above case, `c++` is passed. If it doesn't work for you,
try a specific version of a compiler such as `g++-10` or `clang++-12`.
You might need to pass a C++20 compiler command name to `cmake`. In the
example above, `c++` is passed. If that doesn't work for you, try a specific
version of a compiler, such as `g++-10` or `clang++-12`.
By default, `mold` is installed to `/usr/local/bin`. You can change
that by passing `-DCMAKE_INSTALL_PREFIX=<directory>`. For other cmake
options, see the comments in `CMakeLists.txt`.
By default, `mold` is installed to `/usr/local/bin`. You can change the
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
For other cmake options, see the comments in `CMakeLists.txt`.
If you don't use a recent enough Linux distribution, or if for any reason
`cmake` in the above commands doesn't work for you, you can use Docker to
build it in a Docker environment. To do so, just run `./dist.sh` in this
directory instead of `cmake`. The shell script pulls a Docker image,
builds mold and auxiliary files inside it, and packs them into a
single tar file `mold-$version-$arch-linux.tar.gz`. You can extract
the tar file anywhere and use `mold` executable in it.
If you are not using a recent enough Linux distribution, or if `cmake` does
not work for you for any reason, you can use Podman to build mold in a
container. To do so, run `./dist.sh` in this directory instead of using
`cmake`. The shell script will pull a container image, build mold and auxiliary
files inside it, and package them into a single tar file named
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
and use the mold executable in it.
## How to use
<details><summary>A classic way to use mold</summary>
On Unix, the linker command (which is usually `/usr/bin/ld`) is
invoked indirectly by the compiler driver (which is usually `cc`,
`gcc` or `clang`), which is typically in turn indirectly invoked by
`make` or some other build system command.
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
indirectly invoked by `make` or other build system commands.
If you can specify an additional command line option to your compiler
driver by modifying build system's config files, add one of the
following flags to use `mold` instead of `/usr/bin/ld`:
If you can specify an additional command line option for your compiler driver
by modifying the build system's config files, add one of the following flags
to use mold instead of `/usr/bin/ld`:
- Clang: pass `-fuse-ld=mold`
- For Clang: pass `-fuse-ld=mold`
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
argument, so you need to use `-B` option instead. `-B` is an option
to tell GCC where to look for external commands such as `ld`.
- For GCC before 12.1.0: the `-fuse-ld` option does not accept `mold` as a
valid argument, so you need to use the `-B` option instead. The `-B` option
tells GCC where to look for external commands like `ld`.
If you have installed mold with `make install`, there should be a
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
depending on your `$PREFIX`), and `ld` command should be there. The
`ld` is actually a symlink to `mold`. So, all you need is to pass
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
If you have installed mold with `make install`, there should be a directory
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
`-B/usr/local/libexec/mold`) to GCC.
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
take an absolute path as an argument for `-fuse-ld` though.
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
accept an absolute path as an argument for `-fuse-ld`.
</details>
@@ -136,37 +120,24 @@ take an absolute path as an argument for `-fuse-ld` though.
Create `.cargo/config.toml` in your project directory with the following:
```toml
[target.x86_64-unknown-linux-gnu]
[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
where `/path/to/mold` is an absolute path to `mold` exectuable. In the
above example, we use `clang` as a linker driver as it can always take
the `-fuse-ld` option. If your GCC is recent enough to recognize the
option, you may be able to remove the `linker = "clang"` line.
where `/path/to/mold` is an absolute path to the mold executable. In the
example above, we use `clang` as a linker driver since it always accepts the
`-fuse-ld` option. If your GCC is recent enough to recognize the option, you
may be able to remove the `linker = "clang"` line.
```toml
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
If you want to use mold for all projects, put the above snippet to
If you want to use mold for all projects, add the above snippet to
`~/.cargo/config.toml`.
If you are using macOS, you can modify `config.toml` in a similar manner.
Here is an example with `mold` installed via [Homebrew](https://brew.sh).
```toml
[target.x86_64-apple-darwin]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.aarch64-apple-darwin]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
</details>
<details><summary>If you are using Nim</summary>
@@ -178,67 +149,54 @@ when findExe("mold").len > 0 and defined(linux):
switch("passL", "-fuse-ld=mold")
```
where `mold` must be included in the PATH environment variable. In this example
The above example uses `gcc` as the linker driver.
Use the `fuse-ld` option. If your GCC is recent enough to recognize this option.
where `mold` must be included in the `PATH` environment variable. In this
example, `gcc` is used as the linker driver. Use the `-fuse-ld` option if your
GCC is recent enough to recognize this option.
If you want to use mold for all projects, put the above snippet to `~/.config/config.nims`.
If you want to use mold for all projects, add the above snippet to
`~/.config/config.nims`.
If you are using macOS, you can modify config.nims in a similar manner. Here is an example with mold installed via Homebrew.
</details>
```nim
when findExe("ld64.mold").len > 0 and defined(macosx):
switch("passL", "-fuse-ld=ld64.mold")
```
<details><summary>If you are using Conan package manager</summary>
You can configure [Conan](https://github.com/conan-io) to download the latest
version of `mold` and use it as the linker when building your dependencies and
projects from source. Please see the instructions [here](https://conan.io/center/recipes/mold).
</details>
<details><summary>mold -run</summary>
It is sometimes very hard to pass an appropriate command line option
to `cc` to specify an alternative linker. To deal with the situation,
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
`ld.gold` and redirect it to itself. To use the feature, run `make`
(or another build command) as a subcommand of mold as follows:
It is sometimes very hard to pass an appropriate command line option to `cc`
to specify an alternative linker. To address this situation, mold has a
feature to intercept all invocations of `ld`, `ld.bfd`, `ld.lld`, or `ld.gold`
and redirect them to itself. To use this feature, run `make` (or another build
command) as a subcommand of mold as follows:
```shell
mold -run make <make-options-if-any>
```
Internally, mold invokes a given command with `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object
file intercepts all function calls to `exec(3)`-family functions to
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
</details>
<details><summary>On macOS</summary>
mold/macOS is available as an alpha version. It can be used to build not
only macOS apps but also iOS apps because their binary formats are the same.
The command name of mold/macOS is `ld64.mold`. If you build mold on macOS,
it still produces `mold` and `ld.mold`, but these executables are useful
only for cross compilation (i.e. building Linux apps on macOS.)
If you find any issue with mold/macOS, please file it to
<a href=https://github.com/rui314/mold/issues>our GitHub Issues</a>.
Internally, mold invokes a given command with the `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object file
intercepts all function calls to `exec(3)`-family functions to replace
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
</details>
<details><summary>GitHub Actions</summary>
You can use our <a href=https://github.com/rui314/setup-mold>setup-mold</a>
GitHub Action to speed up GitHub-hosted continuous build. GitHub Actions
runs on a two-core machine, but mold is still significantly faster than
the default GNU linker there especially when a program being linked is
large.
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
run on a 4 core machine, mold is still significantly faster than the default
GNU linker, especially when linking large programs.
</details>
<details><summary>Verify that you are using mold</summary>
mold leaves its identification string in `.comment` section in an output
mold leaves its identification string in the `.comment` section of an output
file. You can print it out to verify that you are actually using mold.
```shell
@@ -249,75 +207,69 @@ String dump of section '.comment':
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
```
If `mold` is in `.comment`, the file is created by mold.
If `mold` is present in the `.comment` section, the file was created by mold.
</details>
<details><summary>Online manual</summary>
Since mold is a drop-in replacement, you should be able to use it
without reading its manual. But just in case you need it, it's available
online at <a href=https://rui314.github.io/mold.html>here</a>.
You can also read the same manual by `man mold`.
Since mold is a drop-in replacement, you should be able to use it without
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
is available online. You can read the same manual by running `man mold`.
</details>
## Why is mold so fast?
One reason is because it simply uses faster algorithms and efficient
data structures than other linkers do. The other reason is that the
new linker is highly parallelized.
One reason is that it utilizes faster algorithms and more efficient data
structures compared to other linkers. Another reason is that mold is highly
parallelized.
Here is a side-by-side comparison of per-core CPU usage of lld (left)
and mold (right). They are linking the same program, Chromium
executable.
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
mold (right), linking the same program, a Chromium executable.
![CPU usage comparison in htop animation](docs/htop.gif)
As you can see, mold uses all available cores throughout its execution
and finishes quickly. On the other hand, lld failed to use available
cores most of the time. In this demo, the maximum parallelism is
artificially capped to 16 so that the bars fit in the GIF.
As you can see, mold uses all available cores throughout its execution and
finishes quickly. In contrast, lld fails to utilize available cores most of
the time. In this demo, the maximum parallelism is artificially capped at 16,
so that the bars fit in the GIF.
For details, please read [design notes](docs/design.md).
## License
mold is available under AGPL. Note that that does not mean that you
have to license your program under AGPL if you use mold to link your
program. An output of the mold linker is a derived work of the object
files and libraries you pass to the linker but not a derived work of
the mold linker itself.
Besides that, you can also buy a commercial, non-AGPL license with
technical support from our company, Blue Whale Systems PTE LTD. If you
are a big company, please consider obtaining it before making hundreds
or thousands of developers of your company to depend on mold. mold is
mostly a single-person open-source project, and just like other
open-source projects, we are not legally obligated to keep maintaining
it. A legally-binding commercial license contract addresses the
concern. By purchasing a license, you are guaranteed that mold will be
maintained for you. Please [contact us](mailto:[email protected])
for a commercial license inquiry.
For details, please see the [design notes](docs/design.md).
## Sponsors
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
and [OpenCollective](https://opencollective.com/mold-linker).
We thank you to everybody who sponsors our project. In particular,
we'd like to acknowledge the following people and organizations who
have sponsored $128/month or more:
It is taken for granted nowadays that compiler toolchains can be easily
installed and used for free, and people may not think too much about the
individuals behind these "free tools". mold supports many projects, but it
is essentially a one-person project. This situation is similar to the one
depicted in the following xkcd illustration.
[![xkcd 2347](https://imgs.xkcd.com/comics/dependency.png)](https://xkcd.com/2347)
If you think that the "Nebraska guy" should be rewarded, please consider
becoming our [GitHub sponsor](https://github.com/sponsors/rui314)!
We thank everyone who sponsors our project. In particular, we'd like to acknowledge
the following people and organizations who have sponsored $128/month or more:
### Corporate sponsors
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
<a href="https://mercury.com"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
- [Uber](https://uber.com)
<a href="https://cybozu-global.com"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
- [G-Research](https://www.gresearch.co.uk)
- [Signal Slot Inc.](https://github.com/signal-slot)
- [GlareDB](https://github.com/GlareDB)
### Individual sponsors
- [300baud](https://github.com/300baud)
- [Johan Andersson](https://github.com/repi)
- [Wei Wu](https://github.com/lazyparser)
- [kyle-elliott](https://github.com/kyle-elliott)
- [Bryant Biggs](https://github.com/bryantbiggs)
- [kraptor23](https://github.com/kraptor23)
- [Jinkyu Yi](https://github.com/jincreator)
- [Pedro Navarro](https://github.com/pedronavf)
-90
View File
@@ -1,90 +0,0 @@
#pragma once
#include "mold.h"
namespace mold {
template <typename C>
std::vector<std::string_view>
read_response_file(C &ctx, std::string_view path) {
std::vector<std::string_view> vec;
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
u8 *data = mf->data;
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;
}
vec.push_back(save_string(ctx, buf));
return i;
};
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, '\"');
else
i = read_unquoted(i);
}
return vec;
}
// Replace "@path/to/some/text/file" with its file contents.
template <typename C>
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
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));
else
vec.push_back(argv[i]);
}
return vec;
}
static inline std::string_view string_trim(std::string_view str) {
size_t pos = str.find_first_not_of(" \t");
if (pos == str.npos)
return "";
str = str.substr(pos);
pos = str.find_last_not_of(" \t");
if (pos == str.npos)
return str;
return str.substr(0, pos + 1);
}
} // namespace mold
-3
View File
@@ -1,3 +0,0 @@
#define MOLD_VERSION "@mold_VERSION@"
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
#cmakedefine01 MOLD_IS_SOLD
-51
View File
@@ -1,51 +0,0 @@
#include "mold.h"
#include <cstdlib>
#ifndef _WIN32
#include <cxxabi.h>
#endif
#include "third-party/rust-demangle/rust-demangle.h"
namespace mold {
std::string_view demangle(std::string_view name) {
static thread_local char *p;
if (p)
free(p);
// Try to demangle as a Rust symbol. Since legacy-style Rust symbols
// are also valid as a C++ mangled name, we need to call this before
// cpp_demangle.
p = rust_demangle(std::string(name).c_str(), 0);
if (p)
return p;
// Try to demangle as a C++ symbol.
if (std::optional<std::string_view> s = cpp_demangle(name))
return *s;
return name;
}
std::optional<std::string_view> cpp_demangle(std::string_view name) {
static thread_local char *buf;
static thread_local size_t buflen;
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
#ifndef _WIN32
if (name.starts_with("_Z")) {
int status;
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
if (status == 0) {
buf = p;
return p;
}
}
#endif
return {};
}
} // namespace mold
+238 -24
View File
@@ -1,33 +1,247 @@
#!/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`).
#
# The mold executable created by this script is statically-linked to
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
# and librt, as they almost always exist on any Linux systems.
# This script creates a mold binary distribution. The output is written to
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
# `mold-2.40.0-x86_64-linux.tar.gz`).
#
# This script aims to produce reproducible outputs. That means each time
# it's run on the same git commit, it generates a bit-for-bit identical
# binary file regardless of when or where it's executed. This property
# serves as a strong safeguard against supply chain attacks. With a
# reproducible build, anyone can independently verify that the binary
# files published on our GitHub release page were built from the git
# commit tagged for release by rebuilding the binaries themselves.
#
# Debian provides snapshot.debian.org to host all historical binary
# packages. We use it to construct a container image pinned to a
# particular timestamp. snapshot.debian.org is known to be very slow,
# but that shouldn't be a big problem for us because we only need that
# site the first time.
#
# The mold executable created by this script is statically linked to
# libstdc++, but dynamically linked to glibc, libm and a few other
# libraries, as these libraries are almost always available on any Linux
# system. We can't statically link glibc because doing so would disable
# dlopen(), which is required to load the LTO linker plugin.
#
# We use a reasonably old Debian version for the build environment because
# a binary dynamically linked against a newer version of glibc won't work
# on a system with an older version of glibc.
#
# We prefer to build mold with Clang rather than GCC because mold's
# Identical Code Folding works best with the LLVM address significance
# table (.llvm_addrsig). Building a release binary with GCC produces a
# slightly larger binary than with Clang.
#
# We need a recent version of Clang to build mold. If it's not available
# via apt-get, we'll build it ourselves.
#
# This script can be used to create non-native binaries (e.g., building
# aarch64 binary on x86-64) because Podman automatically runs everything
# under QEMU if the container image is not native. To use this script for
# non-native builds, you may need to install the qemu-user-static package.
if [ $# -ne 1 ]; then
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
set -e -x
cd "$(dirname $0)"
usage() {
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
exit 1
}
case $# in
0)
arch=$(uname -m)
if [ $arch = arm64 ]; then
arch=aarch64
elif [[ $arch = arm* ]]; then
arch=arm
fi
;;
1)
arch="$1"
;;
*)
usage
esac
# Create a Podman image.
if [ "$GITHUB_REPOSITORY" = '' ]; then
image=mold-builder-$arch
image_build="podman build --arch $arch -t $image -"
else
# If this script is running on GitHub Actions, we want to cache
# the created container image in GitHub's container repostiory.
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
fi
arch=$1
echo $arch | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
{ echo "Error: no docker image for $arch"; exit 1; }
case $arch in
x86_64)
# Debian 9 (Stretch) released in June 2017.
#
# We use a Google-provided mirror (gcr.io) instead of the official Docker
# Hub (docker.io) because docker.io has a strict rate limit policy.
#
# The toolchain in Debian 9 is too old to build mold, so we rebuild it
# from source. We download source archives from official sites and build
# them locally, rather than downloading pre-built binaries from somewhere
# else, to avoid relying on unverifiable third-party binary blobs. Podman
# caches the result of each RUN command, so rebuilding is done only once
# per host.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends wget file make gcc g++ zlib1g-dev libssl-dev ca-certificates && \
rm -rf /var/lib/apt/lists
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
# Build CMake 3.27
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
./bootstrap --parallel=\$(nproc) && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build GCC 14
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
mkdir gmp mpc mpfr && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
make -j\$(nproc) && \
make install && \
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
rm -rf /build
# Build GNU binutils 2.43
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
./configure --prefix=/usr && \
make -j\$(nproc) && \
make install && \
rm -fr /build
# Build Python 3.12.7
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
./configure && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build LLVM 20
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-20.1.3.tar.gz | tar xzf - --strip-components=1 && \
mkdir b && \
cd b && \
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
cmake --build . -j\$(nproc) && \
cmake --install . --strip && \
rm -rf /build
EOF
;;
aarch64 | arm | ppc64le | s390x)
# Debian 11 (Bullseye) released in August 2021
#
# We don't want to build Clang for these targets on QEMU becuase it
# would take an extremely long time. Also, I believe old Linux boxes
# are typically x86-64.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake && \
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
riscv64)
cat <<EOF | $image_build
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake && \
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
loongarch64)
cat <<EOF | $image_build
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e 's!http[^ ]*!http://snapshot.debian.org/archive/debian-ports/20250620T014755Z!g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake && \
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
*)
usage
;;
esac
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
dest=mold-$version-$arch-linux
set -e -x
docker run --platform linux/$arch -it --rm -v "$(pwd):/mold" \
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
bash -c "mkdir /tmp/build &&
cd /tmp/build &&
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /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"
# Source tarballs available on GitHub don't contain .git directory.
# Clone the repo if missing.
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
# We use the timestamp of the last Git commit as the file timestamp
# for build artifacts.
timestamp=$(git log -1 --format=%ct)
# `uname -m` in an ARM32 container running on an ARM64 host reports it
# not as ARM32 but as ARM64. That confuses BLAKE3's cmake script and
# erroneously enables NEON SIMD instructions. `setarch` can be used to
# change the output of `uname -m`.
setarch=
[ $arch = arm ] && setarch='setarch linux32'
mkdir -p dist
# Build mold in a container.
#
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
# build artifacts to appear as if they were built at a specific time.
# We use it to control how the compiler expands the C/C++ __DATE__ and
# __TIME__ macros.
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
$setarch bash -c "
set -e
export SOURCE_DATE_EPOCH=$timestamp
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
cmake --build . -j\$(nproc)
cmake --install .
cmake -DMOLD_USE_MOLD=1 .
cmake --build . -j\$(nproc)
ctest --output-on-failure -j\$(nproc)
cmake --install . --prefix $dest --strip
find $dest -print | xargs touch --no-dereference --date=@$timestamp
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
cp mold /dist
sha256sum /dist/$dest.tar.gz
"
+1
View File
@@ -0,0 +1 @@
<svg version="1.1" viewBox="0.0 0.0 600.0 371.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" width="600" height="371" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M0 0L600.0 0L600.0 371.0L0 371.0L0 0Z" fill-rule="nonzero"/><path stroke="#333333" stroke-width="1.0" stroke-linecap="butt" d="M82.5 275.5L581.5 275.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 231.5L581.5 231.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 186.5L581.5 186.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 142.5L581.5 142.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 98.5L581.5 98.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 53.5L581.5 53.5" fill-rule="nonzero"/><clipPath id="id_0"><path d="M82.55 53.916668L581.45 53.916668L581.45 275.45L82.55 275.45L82.55 53.916668Z" clip-rule="nonzero"/></clipPath><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroLine truncated

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+5
View File
@@ -1,3 +1,8 @@
[This document was written in 2020, and the contents are outdated.
Specifically, we no longer believe that object preloading is a good
idea. That being said, most of the points in this document still hold
even today. Therefore, I'll keep this document as-is.]
## Design and implementation of mold
For the rest of this documentation, I'll explain the design and the
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+179
View File
@@ -0,0 +1,179 @@
# Instrumenting mold with MemorySanitizer
## Introduction
Per <https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo>:
> If you want MemorySanitizer to work properly and not produce any false
> positives, you must ensure that all the code in your program and in
> libraries it uses is instrumented (i.e. built with `-fsanitize=memory`).
> In particular, you would need to link against an MSan-instrumented C++
> standard library. We recommend to use [libc++](https://libcxx.llvm.org/)
> for that purpose.
## Building instrumented libc++
Build an MSan-instrumented libc++ from source:
```sh
cd ~
git clone https://github.com/llvm/llvm-project
cd llvm-project
cmake -S ./runtimes -B build-libcxx -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DLLVM_USE_SANITIZER=MemoryWithOrigins
cmake --build build-libcxx -- cxx cxxabi
export LIBCXX="$HOME/llvm-project/build-libcxx" # for subsequent build steps
```
Upon success, `./build-libcxx/{include,lib}` will contain the resulting
headers and shared libraries.
## Linking mold against instrumented libc++
Use the `MOLD_USE_MSAN` and `MOLD_STDLIB_PREFIX` cmake variables to link
mold against the instrumented build of libc++:
```sh
cd ~
git clone https://github.com/rui314/mold.git
cd mold
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DMOLD_USE_MSAN=ON \
-DMOLD_STDLIB_PREFIX="$LIBCXX" \
-DMOLD_USE_MIMALLOC=OFF \
-DMOLD_USE_SYSTEM_TBB=ON
cmake --build build
```
Most of mold's tests (except those for `-flto`) should work at this
point. Run them like normal:
```sh
ctest --test-dir build
```
Any resulting MemorySanitizer errors should be visible in
`./build/Temporary/Testing/LastTest.log`.
## Building instrumented LTO plugin (experimental)
Exercising `-flto` with MemorySanitizer (and without false positives)
requires instrumenting the transitive dependencies of `lto-unix.cc`:
- `libiberty.a`
- `liblto_plugin.so`
- `LLVMgold.so`
> [!NOTE]
> This is more involved and time-consuming than building only libc++ from
> source. The cost-benefit ratio of this additional instrumentation and
> test coverage may be unfavorable in many cases.
> [!IMPORTANT]
> The following steps are experimental and unlikely to work exactly as-is
> under CI runners and individual developer environments. Consider the
> following a starting point rather than a complete HOWTO.
Build an MSan-instrumented GNU libiberty:
```sh
cd ~
git clone [email protected]:gcc-mirror/gcc.git
cd gcc/libiberty
export CC=clang
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
export LDFLAGS="-fsanitize=memory"
./configure
make -j$(nproc)
unset CC CFLAGS LDFLAGS
```
Install the resulting `./libiberty.a` into the build toolchain being
tested with mold. If the system image is ephemeral or disposable (e.g. a
short-lived VM or container), a quick-and-dirty install could look like:
```sh
sudo cp /usr/lib/libiberty.a{,.bak}
sudo cp ./libiberty.a /usr/lib/
```
Build an MSan-instrumented GCC LTO plugin library:
```sh
cd ~/gcc/lto-plugin # use already-cloned repo from earlier step
export CC=clang
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
export LDFLAGS="-fsanitize=memory"
./configure --with-libiberty=/usr/lib
make -j$(nproc)
unset CC CFLAGS LDFLAGS
```
Install the resulting `liblto_plugin.so`. With the same caveats discussed
above, a simple install into an ephemeral environment could look like:
```sh
export GCC_VERSION="$(gcc -dumpversion)"
sudo cp /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/liblto_plugin.so{,.bak}
sudo cp .libs/liblto_plugin.so /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/
```
Build an MSan-instrumented LLVM LTO plugin library:
```sh
cd ~/llvm-project # use already-cloned repo from earlier step
CF="-nostdinc++ -isystem $LIBCXX/include/c++/v1"
INCDIR="$(find /usr -name plugin-api.h -type f | head -1 | xargs dirname)"
function configure
{
cmake -S ./llvm -B build-plugin -G Ninja \
-DLLVM_ENABLE_PROJECTS=clang \
-DLLVM_TARGETS_TO_BUILD=X86 \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_FLAGS="$CF" \
-DCMAKE_CXX_FLAGS="$CF" \
-DCMAKE_EXE_LINKER_FLAGS="$1" \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DLLVM_BINUTILS_INCDIR="$INCDIR" \
-DLLVM_USE_SANITIZER=MemoryWithOrigins
}
# workaround for linker issues: configure twice with different LDFLAGS
configure "-nostdlib++ -L $LIBCXX/lib -Wl,--rpath=$LIBCXX/lib"
configure "-nostdlib++ -L $LIBCXX/lib -lc++ -Wl,--rpath=$LIBCXX/lib"
cmake --build build-plugin -- LLVMgold.so
```
Install the resulting `LLVMgold.so`. With the same caveats discussed
above, a simple install into an ephemeral environment could look like:
```sh
sudo cp /usr/lib/LLVMgold.so{,.bak}
sudo cp ./build-plugin/lib/LLVMgold.so /usr/lib/
```
mold's tests for `-flto` should now (mostly) work, though there do seem
to be issues around `gcc` inferring `-flto` when not explicitly specified.
One workaround is to use `clang` as `TEST_CC`:
```sh
cd ~/mold # use already-cloned and built repo from earlier step
TEST_CC=clang TEST_CXX=clang++ ctest --test-dir build
```
## References
- <https://github.com/google/sanitizers/wiki/MemorySanitizerBootstrappingClang>
- <https://llvm.org/docs/GoldPlugin.html#lto-how-to-build>
+574 -1239
View File
File diff suppressed because it is too large. Load diff
+985
View File
@@ -0,0 +1,985 @@
mold(1) -- a modern linker
==========================
## SYNOPSIS
`mold` [_option_...] _file_...
## DESCRIPTION
`mold` is a faster drop-in replacement for the default GNU ld(1).
### How to use
See <https://github.com/rui314/mold#how-to-use>.
### Compatibility
`mold` is designed to be a drop-in replacement for the GNU linkers for
linking user-land programs. If your user-land program cannot be built due to
missing command-line options, please file a bug at
<https://github.com/rui314/mold/issues>.
`mold` supports a very limited set of linker script features, which is just
sufficient to read `/usr/lib/x86_64-linux-gnu/libc.so` on Linux systems (on
Linux, that file is contrary to its name not a shared library but an ASCII
linker script that loads a real `libc.so` file.)
Beyond that, we have no plan to support any additional linker script features.
The linker script is an ad-hoc, over-designed, complex language which we
believe needs to be replaced by a simpler mechanism. We have a plan to add a
replacement for the linker script to `mold` instead.
### Archive symbol resolution
Traditionally, Unix linkers are sensitive to the order in which input files
appear on the command line. They process input files from the first (leftmost)
file to the last (rightmost) file one-by-one. While reading input files, they
maintain sets of defined and undefined symbols. When visiting an archive file
(`.a` files), they pull out object files to resolve as many undefined symbols
as possible and move on to the next input file. Object files that weren't
pulled out will never have a chance for a second look.
Due to this behavior, you usually have to add archive files at the end of a
command line, so that when a linker reaches archive files, it knows what
symbols remain as undefined.
If you put archive files at the beginning of a command line, a linker doesn't
have any undefined symbols, and thus no object files will be pulled out from
archives. You can change the processing order by using the `--start-group` and
`--end-group` options, though they make a linker slower.
`mold`, as well as the LLVM lld(1) linker, takes a different approach. They
remember which symbols can be resolved from archive files instead of
forgetting them after processing each archive. Therefore, `mold` and lld(1)
can "go back" in a command line to pull out object files from archives if they
are needed to resolve remaining undefined symbols. They are not sensitive to
the input file order.
`--start-group` and `--end-group` are still accepted by `mold` and lld(1)
for compatibility with traditional linkers, but they are silently ignored.
### Dynamic symbol resolution
Some Unix linker features are difficult to understand without comprehending
the semantics of dynamic symbol resolution. Therefore, even though it's not
specific to `mold`, we'll explain it here.
We use "ELF module" or just "module" as a collective term to refer to an
executable or a shared library file in the ELF format.
An ELF module may have lists of imported symbols and exported symbols, as well
as a list of shared library names from which imported symbols should be
imported. The point is that imported symbols are not bound to any specific
shared library until runtime.
Here is how the Unix dynamic linker resolves dynamic symbols. Upon the start
of an ELF program, the dynamic linker constructs a list of ELF modules which,
as a whole, consist of a complete program. The executable file is always at
the beginning of the list followed by its dependent shared libraries. An
imported symbol is searched from the beginning of the list to the end. If two
or more modules define the same symbol, the one that appears first in the list
takes precedence over the others.
This Unix semantics are contrary to systems such as Windows that have a
two-level namespace for dynamic symbols. On Windows, for example, dynamic
symbols are represented as a tuple of (`symbol-name`, `shared-library-name`),
so that each dynamic symbol is guaranteed to be resolved from some specific
library.
Typically, an ELF module that exports a symbol also imports the same symbol.
Such a symbol is usually resolved to itself, but that's not the case if a
module that appears before it in the symbol search list provides another
definition of the same symbol.
Let's take `malloc` as an example. Assume that you define your version of
`malloc` in your main executable file. Then, all `malloc` calls from any
module are resolved to your function instead of the one in libc, because the
executable is always at the beginning of the dynamic symbol search list. Note
that even `malloc` calls within libc are resolved to your definition since
libc exports and imports `malloc`. Therefore, by defining `malloc` yourself,
you can overwrite a library function, and the `malloc` in libc becomes dead
code.
These Unix semantics are tricky and sometimes considered harmful. For example,
assume that you accidentally define `atoi` as a global function in your
executable that behaves completely differently from the one in the C standard.
Then, all `atoi` function calls from any modules (even function calls within
libc) are redirected to your function instead of the one in libc, which will
very likely cause a problem. That is a somewhat surprising consequence for an
accidental name conflict. On the other hand, this semantic is sometimes useful
because it allows users to override library functions without rebuilding
modules containing them.
Whether good or bad, you should keep these semantics in mind to understand
Unix linkers' behaviors.
### Build reproducibility
`mold`'s output is deterministic. That is, if you pass the same object files
and the same command-line options to the same version of `mold`, it is
guaranteed that `mold` produces the bit-for-bit identical output. The linker's
internal randomness, such as the timing of thread scheduling or iteration
orders of hash tables, doesn't affect the output.
`mold` does not have any host-specific default settings. This is contrary to the
GNU linkers, for which some configurable values, such as system-dependent
library search paths, are hard-coded. `mold` depends only on its command-line
arguments.
## OPTION NOTATIONS
Multi-letter long options may precede either a single dash or double dashes,
except for those starting with the letter "o". For historical reasons, long
options beginning with "o" must precede double dashes.
For example, you can spell `--as-needed` as `-as-needed`, but `--omagic` must
not be spelled as `-omagic`. `-omagic` will be interpreted not as `--omagic`
but as `-o magic`.
## MOLD-SPECIFIC OPTIONS
* `--chroot`=_dir_:
Set _dir_ as the root directory.
* `--color-diagnostics`=[ _auto_ | _always_ | _never_ ]:
Show diagnostic messages in color using ANSI escape sequences. `auto` means
that `mold` prints out messages in color only if the standard output is
connected to a TTY. Default is `auto`.
* `--color-diagnostics`:
Synonym for `--color-diagnostics=auto`.
* `--no-color-diagnostics`:
Synonym for `--color-diagnostics=never`.
* `--detach`, `--no-detach`:
Permit or do not permit mold to create a debug info file in the background.
* `--fork`, `--no-fork`:
Spawn a child process and let it do the actual linking. When linking a large
program, the OS kernel can take a few hundred milliseconds to terminate a
`mold` process. `--fork` hides that latency. By default, it does fork.
Note that `--fork` also hides the resource usage statistics reported by
time(2), since it doesn't call waitpid(2) on the child process. If you
need those statistics, pass `--no-fork`.
* `--perf`:
Print performance statistics.
* `--print-dependencies`:
Print out dependency information for input files.
Each line of the output for this option shows which file depends on which
file to use a specific symbol. This option is useful for debugging why some
object file in a static archive got linked or why some shared library is
kept in an output file's dependency list even with `--as-needed`.
* `--relocatable-merge-sections`:
By default, `mold` doesn't merge input sections by name when merging input
object files into a single output object file for `-r`. For example,
`.text.foo` and `.text.bar` aren't merged for `-r` even though they are
merged into `.text` based on the default section merging rules.
This option changes the behavior so that `mold` merges input sections by
name by the default section merging rules.
* `--repro`:
Archive input files, as well as a text file containing command line options,
in a tar file so that you can run `mold` with the exact same inputs again.
This is useful for reporting a bug with a reproducer. The output filename is
`path/to/output.tar`, where `path/to/output` is an output filename specified
by `-o`.
* `--reverse-sections`:
Reverse the order of input sections before assigning them the offsets in the
output file.
This option is useful for finding bugs that depend on the initialization
order of global objects. In C++, constructors of global objects in a single
source file are guaranteed to be executed in the source order, but there's
no such guarantee across compilation units. Usually, constructors are
executed in the order given to the linker, but depending on it is a mistake.
By reversing the order of input sections using `--reverse-sections`, you can
easily test that your program works in the reversed initialization order.
* `--run` _command_ _arg_...:
Run _command_ with `mold` as `/usr/bin/ld`. Specifically, `mold` runs a
given command with the `LD_PRELOAD` environment set to intercept exec(3)
family functions and replaces `argv[0]` with itself if it is `ld`,
`ld.gold`, or `ld.lld`.
* `--separate-debug-file`, `--separate-debug-file`=_file_:
Bundle debug info sections into a separate file instead of embedding them in
an output executable or a shared library. mold creates a debug info file in
the background by default, so that you can start running your executable as
soon as possible.
By default, the debug info file is created in the same directory as is the
output file, with the `.dbg` file extension. That filename is embedded into
the output file so that `gdb` can automatically find the debug info file for
the output file. For more info about gdb features related to separate debug
files, see
<https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html>.
mold holds a file lock with flock(2) while creating a debug info file in the
background.
If you don't want to create a debug info file in the background, pass the
`--no-detach` option.
* `--shuffle-sections`, `--shuffle-sections`=_number_:
Randomize the output by shuffling the order of input sections before
assigning them the offsets in the output file. If a _number_ is given, it's
used as a seed for the random number generator, so that the linker produces
the same output for the same seed. If no seed is given, a random number is
used as a seed.
This option is useful for benchmarking. Modern CPUs are sensitive to a
program's memory layout. A seemingly benign change in program layout, such
as a small size increase of a function in the middle of a program, can
affect the program's performance. Therefore, even if you write new code and
get a good benchmark result, it is hard to say whether the new code improves
the program's performance; it is possible that the new memory layout happens
to perform better.
By running a benchmark multiple times with randomized memory layouts using
`--shuffle-sections`, you can isolate your program's real performance number
from the randomness caused by memory layout changes.
* `--spare-program-headers`=_number_:
Append the given number of `PT_NULL` entries to the end of the program
header, so that post-link processing tools can easily add new segments by
overwriting the null entries.
Note that ELF requires all `PT_LOAD` segments to be sorted by `p_vaddr`.
Therefore, if you add a new LOAD segment, you may need to sort the entire
program header.
* `--stats`:
Print input statistics.
* `--thread-count`=_count_:
Use _count_ number of threads.
* `--threads`, `--no-threads`:
Use multiple threads. By default, `mold` uses as many threads as the number of
cores or 32, whichever is smaller. The reason it is capped at 32 is because
`mold` doesn't scale well beyond that point. To use only one thread, pass
`--no-threads` or `--thread-count=1`.
* `--quick-exit`, `--no-quick-exit`:
Use or do not use `quick_exit` to exit.
* `--zero-to-bss`:
Convert all-zero data sections into BSS.
When this option is enabled, `mold` scans input data sections that are not
of type `SHT_NOBITS` and checks whether their contents consist solely of
zero bytes. Such sections are then converted into BSS (`SHT_NOBITS`)
sections. This reduces the size of the output file, since BSS sections
occupy no space in the file image.
This behavior is especially useful for user-defined sections created with
`__attribute__((section(".sectname")))` that contain uninitialized global
variables. GCC and Clang do not automatically mark such sections as BSS
even if their contents are entirely zero, and instead emit them as regular
data sections.
For example, consider `__attribute__((section(".sectname"))) int vec[256];`.
By default, this results in a `.sectname` section of type `SHT_PROGBITS`
filled with zeros. With `--zero-to-bss`, the linker will recognize it as
empty data and convert it to a `SHT_NOBITS` section, reducing the output
file size without changing runtime semantics.
* `-z rewrite-endbr`, `-z norewrite-endbr`:
As a security measure, some CPU instruction sets have recently gained a
feature to protect control flow integrity by disallowing indirect branches
by default. If the feature is enabled, the instruction that is executed
immediately after an indirect branch must be an branch target marker
instruction, or a CPU-level fault will raise. The marker instruction is also
known as "landing pad" instruction, to which indirect branches can land.
This feature makes ROP attacks harder to conduct.
To use the feature, a function whose pointer is taken needs to begin with a
landing pad because a function call via a function pointer is compiled to an
indirect branch. On the other hand, if a function is called only directly
(i.e. referred to only by _direct_ branch instructions), it doesn't have to
begin with it.
By default, the compiler always emits a landing pad at the beginning of each
global function because it doesn't know whether or not the function's
pointer is taken in another translation unit. As a result, the resulting
binary has more attack surface than necessary.
If `--rewrite-endbr` is given, mold conducts a whole program analysis
to identify functions whose addresses are actually taken and rewrites
landing pads with no-ops for non-address-taken functions, reducing the
attack surface.
This feature is currently available only on x86-64.
## GNU-COMPATIBLE OPTIONS
* `--help`:
Report usage information to stdout and exit.
* `-v`, `--version`:
Report version information to stdout.
* `-V`:
Report version and target information to stdout.
* `-E`, `--export-dynamic`, `--no-export-dynamic`:
When creating an executable, using the `-E` option causes all global symbols
to be put into the dynamic symbol table, so that the symbols are visible
from other ELF modules at runtime.
By default, or if `--no-export-dynamic` is given, only symbols that are
referenced by DSOs at link-time are exported from an executable.
* `-F` _libname_, `--filter`=_libname_:
Set the `DT_FILTER` dynamic section field to _libname_.
* `-I` _file_, `--dynamic-linker`=_file_, `--no-dynamic-linker`:
Set the dynamic linker path to _file_. If no `-I` option is given, or if
`--no-dynamic-linker` is given, no dynamic linker path is set to an output
file. This is contrary to the GNU linkers which set a default dynamic linker
path in that case. This difference doesn't usually make any difference
because the compiler driver always passes `-I` to the linker.
* `-L` _dir_, `--library-path`=_dir_:
Add _dir_ to the list of library search paths from which `mold` searches
libraries for the `-l` option.
Unlike the GNU linkers, `mold` does not have default search paths. This
difference doesn't usually make any difference because the compiler driver
always passes all necessary search paths to the linker.
* `-M`, `--print-map`:
Write a map file to stdout.
* `-N`, `--omagic`, `--no-omagic`:
Force `mold` to emit an output file with an old-fashioned memory layout.
First, it makes the first data segment not aligned to a page boundary.
Second, text segments are marked as writable if the option is given.
* `-S`, `--strip-debug`:
Omit `.debug_*` sections from the output file.
* `-T` _file_, `--script`=_file_:
Read linker script from _file_.
* `-X`, `--discard-locals`:
Discard temporary local symbols to reduce the sizes of the symbol table and
the string table. Temporary local symbols are local symbols starting with
`.L`. Compilers usually generate such symbols for unnamed program elements
such as string literals or floating-point literals.
* `-e` _symbol_, `--entry`=_symbol_:
Use _symbol_ as the entry point symbol instead of the default entry
point symbol _start.
* `-f` _shlib_, `--auxiliary`=_shlib_:
Set the `DT_AUXILIARY` dynamic section field to _shlib_.
* `-h` _libname_, `--soname`=_libname_:
Set the `DT_SONAME` dynamic section field to _libname_. This option is used
when creating a shared object file. Typically, when you create `libfoo.so`,
you want to pass `--soname=foo` to a linker.
* `-l` _libname_:
Search for `lib`_libname_`.so` or `lib`_libname_`.a` from library search
paths.
* `-m` _target_:
Choose a _target_.
* `-o` _file_, `--output`=_file_:
Use _file_ as the output file name instead of the default name `a.out`.
* `-r`, `--relocatable`:
Instead of generating an executable or a shared object file, combine input
object files to generate another object file that can be used as an input to
a linker.
* `-s`, `--strip-all`:
Omit `.symtab` section from the output file.
* `-u` _symbol_, `--undefined`=_symbol_:
If _symbol_ remains as an undefined symbol after reading all object files,
and if there is a static archive that contains an object file defining
_symbol_, pull out the object file and link it so that the output file
contains a definition of _symbol_.
* `-y` _symbol_, `--trace-symbol`=_symbol_:
Trace references to _symbol_.
* `--Bdynamic`:
Link against shared libraries.
* `--Bstatic`:
Do not link against shared libraries.
* `--Bsymbolic`:
When creating a shared library, make global symbols export-only (i.e. do not
import the same symbol). As a result, references within a shared library are
always resolved locally, negating symbol override at runtime. See "Dynamic
symbol resolution" for more information about symbol imports and exports.
* `--Bsymbolic-functions`:
This option has the same effect as `--Bsymbolic` but works only for function
symbols. Data symbols remain being both imported and exported.
* `--Bsymbolic-non-weak`:
This option has the same effect as `--Bsymbolic` but works only for non-weak
symbols. Weak symbols remain being both imported and exported.
* `--Bsymbolic-non-weak-functions`:
This option has the same effect as `--Bsymbolic` but works only for non-weak
function symbols. Data symbols and weak function symbols remain being both
imported and exported.
* `--Bno-symbolic`:
Cancel `--Bsymbolic`, `--Bsymbolic-functions`, `--Bsymbolic-non-weak` and
`--Bsymbolic-non-weak-functions`.
* `--Map`=_file_:
Write map file to _file_.
* `--Tbss`=_address_:
Alias for `--section-start=.bss=`_address_.
* `--Tdata`=_address_:
Alias for `--section-start=.data=`_address_.
* `--Ttext`=_address_:
Alias for `--section-start=.text=`_address_.
* `--allow-multiple-definition`:
Normally, the linker reports an error if there are more than one definition
of a symbol. This option changes the default behavior so that it doesn't
report an error for duplicate definitions and instead use the first
definition.
* `--allow-shlib-undefined`, `--no-allow-shlib-undefined`:
Even if mold succeeds in linking a main executable without undefined symbol
errors, you may still encounter symbol lookup errors at runtime because the
dynamic linker cannot find some symbols in shared libraries in any ELF
module. This occurs because mold ignores undefined symbols in shared
libraries by default.
If you pass `--no-allow-shlib-undefined`, mold verifies that undefined
symbols in shared libraries given to the linker can be resolved at
link-time. In other words, this converts the runtime error to a link-time
error.
Note that you need to pass all shared libraries, including indirectly
dependent ones, to the linker as arguments for `-l`. If a shared library
depends on a library that's not passed to the linker, the verification will
be skipped for that file.
* `--as-needed`, `--no-as-needed`:
By default, shared libraries given to the linker are unconditionally added
to the list of required libraries in an output file. However, shared
libraries after `--as-needed` are added to the list only when at least one
symbol is actually used by the output file. In other words, shared libraries
after `--as-needed` are not added to the list of needed libraries if they
are not needed by a program.
The `--no-as-needed` option restores the default behavior for subsequent
files.
* `--build-id`=[ `md5` | `sha1` | `sha256` | `fast` | `uuid` | `0x`_hexstring_ | `none` ]:
Create a `.note.gnu.build-id` section containing a byte string to uniquely
identify an output file. `sha256` compute a 256-bit cryptographic hash of an
output file and set it to build-id. `md5` and `sha1` compute the same hash
but truncate it to 128 and 160 bits, respectively, before setting it to
build-id. `uuid` sets a random 128-bit UUID. `0x`_hexstring_ sets
_hexstring_. `fast` is a synonym for `sha256`.
* `--build-id`:
Synonym for `--build-id=sha256`.
* `--no-build-id`:
Synonym for `--build-id=none`.
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zlib:`_N_ | `zstd` | `zstd:`_N_ | `none` ]:
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
compression algorithm. `zlib-gabi` is an alias for `zlib`. `zlib:`_N_
selects the zlib compression level, where _N_ is between 0 and 9 (0
means no compression), and `zstd:`_N_ selects the zstd compression
level, where _N_ is between 1 and 22. Higher levels achieve better
compression ratios but are slower. `zlib` is equivalent to `zlib:1`
and `zstd` is equivalent to `zstd:3`.
* `--defsym`=_symbol_=_value_:
Define _symbol_ as an alias for _value_.
_value_ is either an integer (in decimal or hexadecimal with `0x` prefix) or
a symbol name. If an integer is given as a value, _symbol_ is defined as an
absolute symbol with the given value.
* `--default-symver`:
Use soname as a symbol version and append that version to all symbols.
* `--demangle`, `--no-demangle`:
Demangle C++ and Rust symbols in log messages.
* `--dependency-file`=_file_:
Write a dependency file to _file_. The contents of the written file is
readable by make(1), which defines only one rule with the linker's output
file as a target and all input files as its prerequisites. Users are
expected to include the generated dependency file into a Makefile to
automate the dependency management. This option is analogous to the
compiler's `-MM -MF` options.
* `--dynamic-list`=_file_:
Read a list of dynamic symbols from _file_. Same as
`--export-dynamic-symbol-list`, except that it implies `--Bsymbolic`. If
_file_ does not exist in the current directory, it is searched from library
search paths for the sake of compatibility with GNU ld.
* `--eh-frame-hdr`, `--no-eh-frame-hdr`:
Create `.eh_frame_hdr` section.
* `--emit-relocs`:
The linker usually "consumes" relocation sections. That is, the linker
applies relocations to other sections, and relocation sections themselves
are discarded.
The `--emit-relocs` instructs the linker to leave relocation sections in the
output file. Some post-link binary analysis or optimization tools such as
LLVM Bolt need them.
* `--enable-new-dtags`, `--disable-new-dtags`:
By default, `mold` emits `DT_RUNPATH` for `--rpath`. If you pass
`--disable-new-dtags`, `mold` emits `DT_RPATH` for `--rpath` instead.
* `--execute-only`:
Traditionally, setting the executable bit to 1 for a memory page implies
that the page also become readable, which allows machine code to be read
as data at runtime. That is actually what an attacker often does after
gaining a limited control of a process to find pieces of machine code
they can use to gain the full control of the process. As a mitigation,
recent processors including some ARM64 ones allows "execute-only" pages.
If a page is execute-only, you can call a function there as long as you
know its address but can't read it as data.
This option marks text segments as execute-only by setting just the "X"
bit instead of "RX". Note that on most systems, the absence of the "R"
bit in the text segment serves just as a hint. If you run a program
linked with `--execute-only` on a processor that doesn't support
execute-only pages, your executable will likely still function normally,
but the text segment will remain readable.
* `--exclude-libs`=_libraries_ ...:
Mark all symbols in the given _libraries_ hidden.
* `--export-dynamic-symbol`=_symbol_:
Put symbols matching _symbol_ in the dynamic symbol table. _symbol_ may be a
glob pattern in the same syntax as for the `--export-dynamic-symbol-list` or
`--version-script` options.
* `--export-dynamic-symbol-list`=_file_:
Read a list of dynamic symbols from _file_.
* `--fatal-warnings`, `--no-fatal-warnings`:
Treat warnings as errors.
* `--fini`=_symbol_:
Call _symbol_ at unload-time.
* `--gc-sections`, `--no-gc-sections`:
Remove unreferenced sections.
* `--gdb-index`:
Create a `.gdb_index` section to speed up GNU debugger. To use this, you
need to compile source files with the `-ggnu-pubnames` compiler flag.
* `--hash-style`=[ `sysv` | `gnu` | `both` | `none` ]:
Set hash style.
* `--icf`=[ `safe` | `all` | `none` ], `--no-icf`:
It is not uncommon for a program to contain many identical functions that
differ only in name. For example, a C++ template `std::vector` is very
likely to be instantiated to the identical code for `std::vector<int>` and
`std::vector<unsigned>` because the container cares only about the size of
the parameter type. Identical Code Folding (ICF) is a size optimization to
identify and merge such identical functions.
If `--icf=all` is given, `mold` tries to merge all identical functions. This
reduces the size of the output most, but it is not a "safe" optimization. It
is guaranteed in C and C++ that two pointers pointing two different
functions will never be equal, but `--icf=all` breaks that assumption as two
identical functions have the same address after merging. So a care must be
taken when you use this flag that your program does not depend on the
function pointer uniqueness.
`--icf=safe` is a flag to merge functions only when it is safe to do so.
That is, if a program does not take an address of a function, it is safe to
merge that function with other function, as you cannot compare a function
pointer with something else without taking an address of a function.
`--icf=safe` needs to be used with a compiler that supports `.llvm_addrsig`
section which contains the information as to what symbols are address-taken.
LLVM/Clang supports that section by default. Since GCC does not support it
yet, you cannot use `--icf=safe` with GCC (it doesn't do any harm but can't
optimize at all.)
`--icf=none` and `--no-icf` disables ICF.
* `--ignore-data-address-equality`:
Make ICF to merge not only functions but also data. This option should be
used in combination with `--icf=all`.
* `--image-base`=_addr_:
Set the base address to _addr_.
* `--init`=_symbol_:
Call _symbol_ at load-time.
* `--no-undefined`:
Report undefined symbols (even with `--shared`).
* `--noinhibit-exec`:
Create an output file even if errors occur.
* `--package-metadata`=_percent-encoded-string_:
Embed a specified string into the `.note.package` section. This option
is designed for build scripts that generate binary packages, such as
`.rpm` or `.deb`, to include package metadata in each executable. It
simplifies the process of identifying the corresponding package for a
given executable or core file.
An argument to this option is treated as percent-encoded and decoded
before being inserted into the section, allowing you to avoid the use of
the comma (`,`) character in the argument. This is useful because the
compiler replaces all occurrences of commas in `-Wl,` with spaces before
forwarding them to the linker. Note that `mold` always interprets the
argument as percent-encoded, so you also need to escape all occurrences
of `%` as `%25`.
* `--pack-dyn-relocs`=[ `relr` | `android` | `android+relr` | `none` ]:
Compress dynamic relocations in the output file.
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
`.relr.dyn` section uses a space-efficient encoding scheme, specifying this
flag can reduce the size of the output. This is typically most effective for
position-independent executable.
If `android` is specified, all dynamic relocations are encoded in Android's
packed relocation format (APS2) and stored in the `.rel.dyn` or `.rela.dyn`
section, whose section type is changed to `SHT_ANDROID_REL` or
`SHT_ANDROID_RELA`. The dynamic table is updated to use `DT_ANDROID_REL`
/ `DT_ANDROID_RELA` and their `SZ` variants in place of the standard tags.
`android+relr` combines both: `R_*_RELATIVE` relocations go to `.relr.dyn`
and the rest are APS2-encoded. This is the configuration used by most
Android system binaries.
Note that a runtime loader has to support `.relr.dyn` to run executables or
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2026, glibc
(since 2.36), musl (since 1.2.4), Android, ChromeOS, and Fuchsia all
support it. The APS2 format, on the other hand, is only handled by
Android's bionic loader; the encoder itself is target-agnostic, but the
produced binary can only be loaded on platforms where bionic (or a
compatible loader) handles `DT_ANDROID_REL(A)`.
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
Create a position-independent executable.
* `--print-gc-sections`, `--no-print-gc-sections`:
Print removed unreferenced sections.
* `--print-icf-sections`, `--no-print-icf-sections`:
Print folded identical sections.
* `--push-state`, `--pop-state`:
`--push-state` saves the current values of `--as-needed`, `--whole-archive`,
`--static`, and `--start-lib`. The saved values can be restored by
pop-state.
`--push-state` and `--pop-state` pairs can nest.
These options are useful when you want to construct linker command line
options programmatically. For example, if you want to link `libfoo.so` by
as-needed basis but don't want to change the global state of `--as-needed`,
you can append `--push-state --as-needed -lfoo --pop-state` to the linker
command line options.
* `--relax, --no-relax`:
Rewrite machine instructions with more efficient ones for some relocations.
The feature is enabled by default.
* `--require-defined`=_symbol_:
Like `--undefined`, except the new symbol must be defined by the end of the
link.
* `--retain-symbols-file`=_file_:
Keep only symbols listed in _file_. _file_ is a text file containing a
symbol name on each line. `mold` discards all local symbols as well as
global symbol that are not in _file_. Note that this option removes symbols
only from `.symtab` section and does not affect `.dynsym` section, which is
used for dynamic linking.
* `--rpath`=_dir_:
Add _dir_ to runtime search paths.
* `--section-start`=_section_=_address_:
Set _address_ to section. _address_ is a hexadecimal number that may start
with an optional `0x`.
* `--shared`, `--Bshareable`:
Create a share library.
* `--spare-dynamic-tags`=_number_:
Append the given number of `DT_NULL` entries to the end of the `.dynamic`
section, so that post-link processing tools can easily add new dynamic tags
by overwriting the null entries.
* `--start-lib`, `--end-lib`:
Handle object files between `--start-lib` and `--end-lib` as if they were in
an archive file. That means object files between them are linked only when
they are needed to resolve undefined symbols. The options are useful if you
want to link object files only when they are needed but want to avoid the
overhead of running ar(3).
* `--static`:
Do not link against shared libraries.
* `--sysroot`=_dir_:
Set target system root directory to _dir_.
* `--trace`:
Print name of each input file.
* `--undefined-glob`=_pattern_:
Synonym for `--undefined`, except that `--undefined-glob` takes a glob
pattern instead of just a single symbol name.
* `--undefined-version`, `--no-undefined-version`:
By default, `mold` warns on a symbol specified by a version script or by
`--export-dynamic-symbol` if it is not defined. You can silence the warning
by `--undefined-version`.
* `--unique`=_pattern_:
Don't merge input sections that match the given glob pattern _pattern_.
* `--unresolved-symbols`=[ `report-all` | `ignore-all` | `ignore-in-object-files` | `ignore-in-shared-libs` ]:
How to handle undefined symbols.
* `--version-script`=_file_:
Read version script from _file_. If _file_ does not exist in the current
directory, it is searched from library search paths for the sake of
compatibility with GNU ld.
* `-w`, `--no-warnings`:
Suppress warnings and cancel `--fatal-warnings`.
* `--warn-common`, `--no-warn-common`:
Warn about common symbols.
* `--warn-once`:
Only warn once for each undefined symbol instead of warn for each relocation
referring an undefined symbol.
* `--warn-unresolved-symbols`, `--error-unresolved-symbols`:
Normally, the linker reports an error for unresolved symbols.
`--warn-unresolved-symbols` option turns it into a warning.
`--error-unresolved-symbols` option restores the default behavior.
* `--whole-archive`, `--no-whole-archive`:
When archive files (`.a` files) are given to the linker, only object files
that are needed to resolve undefined symbols are extracted from them and
linked to an output file. `--whole-archive` changes that behavior for
subsequent archives so that the linker extracts all object files and links
them to an output. For example, if you are creating a shared object file and
you want to include all archive members to the output, you should pass
`--whole-archive`. `--no-whole-archive` restores the default behavior for
subsequent archives.
* `--wrap`=_symbol_:
Make _symbol_ be resolved to `__wrap_`_symbol_. The original symbol can be
resolved as `__real_`_symbol_. This option is typically used for wrapping an
existing function.
* `-z cet-report`=[ `warning` | `error` | `none` ]:
Intel Control-flow Enforcement Technology (CET) is a new x86 feature
available since Tiger Lake which is released in 2020. It defines new
instructions to harden security to protect programs from control hijacking
attacks. You can tell the compiler to use the feature by specifying the
`-fcf-protection` flag.
`-z cet-report` flag is used to make sure that all object files were
compiled with a correct `-fcf-protection` flag. If `warning` or `error` are
given, `mold` prints out a warning or an error message if an object file was
not compiled with the compiler flag.
`mold` looks for `GNU_PROPERTY_X86_FEATURE_1_IBT` bit and
`GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in `.note.gnu.property` section to
determine whether or not an object file was compiled with `-fcf-protection`.
* `-z now`, `-z lazy`:
By default, functions referring to other ELF modules are resolved by the
dynamic linker when they are called for the first time. `-z now` marks an
executable or a shared library file so that all dynamic symbols are resolved
when a file is loaded to memory. `-z lazy` restores the default behavior.
* `-z origin`:
Mark object requiring immediate `$ORIGIN` processing at runtime.
* `-z ibt`:
Turn on `GNU_PROPERTY_X86_FEATURE_1_IBT` bit in `.note.gnu.property` section
to indicate that the output uses IBT-enabled PLT. This option implies `-z
ibtplt`.
* `-z ibtplt`:
Generate Intel Branch Tracking (IBT)-enabled PLT which is the default on
x86-64. This is the default.
* `-z execstack`, `-z noexecstack`:
By default, the pages for the stack area (i.e. the pages where local
variables reside) are not executable for security reasons. `-z execstack`
makes it executable. `-z noexecstack` restores the default behavior.
* `-z keep-text-section-prefix`, `-z nokeep-text-section-prefix`:
Keep `.text.hot`, `.text.unknown`, `.text.unlikely`, `.text.startup`, and
`.text.exit` as separate sections in the final binary instead of merging
them as `.text`.
* `-z rodynamic`:
Make the `.dynamic` section read-only.
* `-z relro`, `-z norelro`:
Some sections such as `.dynamic` have to be writable only during a module is
being loaded to memory. Once the dynamic linker finishes its job, such
sections won't be mutated by anyone. As a security mitigation, it is
preferred to make such segments read-only during program execution.
`-z relro` puts such sections into a special segment called `relro`. The
dynamic linker makes a relro segment read-only after it finishes its job.
By default, `mold` generates a relro segment. `-z norelro` disables the
feature.
* `-z sectionheader`, `-z nosectionheader`:
`-z nosectionheader` tell the linker to omit the section header.
By default, the linker does not omit the section header.
* `-z separate-loadable-segments`, `-z separate-code`, `-z noseparate-code`:
If one memory page contains multiple segments, the page protection bits are
set in such a way that the needed attributes (writable or executable) are
satisfied for all segments. This usually happens at a boundary of two
segments with two different attributes.
`separate-loadable-segments` adds paddings between segments with different
attributes so that they do not share the same page.
`separate-code` adds paddings only between executable and non-executable
segments. This is the default.
`noseparate-code` does not add any paddings between segments.
* `-z defs`, `-z nodefs`:
Report undefined symbols (even with `--shared`).
* `-z shstk`:
Enforce shadow stack by turning `GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in
`.note.gnu.property` output section. Shadow stack is part of Intel
Control-flow Enforcement Technology (CET), which is available since Tiger
Lake (2020).
* `-z start_stop_visibility`=[ `hidden` | `protected` ]:
If a section name is valid as a C identifier (i.e., it matches
`/^[_a-zA-Z][_a-zA-Z0-9]*$/`), mold creates `__start_SECNAME` and
`__stop_SECNAME` symbols to mark the beginning and end of the section,
where `SECNAME` is the section name.
You can make these marker symbols visible from other ELF modules by passing
`-z start_stop_visibility=protected`. Default is `hidden`.
* `-z text`, `-z notext`, `-z textoff`:
`mold` by default reports an error if dynamic relocations are created in
read-only sections. If `-z notext` or `-z textoff` are given, `mold` creates
such dynamic relocations without reporting an error. `-z text` restores the
default behavior.
* `-z max-page-size`=_number_:
Some CPU ISAs support multiple memory page sizes. This option specifies the
maximum page size that an output binary can run on. In general, binaries
built for a larger page size can run on a system with a smaller page size,
but not vice versa. The default value is 4 KiB for i386, x86-64, and RISC-V,
and 64 KiB for ARM64.
* `-z nodefaultlib`:
Make the dynamic loader ignore default search paths.
* `-z nodelete`:
Mark DSO non-deletable at runtime.
* `-z nodlopen`:
Mark DSO not available to dlopen(3). This option makes it possible for the
linker to optimize thread-local variable accesses by rewriting instructions
for some targets.
* `-z nodump`:
Mark DSO not available to dldump(3).
* `-z nocopyreloc`:
Do not create copy relocations.
* `-z initfirst`:
Mark DSO to be initialized first at runtime.
* `-z interpose`:
Mark object to interpose all DSOs but executable.
* `-(`, `-)`, `-EL`, `-O`_number_, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-copy-dt-needed-entries`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
Ignored
## ENVIRONMENT VARIABLES
* `MOLD_JOBS`:
If this variable is set to `1`, only one `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 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 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.
* `MOLD_DEBUG`:
If this variable is set to a non-empty string, `mold` embeds its
command-line options in the output file's `.comment` section.
* `MOLD_REPRO`:
Setting this variable to a non-empty string has the same effect as passing
the `--repro` option.
## SEE ALSO
gold(1), ld(1), elf(5), ld.so(8)
## AUTHOR
Rui Ueyama <ruiu@cs.stanford.edu>
## BUGS
Report bugs to <https://github.com/rui314/mold/issues>.
-342
View File
@@ -1,342 +0,0 @@
// Alpha is a 64-bit RISC ISA developed by DEC (Digital Equipment
// Corporation) in the early '90s. It aimed to be an ISA that would last
// 25 years. DEC expected Alpha would become 1000x faster during that time
// span. Since the ISA was developed from scratch for future machines,
// it's 64-bit from the beginning. There's no 32-bit variant.
//
// DEC ported its own Unix (Tru64) to Alpha. Microsoft also ported Windows
// NT to it. But it wasn't a huge commercial success.
//
// DEC was acquired by Compaq in 1997. In the late '90s, Intel and
// Hewlett-Packard were advertising that their upcoming Itanium processor
// would achieve significantly better performance than RISC processors, so
// Compaq decided to discontinue the Alpha processor line to switch to
// Itanium. Itanium resulted in a miserable failure, but it still suceeded
// to wipe out several RISC processors just by promising overly optimistic
// perf numbers. Alpha as an ISA would probably have been fine after 25
// years since its introduction (which is 1992 + 25 = 2017), but the
// company and its market didn't last that long.
//
// From the linker's point of view, there are a few peculiarities in its
// psABI as shown below:
//
// - Alpha lacks PC-relative memory load/store instructions, so it uses
// register-relative load/store instructions in position-independent
// code. Specifically, GP (which is an alias for $r29) is always
// maintained to refer to .got+0x8000, and global variables' addresses
// are loaded in a GP-relative manner.
//
// - It looks like even function addresses are first loaded to register
// in a GP-relative manner before calling it. We can relax it to
// convert the instruction sequence with a direct branch instruction,
// but by default, object files don't use a direct branch to call a
// function. Therefore, by default, we don't need to create a PLT.
// Any function call is made by first reading its address from GOT and
// jump to the address.
#include "mold.h"
namespace mold::elf {
using E = ALPHA;
// A 32-bit immediate can be materialized in a register with a "load high"
// and a "load low" instruction sequence. The first instruction sets the
// upper 16 bits in a register, and the second one set the lower 16
// bits. When doing so, they sign-extend an immediate. Therefore, if the
// 15th bit of an immediate happens to be 1, setting a "low half" value
// negates the upper 16 bit values that has already been set in a
// register. To compensate that, we need to add 0x8000 when setting the
// upper 16 bits.
static u32 hi(u32 val) {
return bits(val + 0x8000, 31, 16);
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_ALPHA_SREL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
u64 GP = ctx.got->shdr.sh_addr + 0x8000;
switch (rel.r_type) {
case R_ALPHA_REFQUAD:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_ALPHA_GPREL32:
*(ul32 *)loc = S + A - GP;
break;
case R_ALPHA_LITERAL:
if (A)
*(ul16 *)loc = ctx.extra.got->get_addr(sym, A) - GP;
else
*(ul16 *)loc = GOT + G - GP;
break;
case R_ALPHA_BRSGP:
*(ul32 *)loc |= bits(S + A - P - 4, 22, 0);
break;
case R_ALPHA_GPDISP:
*(ul16 *)loc = hi(GP - P);
*(ul16 *)(loc + A) = GP - P;
break;
case R_ALPHA_SREL32:
*(ul32 *)loc = S + A - P;
break;
case R_ALPHA_GPRELHIGH:
*(ul16 *)loc = hi(S + A - GP);
break;
case R_ALPHA_GPRELLOW:
*(ul16 *)loc = S + A - GP;
break;
case R_ALPHA_TLSGD:
*(ul16 *)loc = sym.get_tlsgd_addr(ctx) - GP;
break;
case R_ALPHA_TLSLDM:
*(ul16 *)loc = ctx.got->get_tlsld_addr(ctx) - GP;
break;
case R_ALPHA_DTPRELHI:
*(ul16 *)loc = hi(S + A - ctx.dtp_addr);
break;
case R_ALPHA_DTPRELLO:
*(ul16 *)loc = S + A - ctx.dtp_addr;
break;
case R_ALPHA_GOTTPREL:
*(ul16 *)loc = sym.get_gottp_addr(ctx) + A - GP;
break;
case R_ALPHA_TPRELHI:
*(ul16 *)loc = hi(S + A - ctx.tp_addr);
break;
case R_ALPHA_TPRELLO:
*(ul16 *)loc = S + A - ctx.tp_addr;
break;
case R_ALPHA_LITUSE:
case R_ALPHA_HINT:
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_ALPHA_REFLONG:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
case R_ALPHA_REFQUAD:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on Alpha";
switch (rel.r_type) {
case R_ALPHA_REFQUAD:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_ALPHA_LITERAL:
if (rel.r_addend) {
sym.flags.fetch_or(NEEDS_ALPHA_GOT, std::memory_order_relaxed);
ctx.extra.got->add_symbol(sym, rel.r_addend);
} else {
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
}
break;
case R_ALPHA_SREL32:
scan_pcrel(ctx, sym, rel);
break;
case R_ALPHA_BRSGP:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_ALPHA_TLSGD:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_ALPHA_TLSLDM:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_ALPHA_GOTTPREL:
case R_ALPHA_TPRELHI:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_ALPHA_GPREL32:
case R_ALPHA_LITUSE:
case R_ALPHA_GPDISP:
case R_ALPHA_HINT:
case R_ALPHA_GPRELHIGH:
case R_ALPHA_GPRELLOW:
case R_ALPHA_DTPRELHI:
case R_ALPHA_DTPRELLO:
case R_ALPHA_TPRELLO:
break;
default:
Fatal(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
// An R_ALPHA_LITERAL relocation may request the linker to create a GOT
// entry for an external symbol with a non-zero addend. This is an unusual
// request which we cannot find in any other targets.
//
// Referring an external symbol with a non-zero addend is a bad practice.
// To see why, assume that we have two R_ALPHA_LITERAL relocs to symbol X
// with addend 8 and 16. To satisfy that request, we need to create not
// only one but two GOT entries for symbol X with different addends.
//
// We don't want to mess up the implementation of the common GOT section
// for Alpha. So we create another GOT-like section, .alpha_got. Any GOT
// entry for an R_ALPHA_LITERAL reloc with a non-zero addends is created
// not in .got but in .alpha_got.
//
// Since .alpha_got entries are accessed relative to GP, .alpha_got
// needs to be close enough to .got. It's actually placed next to .got.
void AlphaGotSection::add_symbol(Symbol<E> &sym, i64 addend) {
assert(addend);
std::scoped_lock lock(mu);
entries.push_back({&sym, addend});
}
bool operator<(const AlphaGotSection::Entry &a, const AlphaGotSection::Entry &b) {
return std::tuple(a.sym->file->priority, a.sym->sym_idx, a.addend) <
std::tuple(b.sym->file->priority, b.sym->sym_idx, b.addend);
};
u64 AlphaGotSection::get_addr(Symbol<E> &sym, i64 addend) {
auto it = std::lower_bound(entries.begin(), entries.end(), Entry{&sym, addend});
assert(it != entries.end());
return this->shdr.sh_addr + (it - entries.begin()) * sizeof(Word<E>);
}
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) {
i64 n = 0;
for (Entry &e : entries)
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
n++;
return n;
}
void AlphaGotSection::finalize() {
sort(entries);
remove_duplicates(entries);
shdr.sh_size = entries.size() * sizeof(Word<E>);
}
void AlphaGotSection::copy_buf(Context<E> &ctx) {
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
for (i64 i = 0; i < entries.size(); i++) {
Entry &e = entries[i];
u64 P = this->shdr.sh_addr + sizeof(Word<E>) * i;
ul64 *buf = (ul64 *)(ctx.buf + this->shdr.sh_offset + sizeof(Word<E>) * i);
if (e.sym->is_imported) {
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
} else {
*buf = e.sym->get_addr(ctx) + e.addend;
if (ctx.arg.pic && !e.sym->is_absolute())
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, *buf);
}
}
}
} // namespace mold::elf
-760
View File
@@ -1,760 +0,0 @@
// ARM32 is a bit special from the linker's viewpoint because ARM
// processors support two different instruction encodings: Thumb and
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
// thumb is a subset of ARM, so not all ARM instructions are
// representable in Thumb.
//
// ARM processors originally supported only ARM instructions. Thumb
// instructions were later added to increase code density.
//
// ARM processors runs in either ARM mode or Thumb mode. The mode can
// be switched using BX (branch and mode exchange)-family instructions.
// We need to use that instructions to, for example, call a function
// encoded in Thumb from a function encoded in ARM. Sometimes, the
// linker even has to emit an interworking thunk code to switch mode.
//
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
// byte boundaries.
//
// You can distinguish Thumb functions from ARM functions by looking
// at the least significant bit (LSB) of its "address". If LSB is 0,
// it's ARM; otherwise, Thumb.
//
// For example, if a symbol `foo` is of type STT_FUNC and has value
// 0x2001, `foo` is a function using Thumb instructions whose address
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
// aligned). Likewise, if a function pointer has value 0x2001, it
// refers a Thumb function at 0x2000.
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
#include <tbb/parallel_sort.h>
namespace mold::elf {
using E = ARM32;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_ARM_NONE:
return 0;
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_TARGET1:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET2:
return *(il32 *)loc;
case R_ARM_THM_JUMP11:
return sign_extend(*(ul16 *)loc, 10) << 1;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL: {
u32 S = bit(*(ul16 *)loc, 10);
u32 J1 = bit(*(ul16 *)(loc + 2), 13);
u32 J2 = bit(*(ul16 *)(loc + 2), 11);
u32 I1 = !(J1 ^ S);
u32 I2 = !(J2 ^ S);
u32 imm10 = bits(*(ul16 *)loc, 9, 0);
u32 imm11 = bits(*(ul16 *)(loc + 2), 10, 0);
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
return sign_extend(val, 24);
}
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_TLS_CALL:
return sign_extend(*(ul32 *)loc & 0x00ff'ffff, 23) << 2;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS: {
u32 imm12 = bits(*(ul32 *)loc, 11, 0);
u32 imm4 = bits(*(ul32 *)loc, 19, 16);
return sign_extend((imm4 << 12) | imm12, 15);
}
case R_ARM_PREL31:
return sign_extend(*(ul32 *)loc, 30);
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS: {
u32 imm4 = bits(*(ul16 *)loc, 3, 0);
u32 i = bit(*(ul16 *)loc, 10);
u32 imm3 = bits(*(ul16 *)(loc + 2), 14, 12);
u32 imm8 = bits(*(ul16 *)(loc + 2), 7, 0);
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
return sign_extend(val, 15);
}
default:
unreachable();
}
}
static void write_mov_imm(u8 *loc, u32 val) {
u32 imm12 = bits(val, 11, 0);
u32 imm4 = bits(val, 15, 12);
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
}
static void write_thm_b_imm(u8 *loc, u32 val) {
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/BL--BLX--immediate-
u32 sign = bit(val, 24);
u32 I1 = bit(val, 23);
u32 I2 = bit(val, 22);
u32 J1 = !I1 ^ sign;
u32 J2 = !I2 ^ sign;
u32 imm10 = bits(val, 21, 12);
u32 imm11 = bits(val, 11, 1);
ul16 *buf = (ul16 *)loc;
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (sign << 10) | imm10;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
static void write_thm_mov_imm(u8 *loc, u32 val) {
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOVT
u32 imm4 = bits(val, 15, 12);
u32 i = bit(val, 11);
u32 imm3 = bits(val, 10, 8);
u32 imm8 = bits(val, 7, 0);
ul16 *buf = (ul16 *)loc;
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
}
template <>
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_ARM_NONE:
break;
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_TARGET1:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET2:
*(ul32 *)loc = val;
break;
case R_ARM_THM_JUMP11:
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
break;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL:
write_thm_b_imm(loc, val);
break;
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS:
write_mov_imm(loc, val);
break;
case R_ARM_PREL31:
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS:
write_thm_mov_imm(loc, val);
break;
default:
unreachable();
}
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe08f'e00e, // 1: add lr, pc, lr
0xe5be'f008, // ldr pc, [lr, #8]!
0x0000'0000, // 2: .word .got.plt - 1b - 8
0xe320'f000, // nop
0xe320'f000, // nop
0xe320'f000, // nop
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
static const ul32 plt_entry[] = {
0xe59f'c004, // 1: ldr ip, 2f
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0x0000'0000, // 2: .word sym@GOT - 1b
};
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
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;
}
// ARM does not use .eh_frame for exception handling. Instead, it uses
// .ARM.exidx and .ARM.extab. So this function is empty.
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {}
// ARM and Thumb branch instructions can jump within ±16 MiB.
static bool is_jump_reachable(i64 val) {
return sign_extend(val, 24) == val;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks =
output_section->thunks;
auto get_trampoline_addr = [&](u64 addr) {
for (;;) {
assert(!thunks.empty());
i64 disp = output_section->shdr.sh_addr + thunks[0]->offset - addr;
if (is_jump_reachable(disp))
return disp;
thunks = thunks.subspan(1);
}
};
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
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 << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = get_addend(*this, rel);
u64 P = get_addr() + rel.r_offset;
u64 T = S & 1;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto get_thumb_thunk_addr = [&] { return get_thunk_addr(i); };
auto get_arm_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_TARGET1:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_ARM_REL32:
*(ul32 *)loc = S + A - P;
break;
case R_ARM_THM_CALL: {
// THM_CALL relocation refers either BL or BLX instruction.
// They are different in only one bit. We need to use BL if
// the jump target is Thumb. Otherwise, use BLX.
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0x8000'f3af; // NOP.W
break;
}
i64 val = S + A - P;
if (is_jump_reachable(val)) {
if (T) {
write_thm_b_imm(loc, val);
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
} else {
write_thm_b_imm(loc, align_to(val, 4));
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
}
} else {
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
}
break;
}
case R_ARM_BASE_PREL:
*(ul32 *)loc = GOT + A - P;
break;
case R_ARM_GOTOFF32:
*(ul32 *)loc = ((S + A) | T) - GOT;
break;
case R_ARM_GOT_PREL:
case R_ARM_TARGET2:
*(ul32 *)loc = GOT + G + A - P;
break;
case R_ARM_GOT_BREL:
*(ul32 *)loc = G + A;
break;
case R_ARM_CALL: {
// Just like THM_CALL, ARM_CALL relocation refers either BL or
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
u64 val = S + A - P;
if (is_jump_reachable(val)) {
if (T)
*(ul32 *)loc = 0xfa00'0000 | (bit(val, 1) << 24) | bits(val, 25, 2);
else
*(ul32 *)loc = 0xeb00'0000 | bits(val, 25, 2);
} else {
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
}
break;
}
case R_ARM_JUMP24:
case R_ARM_PLT32:
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
} else {
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
// takes an immediate; it takes only a register. So if mode switch
// is required, we jump to a linker-synthesized thunk which constructs
// a branch destination in a register and branches to that address.
u64 val = S + A - P;
if (!is_jump_reachable(val) || T)
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP11: {
assert(T);
i64 val = S + A - P;
check(val, -(1 << 11), 1 << 11);
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
break;
}
case R_ARM_THM_JUMP19: {
i64 val = S + A - P;
check(val, -(1 << 19), 1 << 19);
// sign:J2:J1:imm6:imm11:'0'
u32 sign = bit(val, 20);
u32 J2 = bit(val, 19);
u32 J1 = bit(val, 18);
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
*(ul16 *)loc &= 0b1111'1011'1100'0000;
*(ul16 *)loc |= (sign << 10) | imm6;
*(ul16 *)(loc + 2) &= 0b1101'0000'0000'0000;
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
break;
}
case R_ARM_THM_JUMP24:
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0x8000'f3af; // NOP.W
} else {
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
// switch processor mode.
u64 val = S + A - P;
if (!is_jump_reachable(val) || !T)
val = get_thumb_thunk_addr() + A - P;
write_thm_b_imm(loc, val);
}
break;
case R_ARM_MOVW_PREL_NC:
write_mov_imm(loc, ((S + A) | T) - P);
break;
case R_ARM_MOVW_ABS_NC:
write_mov_imm(loc, (S + A) | T);
break;
case R_ARM_THM_MOVW_PREL_NC:
write_thm_mov_imm(loc, ((S + A) | T) - P);
break;
case R_ARM_PREL31: {
i64 val = S + A - P;
check(val, -(1LL << 30), 1LL << 30);
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
}
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov_imm(loc, (S + A) | T);
break;
case R_ARM_MOVT_PREL:
write_mov_imm(loc, (S + A - P) >> 16);
break;
case R_ARM_THM_MOVT_PREL:
write_thm_mov_imm(loc, (S + A - P) >> 16);
break;
case R_ARM_MOVT_ABS:
write_mov_imm(loc, (S + A) >> 16);
break;
case R_ARM_THM_MOVT_ABS:
write_thm_mov_imm(loc, (S + A) >> 16);
break;
case R_ARM_TLS_GD32:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDM32:
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDO32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_ARM_TLS_IE32:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_ARM_TLS_LE32:
*(ul32 *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
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;
} else {
*(ul32 *)loc = S - ctx.tp_addr;
}
break;
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
// BL <tls_trampoline>
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
} else {
// BL -> NOP
*(ul32 *)loc = 0xe320'f000;
}
break;
case R_ARM_THM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
u64 val = align_to(get_trampoline_addr(P + 4), 4);
write_thm_b_imm(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else {
// BL -> NOP.W
*(ul32 *)loc = 0x8000'f3af;
}
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : get_addend(*this, rel);
switch (rel.r_type) {
case R_ARM_ABS32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_MOVT_ABS:
case R_ARM_THM_MOVT_ABS:
case R_ARM_TARGET1:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_ARM_THM_CALL:
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_THM_JUMP24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TARGET2:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_ARM_MOVT_PREL:
case R_ARM_THM_MOVT_PREL:
case R_ARM_PREL31:
scan_pcrel(ctx, sym, rel);
break;
case R_ARM_TLS_GD32:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_ARM_TLS_LDM32:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_ARM_TLS_IE32:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_ARM_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_THM_JUMP11:
case R_ARM_THM_JUMP19:
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_CALL:
case R_ARM_THM_TLS_CALL:
case R_ARM_V4BX:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// 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.
static ul32 hdr[] = {
0xe08e'0000, // add r0, lr, r0
0xe590'1004, // ldr r1, [r0, #4]
0xe12f'ff11, // bx r1
};
// This is a range extension and mode switch thunk.
// It has two entry points: +0 for Thumb and +4 for ARM.
const u8 entry[] = {
// .thumb
0xfc, 0x46, // mov ip, pc
0x60, 0x47, // bx ip # jumps to the following `ldr` insn
// .arm
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, 2f
0x0f, 0xc0, 0x8c, 0xe0, // 1: add ip, ip, pc
0x1c, 0xff, 0x2f, 0xe1, // bx ip
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
};
static_assert(E::thunk_hdr_size == sizeof(hdr));
static_assert(E::thunk_size == sizeof(entry));
memcpy(buf, hdr, sizeof(hdr));
for (i64 i = 0; i < symbols.size(); i++) {
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
memcpy(loc, entry, sizeof(entry));
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;
}
}
// 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.
//
// Other target uses .eh_frame_hdr instead for the same purpose.
// I don't know why only ARM uses the different mechanism, but it's
// likely that it's due to some historical reason.
//
// This function sorts .ARM.exidx records.
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
// .ARM.exidx records consists of a signed 31-bit relative address
// and a 32-bit value. The relative address indicates the start
// address of a function that the record covers. The value is one of
// the followings:
//
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
// 2. a compact unwinding record encoded into a 32-bit value, or
// 3. a 31-bit relative address which points to a larger record in
// the .ARM.extab section.
//
// CANTUNWIND is value 1. The most significant bit is set in (2) but
// not in (3). So we can distinguished them just by looking at a value.
const u32 EXIDX_CANTUNWIND = 1;
struct Entry {
ul32 addr;
ul32 val;
};
if (osec.shdr.sh_size % sizeof(Entry))
Fatal(ctx) << "invalid .ARM.exidx section size";
Entry *ent = (Entry *)(ctx.buf + osec.shdr.sh_offset);
i64 num_entries = osec.shdr.sh_size / sizeof(Entry);
// Entry's addresses are relative to themselves. In order to sort
// records by addresses, we first translate them so that the addresses
// are relative to the beginning of the section.
auto is_relative = [](u32 val) {
return val != EXIDX_CANTUNWIND && !(val & 0x8000'0000);
};
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
return a.addr < b.addr;
});
// Make addresses relative to themselves.
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
});
}
void fixup_arm_exidx_section(Context<E> &ctx) {
Timer t(ctx, "fixup_arm_exidx_section");
auto find_exidx = [&]() -> OutputSection<E> * {
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
return osec;
return nullptr;
};
OutputSection<E> *exidx = find_exidx();
if (!exidx)
return;
// Sort .ARM.exidx contents
sort_exidx(ctx, *exidx);
// .ARM.exidx's sh_link should be set to the .text section index.
// Runtime doesn't care about it, but the binutils's strip command does.
if (ctx.shdr) {
auto find_text = [&]() -> Chunk<E> * {
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->name == ".text")
return chunk;
return nullptr;
};
if (Chunk<E> *text = find_text()) {
exidx->shdr.sh_link = text->shndx;
ctx.shdr->copy_buf(ctx);
}
}
}
} // namespace mold::elf
-461
View File
@@ -1,461 +0,0 @@
// This file implements the PowerPC ELFv2 ABI which was standardized in
// 2014. Modern little-endian PowerPC systems are based on this ABI.
// The ABI is often referred to as "ppc64le". This shouldn't be confused
// with "ppc64" which refers the original, big-endian PowerPC systems.
//
// PPC64 is a bit tricky to support because PC-relative load/store
// instructions are generally not available. Therefore, it's not easy
// for position-independent code to load a value from, for example,
// .got, as we can't do that with [PC + the offset to the .got entry].
//
// We can get the program counter by the following four instructions
//
// mflr r1 // save the current link register to r1
// bl .+4 // branch to the next instruction as if it were a function
// mflr r0 // copy the return address to r0
// mtlr r1 // restore the original link register value
//
// , but that's too expensive to do if we do this for each load/store.
//
// As a workaround, most functions are compiled in such a way that r2 is
// assumed to always contain the address of .got + 0x8000. With this, we
// can for example load the first entry of .got with a single instruction
// `lw r0, -0x8000(r2)`. r2 is called the TOC pointer.
//
// There's only one .got for each ELF module. Therefore, if a callee is in
// the same ELF module, r2 doesn't have to be recomputed. Most function
// calls are usually within the same ELF module, so this mechanism is
// efficient.
//
// In PPC64, a function usually have two entry points, global and local.
// The global entry point is usually 8 bytes precedes the local entry
// point. In between is the following instructions:
//
// addis r2, r12, .TOC.@ha
// addi r2, r2, .TOC.@lo + 4;
//
// The global entry point assumes that the address of itself is in r12,
// and it computes its own TOC pointer from r12. It's easy to do so for
// the callee because the offset between its .got + 0x8000 and the
// function is known at link-time. The above code sequence then falls
// through to the local entry point that assumes r2 is .got + 0x8000.
//
// So, if a callee's TOC pointer is different from the current one
// (e.g. calling a function in another .so), we first load the callee's
// address to r12 (e.g. from .got.plt with a r2-relative load) and branch
// to that address. Then the callee computes its own TOC pointer using
// r12.
//
// Note on section names: the PPC64 psABI uses a weird naming convention
// which calls .got.plt .plt. We ignored that part because it's just
// confusing. Since the runtime only cares about segments, we should be
// able to name sections whatever we want.
//
// https://openpowerfoundation.org/specifications/64bitelfabi/
#include "mold.h"
namespace mold::elf {
using E = PPC64V2;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
// reach. Thunks read addresses from .got.plt and jump there.
// Therefore, once PLT symbols are resolved and final addresses are
// written to .got.plt, thunks just skip .plt and directly jump to the
// resolved addresses.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
// Get PC
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
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
0x7800'f082, // rldicl r0, r0, 62, 2
// 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;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
// When the control is transferred to a PLT entry, the PLT entry's
// address is already set to %r12 by the caller.
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx);
*(ul32 *)buf = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
}
// .plt.got is not necessary on PPC64 because range extension thunks
// directly read GOT entries and jump there.
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_PPC64_ADDR64:
*(ul64 *)loc = val;
break;
case R_PPC64_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_PPC64_REL64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
i64 val = sym.esym().ppc_local_entry;
assert(val <= 7);
if (val == 0 || val == 1)
return 0;
if (val == 7)
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
return 1 << val;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
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 << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
u64 TOC = ctx.extra.TOC->value;
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (name() == ".toc")
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_PPC64_TOC16_HA:
*(ul16 *)loc = ha(S + A - TOC);
break;
case R_PPC64_TOC16_LO:
*(ul16 *)loc = lo(S + A - TOC);
break;
case R_PPC64_TOC16_DS:
case R_PPC64_TOC16_LO_DS:
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
break;
case R_PPC64_REL24: {
i64 val = S + A - P + get_local_entry_offset(ctx, sym);
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
val = get_thunk_addr(i) + A - P;
check(val, -(1 << 25), 1 << 25);
*(ul32 *)loc |= bits(val, 25, 2) << 2;
// If a callee is an external function, PLT saves %r2 to the
// caller's r2 save slot. We need to restore it after function
// return. To do so, there's usually a NOP as a placeholder
// after a BL. 0x6000'0000 is a NOP.
if (sym.has_plt(ctx) && *(ul32 *)(loc + 4) == 0x6000'0000)
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
break;
}
case R_PPC64_REL64:
*(ul64 *)loc = S + A - P;
break;
case R_PPC64_REL16_HA:
*(ul16 *)loc = ha(S + A - P);
break;
case R_PPC64_REL16_LO:
*(ul16 *)loc = lo(S + A - P);
break;
case R_PPC64_PLT16_HA:
*(ul16 *)loc = ha(G + GOT - TOC);
break;
case R_PPC64_PLT16_HI:
*(ul16 *)loc = hi(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO:
*(ul16 *)loc = lo(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO_DS:
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
break;
case R_PPC64_GOT_TPREL16_HA:
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_HA:
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_LO:
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD16_HA:
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD16_LO:
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_DTPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_DTPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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 << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
case R_PPC64_ADDR32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
break;
}
case R_PPC64_DTPREL64:
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (name() == ".toc")
scan_toc_rel(ctx, sym, rel);
else
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_PPC64_PLT16_HA:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSGD16_HA:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSLD16_HA:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO:
case R_PPC64_TOC16_LO_DS:
case R_PPC64_TOC16_DS:
case R_PPC64_REL16_HA:
case R_PPC64_REL16_LO:
case R_PPC64_PLT16_HI:
case R_PPC64_PLT16_LO:
case R_PPC64_PLT16_LO_DS:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_LO:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// If the destination is PLT, we read an address from .got.plt or .got
// and jump there.
static const ul32 plt_thunk[] = {
// Save r2 to the r2 save slot reserved in the caller's stack frame
0xf841'0018, // std r2, 24(r1)
// Jump to a PLT entry
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
};
// If the destination is a non-imported function, we directly jump
// to its local entry point.
static const ul32 local_thunk[] = {
// Jump to a local entry point
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
0x6000'0000, // nop
};
static_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];
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
if (sym.has_plt(ctx)) {
memcpy(loc, plt_thunk, sizeof(plt_thunk));
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
i64 val = got - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx) + get_local_entry_offset(ctx, sym) -
ctx.extra.TOC->value;
loc[0] |= higha(val);
loc[1] |= lo(val);
}
}
}
} // namespace mold::elf
-950
View File
@@ -1,950 +0,0 @@
// RISC-V is a clean RISC ISA. It supports PC-relative load/store for
// position-independent code. Its 32-bit and 64-bit ISAs are almost
// identical. That is, you can think RV32 as a RV64 without 64-bit
// operations. In this file, we support both RV64 and RV32.
//
// RISC-V is essentially little-endian, but the big-endian version is
// available as an extension. GCC supports `-mbig-endian` to generate
// big-endian code. Even in big-endian mode, machine instructions are
// defined to be encoded in little-endian, though. Only the behavior of
// load/store instructions are different between LE RISC-V and BE RISC-V.
//
// From the linker's point of view, the RISC-V's psABI is unique because
// sections in input object files can be shrunk while being copied to the
// output file. That is contrary to other psABIs in which sections are an
// atomic unit of copying. Let me explain it in more details.
//
// Since RISC-V instructions are 16-bit or 32-bit long, there's no way to
// embed a very large immediate into a branch instruction. In fact, JAL
// (jump and link) instruction can jump to only within PC ± 1 MiB because
// its immediate is only 21 bits long. If the destination is out of its
// reach, we need to use two instructions instead; the first instruction
// being AUIPC which sets upper 20 bits to a register and the second being
// JALR with a 12-bit immediate and the register. Combined, they specify a
// 32 bits displacement.
//
// Other RISC ISAs have the same limitation, and they solved the problem by
// letting the linker create so-called "range extension thunks". It works as
// follows: the compiler optimistically emits single jump instructions for
// function calls. If the linker finds that a branch target is out of reach,
// it emits a small piece of machine code near the branch instruction and
// redirect the branch to the linker-synthesized code. The code constructs a
// full 32-bit address in a register and jump to the destination. That
// linker-synthesized code is called "range extension thunks" or just
// "thunks".
//
// The RISC-V psABI is unique that it works the other way around. That is,
// for RISC-V, the compiler always emits two instructions (AUIPC + JAL) for
// function calls. If the linker finds the destination is reachable with a
// single instruction, it replaces the two instructions with the one and
// shrink the section size by one instruction length, instead of filling the
// gap with a nop.
//
// With the presence of this relaxation, sections can no longer be
// considered as an atomic unit. If we delete 4 bytes from the middle of a
// section, all contents after that point needs to be shifted by 4. Symbol
// values and relocation offsets have to be adjusted accordingly if they
// refer past the deleted bytes.
//
// In mold, we use `r_deltas` to memorize how many bytes have be adjusted
// for relocations. For symbols, we directly mutate their `value` member.
//
// RISC-V object files tend to have way more relocations than those for
// other targets. This is because all branches, including ones that jump
// within the same section, are explicitly expressed with relocations.
// Here is why we need them: all control-flow statements such as `if` or
// `for` are implemented using branch instructions. For other targets, the
// compiler doesn't emit relocations for such branches because they know
// at compile-time exactly how many bytes has to be skipped. That's not
// true to RISC-V because the linker may delete bytes between a branch and
// its destination. Therefore, all branches including in-section ones have
// to be explicitly expressed with relocations.
//
// Note that this mechanism only shrink sections and never enlarge, as
// the compiler always emits the longest instruction sequence. This
// makes the linker implementation a bit simpler because we don't need
// to worry about oscillation.
//
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
static u32 itype(u32 val) {
return val << 20;
}
static u32 stype(u32 val) {
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
}
static u32 btype(u32 val) {
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
}
static u32 utype(u32 val) {
// U-type instructions are used in combination with I-type
// instructions. U-type insn sets an immediate to the upper 20-bits
// of a register. I-type insn sign-extends a 12-bits immediate and
// adds it to a register value to construct a complete value. 0x800
// is added here to compensate for the sign-extension.
return (val + 0x800) & 0xffff'f000;
}
static u32 jtype(u32 val) {
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
}
static u32 cbtype(u32 val) {
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
bit(val, 1) << 3 | bit(val, 5) << 2;
}
static u32 cjtype(u32 val) {
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
bit(val, 1) << 3 | bit(val, 5) << 2;
}
static void write_itype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
*(ul32 *)loc |= itype(val);
}
static void write_stype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
*(ul32 *)loc |= stype(val);
}
static void write_btype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
*(ul32 *)loc |= btype(val);
}
static void write_utype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
*(ul32 *)loc |= utype(val);
}
static void write_jtype(u8 *loc, u32 val) {
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
*(ul32 *)loc |= jtype(val);
}
static void write_cbtype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'000'111'00000'11;
*(ul16 *)loc |= cbtype(val);
}
static void write_cjtype(u8 *loc, u32 val) {
*(ul16 *)loc &= 0b111'00000000000'11;
*(ul16 *)loc |= cjtype(val);
}
// 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;
*(ul32 *)loc |= rs1 << 15;
}
template <typename E>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn_64[] = {
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
0x0003'be03, // ld t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
0xfd43'0313, // addi t1, t1, -44 # .plt entry
0x0003'8293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
0x0013'5313, // srli t1, t1, 1 # .plt entry offset
0x0082'b283, // ld t0, 8(t0) # link map
0x000e'0067, // jr t3
};
static const ul32 insn_32[] = {
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
0x0003'ae03, // lw t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
0xfd43'0313, // addi t1, t1, -44 # .plt entry
0x0003'8293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
0x0023'5313, // srli t1, t1, 2 # .plt entry offset
0x0042'a283, // lw t0, 4(t0) # link map
0x000e'0067, // jr t3
};
if constexpr (E::is_64)
memcpy(buf, insn_64, sizeof(insn_64));
else
memcpy(buf, insn_32, sizeof(insn_32));
u64 gotplt = ctx.gotplt->shdr.sh_addr;
u64 plt = ctx.plt->shdr.sh_addr;
write_utype(buf, gotplt - plt);
write_itype(buf + 8, gotplt - plt);
write_itype(buf + 16, gotplt - plt);
}
static const ul32 plt_entry_64[] = {
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
0x000e'3e03, // ld t3, %pcrel_lo(1b)(t3)
0x000e'0367, // jalr t1, t3
0x0000'0013, // nop
};
static const ul32 plt_entry_32[] = {
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
0x000e'2e03, // lw t3, %pcrel_lo(1b)(t3)
0x000e'0367, // jalr t1, t3
0x0000'0013, // nop
};
template <typename E>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if constexpr (E::is_64)
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
else
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
u64 gotplt = sym.get_gotplt_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
write_utype(buf, gotplt - plt);
write_itype(buf + 4, gotplt - plt);
}
template <typename E>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if constexpr (E::is_64)
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
else
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
u64 got = sym.get_got_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
write_utype(buf, got - plt);
write_itype(buf + 4, got - plt);
}
template <typename E>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_RISCV_ADD32:
*(U32<E> *)loc += val;
break;
case R_RISCV_SUB8:
*loc -= val;
break;
case R_RISCV_SUB16:
*(U16<E> *)loc -= val;
break;
case R_RISCV_SUB32:
*(U32<E> *)loc -= val;
break;
case R_RISCV_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
break;
case R_RISCV_SET6:
*loc = (*loc & 0b1100'0000) | (val & 0b0011'1111);
break;
case R_RISCV_SET8:
*loc = val;
break;
case R_RISCV_SET16:
*(U16<E> *)loc = val;
break;
case R_RISCV_SET32:
*(U32<E> *)loc = val;
break;
case R_RISCV_32_PCREL:
*(U32<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);
auto get_r_delta = [&](i64 idx) {
return extra.r_deltas.empty() ? 0 : extra.r_deltas[idx];
};
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_RISCV_RELAX)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
i64 r_offset = rel.r_offset - get_r_delta(i);
i64 removed_bytes = get_r_delta(i + 1) - get_r_delta(i);
u8 *loc = base + r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_RISCV_32:
if constexpr (E::is_64)
*(U32<E> *)loc = S + A;
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_RISCV_64:
assert(E::is_64);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_RISCV_BRANCH: {
i64 val = S + A - P;
check(val, -(1 << 12), 1 << 12);
write_btype(loc, val);
break;
}
case R_RISCV_JAL: {
i64 val = S + A - P;
check(val, -(1 << 20), 1 << 20);
write_jtype(loc, val);
break;
}
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
if (removed_bytes == 4) {
// auipc + jalr -> jal
*(ul32 *)loc = (rd << 7) | 0b1101111;
write_jtype(loc, S + A - P);
} else if (removed_bytes == 6 && rd == 0) {
// auipc + jalr -> c.j
*(ul16 *)loc = 0b101'00000000000'01;
write_cjtype(loc, S + A - P);
} 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);
} else {
assert(removed_bytes == 0);
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);
}
break;
}
case R_RISCV_GOT_HI20:
*(ul32 *)loc = G + GOT + A - P;
break;
case R_RISCV_TLS_GOT_HI20:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_RISCV_TLS_GD_HI20:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_RISCV_PCREL_HI20:
if (sym.esym().is_undef_weak()) {
// Calling an undefined weak symbol does not make sense.
// We make such call into an infinite loop. This should
// help debugging of a faulty program.
*(ul32 *)loc = 0;
} else {
*(ul32 *)loc = S + A - P;
}
break;
case R_RISCV_HI20:
assert(removed_bytes == 0 || removed_bytes == 4);
if (removed_bytes == 0) {
i64 val = S + A;
check(val, -(1LL << 31), 1LL << 31);
write_utype(loc, val);
}
break;
case R_RISCV_LO12_I:
case R_RISCV_LO12_S: {
i64 val = S + A;
if (rel.r_type == R_RISCV_LO12_I)
write_itype(loc, val);
else
write_stype(loc, val);
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
// accessible relative to the zero register. If the upper 20 bits
// are all zero, the corresponding LUI might have been removed.
if (sign_extend(val, 11) == val)
set_rs1(loc, 0);
break;
}
case R_RISCV_TPREL_HI20:
assert(removed_bytes == 0 || removed_bytes == 4);
if (removed_bytes == 0)
write_utype(loc, S + A - ctx.tp_addr);
break;
case R_RISCV_TPREL_ADD:
break;
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S: {
i64 val = S + A - ctx.tp_addr;
if (rel.r_type == R_RISCV_TPREL_LO12_I)
write_itype(loc, val);
else
write_stype(loc, val);
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(tp)` if the address is
// directly accessible using tp. tp is x4.
if (sign_extend(val, 11) == val)
set_rs1(loc, 4);
break;
}
case R_RISCV_ADD8:
loc += S + A;
break;
case R_RISCV_ADD16:
*(U16<E> *)loc += S + A;
break;
case R_RISCV_ADD32:
*(U32<E> *)loc += S + A;
break;
case R_RISCV_ADD64:
*(U64<E> *)loc += S + A;
break;
case R_RISCV_SUB8:
loc -= S + A;
break;
case R_RISCV_SUB16:
*(U16<E> *)loc -= S + A;
break;
case R_RISCV_SUB32:
*(U32<E> *)loc -= S + A;
break;
case R_RISCV_SUB64:
*(U64<E> *)loc -= S + A;
break;
case R_RISCV_ALIGN: {
// A R_RISCV_ALIGN is followed by a NOP sequence. We need to remove
// zero or more bytes so that the instruction after R_RISCV_ALIGN is
// aligned to a given alignment boundary.
//
// We need to guarantee that the NOP sequence is valid after byte
// removal (e.g. we can't remove the first 2 bytes of a 4-byte NOP).
// For the sake of simplicity, we always rewrite the entire NOP sequence.
i64 padding_bytes = rel.r_addend - removed_bytes;
assert((padding_bytes & 1) == 0);
i64 i = 0;
for (; i <= padding_bytes - 4; i += 4)
*(ul32 *)(loc + i) = 0x0000'0013; // nop
if (i < padding_bytes)
*(ul16 *)(loc + i) = 0x0001; // c.nop
break;
}
case R_RISCV_RVC_BRANCH: {
i64 val = S + A - P;
check(val, -(1 << 8), 1 << 8);
write_cbtype(loc, val);
break;
}
case R_RISCV_RVC_JUMP: {
i64 val = S + A - P;
check(val, -(1 << 11), 1 << 11);
write_cjtype(loc, val);
break;
}
case R_RISCV_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
break;
case R_RISCV_SET6:
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
break;
case R_RISCV_SET8:
*loc = S + A;
break;
case R_RISCV_SET16:
*(U16<E> *)loc = S + A;
break;
case R_RISCV_SET32:
*(U32<E> *)loc = S + A;
break;
case R_RISCV_32_PCREL:
*(U32<E> *)loc = S + A - P;
break;
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
// These relocations are handled in the next loop.
break;
default:
unreachable();
}
}
// Handle PC-relative LO12 relocations. In the above loop, pcrel HI20
// relocations overwrote instructions with full 32-bit values to allow
// their corresponding pcrel LO12 relocations to read their values.
for (i64 i = 0; i < rels.size(); i++) {
switch (rels[i].r_type) {
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S: {
Symbol<E> &sym = *file.symbols[rels[i].r_sym];
assert(sym.get_input_section() == this);
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
u32 val = *(ul32 *)(base + sym.value);
if (rels[i].r_type == R_RISCV_PCREL_LO12_I)
write_itype(loc, val);
else
write_stype(loc, val);
}
}
}
// Restore the original instructions pcrel HI20 relocations overwrote.
for (i64 i = 0; i < rels.size(); i++) {
switch (rels[i].r_type) {
case R_RISCV_GOT_HI20:
case R_RISCV_PCREL_HI20:
case R_RISCV_TLS_GOT_HI20:
case R_RISCV_TLS_GD_HI20: {
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
u32 val = *(ul32 *)loc;
memcpy(loc, contents.data() + rels[i].r_offset, 4);
write_utype(loc, val);
}
}
}
}
template <typename E>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_RISCV_32:
*(U32<E> *)loc = S + A;
break;
case R_RISCV_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U64<E> *)loc = *val;
else
*(U64<E> *)loc = S + A;
break;
case R_RISCV_ADD8:
*loc += S + A;
break;
case R_RISCV_ADD16:
*(U16<E> *)loc += S + A;
break;
case R_RISCV_ADD32:
*(U32<E> *)loc += S + A;
break;
case R_RISCV_ADD64:
*(U64<E> *)loc += S + A;
break;
case R_RISCV_SUB8:
*loc -= S + A;
break;
case R_RISCV_SUB16:
*(U16<E> *)loc -= S + A;
break;
case R_RISCV_SUB32:
*(U32<E> *)loc -= S + A;
break;
case R_RISCV_SUB64:
*(U64<E> *)loc -= S + A;
break;
case R_RISCV_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
break;
case R_RISCV_SET6:
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
break;
case R_RISCV_SET8:
*loc = S + A;
break;
case R_RISCV_SET16:
*(U16<E> *)loc = S + A;
break;
case R_RISCV_SET32:
*(U32<E> *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
}
template <typename E>
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
// If a section is not relaxed, we can copy it as a one big chunk.
if (extra.r_deltas.empty()) {
uncompress_to(ctx, buf);
return;
}
// Memory-allocated sections may be relaxed, so copy each segment
// individually.
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 pos = 0;
for (i64 i = 0; i < rels.size(); i++) {
i64 delta = extra.r_deltas[i + 1] - extra.r_deltas[i];
if (delta == 0)
continue;
assert(delta > 0);
const ElfRel<E> &r = rels[i];
memcpy(buf, contents.data() + pos, r.r_offset - pos);
buf += r.r_offset - pos;
pos = r.r_offset + delta;
}
memcpy(buf, contents.data() + pos, contents.size() - pos);
}
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);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_RISCV_32:
if constexpr (E::is_64)
scan_absrel(ctx, sym, rel);
else
scan_dyn_absrel(ctx, sym, rel);
break;
case R_RISCV_HI20:
scan_absrel(ctx, sym, rel);
break;
case R_RISCV_64:
if constexpr (!E::is_64)
Fatal(ctx) << *this << ": R_RISCV_64 cannot be used on RV32";
scan_dyn_absrel(ctx, sym, rel);
break;
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_RISCV_GOT_HI20:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_RISCV_TLS_GOT_HI20:
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_RISCV_TLS_GD_HI20:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_RISCV_32_PCREL:
scan_pcrel(ctx, sym, rel);
break;
case R_RISCV_BRANCH:
case R_RISCV_JAL:
case R_RISCV_PCREL_HI20:
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S:
case R_RISCV_TPREL_ADD:
case R_RISCV_ADD8:
case R_RISCV_ADD16:
case R_RISCV_ADD32:
case R_RISCV_ADD64:
case R_RISCV_SUB8:
case R_RISCV_SUB16:
case R_RISCV_SUB32:
case R_RISCV_SUB64:
case R_RISCV_ALIGN:
case R_RISCV_RVC_BRANCH:
case R_RISCV_RVC_JUMP:
case R_RISCV_RELAX:
case R_RISCV_SUB6:
case R_RISCV_SET6:
case R_RISCV_SET8:
case R_RISCV_SET16:
case R_RISCV_SET32:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <typename E>
static bool is_resizable(Context<E> &ctx, 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
// because `shrink_section` may increase a distance between a branch
// instruction and an absolute symbol. Branching to an absolute
// location is extremely rare in real code, though.
if (sym.is_absolute())
return INT32_MAX;
// Likewise, relocations against weak undefined symbols won't be relaxed.
if (sym.esym().is_undef_weak())
return INT32_MAX;
// Compute a distance between the relocated place and the symbol.
i64 S = sym.get_addr(ctx);
i64 A = rel.r_addend;
i64 P = isec.get_addr() + rel.r_offset;
return S + A - P;
}
// 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);
i64 delta = 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &r = rels[i];
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
isec.extra.r_deltas[i] = delta;
// Handling R_RISCV_ALIGN is mandatory.
//
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate some
// or all of the instructions so that the instruction that immediately
// follows the NOPs is aligned to a specified alignment boundary.
if (r.r_type == R_RISCV_ALIGN) {
// The total bytes of NOPs is stored to r_addend, so the next
// instruction is r_addend away.
u64 loc = isec.get_addr() + r.r_offset - delta;
u64 next_loc = loc + r.r_addend;
u64 alignment = bit_ceil(r.r_addend + 1);
assert(alignment <= (1 << isec.p2align));
delta += next_loc - align_to(loc, alignment);
continue;
}
// Handling other relocations is optional.
if (!ctx.arg.relax || i == rels.size() - 1 ||
rels[i + 1].r_type != R_RISCV_RELAX)
continue;
// Linker-synthesized symbols haven't been assigned their final
// values when we are shrinking sections because actual values can
// be computed only after we fix the file layout. Therefore, we
// assume that relocations against such symbols are always
// non-relaxable.
if (sym.file == ctx.internal_obj)
continue;
switch (r.r_type) {
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
// These relocations refer an AUIPC + JALR instruction pair to
// allow to jump to anywhere in PC ± 2 GiB. If the jump target is
// close enough to PC, we can use C.J, C.JAL or JAL instead.
i64 dist = compute_distance(ctx, sym, isec, r);
if (dist & 1)
break;
std::string_view contents = isec.contents;
i64 rd = get_rd(*(ul32 *)(contents.data() + r.r_offset + 4));
if (rd == 0 && sign_extend(dist, 11) == dist && use_rvc) {
// If rd is x0 and the jump target is within ±2 KiB, we can use
// C.J, saving 6 bytes.
delta += 6;
} else if (rd == 1 && sign_extend(dist, 11) == dist && use_rvc && !E::is_64) {
// 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;
} else if (sign_extend(dist, 20) == dist) {
// If the jump target is within ±1 MiB, we can use JAL.
delta += 4;
}
break;
}
case R_RISCV_HI20:
// If the upper 20 bits are all zero, we can remove LUI.
// The corresponding instructions referred by LO12_I/LO12_S
// relocations will use the zero register instead.
if (bits(sym.get_addr(ctx), 31, 12) == 0)
delta += 4;
break;
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_ADD: {
// These relocations are used to materialize the upper 20 bits of
// an address relative to the thread pointer as follows:
//
// lui a5,%tprel_hi(foo) # R_RISCV_TPREL_HI20 (symbol)
// add a5,a5,tp,%tprel_add(foo) # R_RISCV_TPREL_ADD (symbol)
//
// Then thread-local variable `foo` is accessed with a 12-bit offset
// like this:
//
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
//
// However, if the offset is ±2 KiB, we don't need to materialize
// the upper 20 bits in a register. We can instead access the
// thread-local variable directly with TP like this:
//
// sw t0,%tprel_lo(foo)(tp)
//
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
if (sign_extend(val, 11) == val)
delta += 4;
break;
}
}
}
isec.extra.r_deltas[rels.size()] = delta;
isec.sh_size -= delta;
}
// Shrink sections by interpreting relocations.
//
// This operation seems to be optional, because by default longest
// instructions are being used. However, calling this function is actually
// mandatory because of R_RISCV_ALIGN. R_RISCV_ALIGN is a directive to the
// linker to align the location referred to by the relocation to a
// specified byte boundary. We at least have to interpret them to satisfy
// the alignment constraints.
template <typename E>
i64 riscv_resize_sections(Context<E> &ctx) {
Timer t(ctx, "riscv_resize_sections");
// True if we can use the 2-byte instructions. This is usually true on
// Unix because RV64GC is generally considered the baseline hardware.
bool use_rvc = get_eflags(ctx) & EF_RISCV_RVC;
// Find all the relocations that can be relaxed.
// 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()))
shrink_section(ctx, *isec, use_rvc);
});
// Fix symbol values.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file != file)
continue;
InputSection<E> *isec = sym->get_input_section();
if (!isec || isec->extra.r_deltas.empty())
continue;
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
auto it = std::lower_bound(rels.begin(), rels.end(), sym->value,
[&](const ElfRel<E> &r, u64 val) {
return r.r_offset < val;
});
sym->value -= isec->extra.r_deltas[it - rels.begin()];
}
});
// Re-compute section offset again to finalize them.
compute_section_sizes(ctx);
return set_osec_offsets(ctx);
}
#define INSTANTIATE(E) \
template void write_plt_header(Context<E> &, u8 *); \
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
template void \
EhFrameSection<E>::apply_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
template void InputSection<E>::copy_contents_riscv(Context<E> &, u8 *); \
template void InputSection<E>::scan_relocations(Context<E> &); \
template i64 riscv_resize_sections(Context<E> &);
INSTANTIATE(RV64LE);
INSTANTIATE(RV64BE);
INSTANTIATE(RV32LE);
INSTANTIATE(RV32BE);
} // namespace mold::elf
-752
View File
@@ -1,752 +0,0 @@
// Supporting x86-64 is straightforward. Unlike its predecessor, i386,
// x86-64 supports PC-relative addressing for position-independent code.
// Being CISC, its instructions are variable in size. Branch instructions
// take 4 bytes offsets, so we don't need range extension thunks.
//
// The psABI specifies %r11 as neither caller- nor callee-saved. It's
// intentionally left out so that we can use it as a scratch register in
// PLT.
//
// Thread Pointer (TP) is stored not to a general-purpose register but to
// FS segment register. Segment register is a 64-bits register which can
// be used as a base address for memory access. Each thread has a unique
// FS value, and they access their thread-local variables relative to FS
// as %fs:offset_from_tp.
//
// The value of a segment register itself is not generally readable from
// the user space. As a workaround, libc initializes %fs:0 (the first word
// referenced by FS) to the value of %fs itself. So we can obtain TP just
// by `mov %fs:0, %rax` if we need it.
//
// For historical reasons, TP points past the end of the TLS block on x86.
// This is contrary to other psABIs which usually use the beginning of the
// TLS block as TP (with some addend). As a result, offsets from TP to
// thread-local variables (TLVs) in the main executable are all negative.
//
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
#include "mold.h"
namespace mold::elf {
using E = X86_64;
// This is a security-enhanced version of the regular PLT. The PLT
// header and each PLT entry starts with endbr64 for the Intel's
// control-flow enforcement security mechanism.
//
// Note that our IBT-enabled PLT instruction sequence is different
// from the one used in GNU ld. GNU's IBTPLT implementation uses two
// separate sections (.plt and .plt.sec) in which one PLT entry takes
// 32 bytes in total. Our IBTPLT consists of just .plt and each entry
// is 16 bytes long.
//
// Our PLT entry clobbers %r11, but that's fine because the resolver
// function (_dl_runtime_resolve) clobbers %r11 anyway.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0x41, 0x53, // push %r11
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
}
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
};
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;
}
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;
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_X86_64_32:
*(ul32 *)loc = val;
break;
case R_X86_64_64:
*(ul64 *)loc = val;
break;
case R_X86_64_PC32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_X86_64_PC64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static u32 relax_gotpcrelx(u8 *loc) {
switch ((loc[0] << 8) | loc[1]) {
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
}
return 0;
}
static u32 relax_rex_gotpcrelx(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
}
return 0;
}
static u32 relax_gottpoff(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
}
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;
}
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
// mapped to memory at runtime) based on the result of
// scan_relocations().
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
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 write32 = [&](u64 val) {
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
};
auto write32s = [&](u64 val) {
check(val, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val;
};
u64 S = sym.get_addr(ctx);
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;
switch (rel.r_type) {
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
break;
}
case R_X86_64_32:
write32(S + A);
break;
case R_X86_64_32S:
write32s(S + A);
break;
case R_X86_64_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_X86_64_PC8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*loc = val;
break;
}
case R_X86_64_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
break;
}
case R_X86_64_PC32:
case R_X86_64_PLT32:
write32s(S + A - P);
break;
case R_X86_64_PC64:
*(ul64 *)loc = S + A - P;
break;
case R_X86_64_GOT32:
write32s(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;
break;
case R_X86_64_GOTPC32:
write32s(GOTPLT + A - P);
break;
case R_X86_64_GOTPC64:
*(ul64 *)loc = GOTPLT + A - P;
break;
case R_X86_64_GOTPCREL:
write32s(G + GOTPLT + A - P);
break;
case R_X86_64_GOTPCREL64:
*(ul64 *)loc = G + GOTPLT + A - P;
break;
case R_X86_64_GOTPCRELX:
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
u32 insn = relax_gotpcrelx(loc - 2);
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
}
break;
case R_X86_64_REX_GOTPCRELX:
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
u32 insn = relax_rex_gotpcrelx(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
}
break;
case R_X86_64_TLSGD:
if (sym.has_tlsgd(ctx)) {
write32s(sym.get_tlsgd_addr(ctx) + A - P);
} else {
// Relax GD to LE. If we are creating an exectuable, the offset of
// a thread-local variable from TP is a link-time constant. So we
// don't need to call __tls_get_addr to obtain the address of a TLV.
i64 val = S - ctx.tp_addr;
assert(A == -4);
check(val, -(1LL << 31), 1LL << 31);
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
//
// or
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
};
memcpy(loc - 4, insn, sizeof(insn));
*(ul32 *)(loc + 8) = val;
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 9) = val;
break;
}
default:
unreachable();
}
i++;
}
break;
case R_X86_64_TLSLD:
if (ctx.got->has_tlsld(ctx)) {
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
} else {
// Relax LD to LE. If we are creating an executable, we don't need
// to call __tls_get_addr to obtain the address of the beginning
// of the current TLS block. TP points past the end of the TLS
// block, and we know the exact size of the TLS block we have
// created, so we can just subtract it from TP.
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// e8 00 00 00 00 call __tls_get_addr
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x90, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
default:
unreachable();
}
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
assert(A == -4);
i++;
}
break;
case R_X86_64_DTPOFF32:
write32s(S + A - ctx.dtp_addr);
break;
case R_X86_64_DTPOFF64:
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_X86_64_TPOFF32:
write32s(S + A - ctx.tp_addr);
break;
case R_X86_64_TPOFF64:
*(ul64 *)loc = S + A - ctx.tp_addr;
break;
case R_X86_64_GOTTPOFF:
if (sym.has_gottp(ctx)) {
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_gottpoff(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S - ctx.tp_addr);
assert(A == -4);
}
break;
case R_X86_64_GOTPC32_TLSDESC:
if (sym.has_tlsdesc(ctx)) {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
} else {
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S - ctx.tp_addr);
assert(A == -4);
}
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
break;
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();
}
}
}
// This function is responsible for applying relocations against
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
// at runtime).
//
// Relocations against non-SHF_ALLOC sections are much easier to
// handle than that against SHF_ALLOC sections. It is because, since
// they are not mapped to memory, they don't contain any variable or
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
// mostly debug info sections.
//
// Relocations against non-SHF_ALLOC sections are not scanned by
// scan_relocations.
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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 write32 = [&](u64 val) {
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
};
auto write32s = [&](u64 val) {
check(val, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val;
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
break;
}
case R_X86_64_32:
write32(S + A);
break;
case R_X86_64_32S:
write32s(S + A);
break;
case R_X86_64_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
case R_X86_64_DTPOFF32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
write32s(S + A - ctx.dtp_addr);
break;
case R_X86_64_DTPOFF64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
break;
case R_X86_64_SIZE64:
*(ul64 *)loc = sym.esym().st_size + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
}
// Linker has to create data structures in an output file to apply
// some type of relocations. For example, if a relocation refers a GOT
// or a PLT entry of a symbol, linker has to create an entry in .got
// or in .plt for that symbol. In order to fix the file layout, we
// need to scan relocations.
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_X86_64_8:
case R_X86_64_16:
case R_X86_64_32:
case R_X86_64_32S:
scan_absrel(ctx, sym, rel);
break;
case R_X86_64_64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_X86_64_PC8:
case R_X86_64_PC16:
case R_X86_64_PC32:
case R_X86_64_PC64:
scan_pcrel(ctx, sym, rel);
break;
case R_X86_64_GOT32:
case R_X86_64_GOT64:
case R_X86_64_GOTPC32:
case R_X86_64_GOTPC64:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCREL64:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_X86_64_GOTPCRELX: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_gotpcrelx(loc - 2);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_X86_64_REX_GOTPCRELX: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_X86_64_PLT32:
case R_X86_64_PLTOFF64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_X86_64_TLSGD: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (relax_tlsgd(ctx, sym))
i++;
else
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
}
case R_X86_64_TLSLD: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (relax_tlsld(ctx))
i++;
else
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
}
case R_X86_64_GOTTPOFF: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
!sym.is_imported && relax_gottpoff(loc - 3);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
}
case R_X86_64_GOTPC32_TLSDESC: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
<< " against an invalid code sequence";
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
}
case R_X86_64_GOTOFF64:
case R_X86_64_DTPOFF32:
case R_X86_64_DTPOFF64:
case R_X86_64_TPOFF32:
case R_X86_64_TPOFF64:
case R_X86_64_SIZE32:
case R_X86_64_SIZE64:
case R_X86_64_TLSDESC_CALL:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
} // namespace mold::elf
-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() == 0xffffffff)
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
i64 len = *(U32<E> *)data.data() + 4;
vec.push_back(data.substr(0, len));
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>
inline 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
-914
View File
@@ -1,914 +0,0 @@
#include "mold.h"
namespace mold::elf {
template <>
std::string rel_to_string<X86_64>(u32 r_type) {
switch (r_type) {
case R_X86_64_NONE: return "R_X86_64_NONE";
case R_X86_64_64: return "R_X86_64_64";
case R_X86_64_PC32: return "R_X86_64_PC32";
case R_X86_64_GOT32: return "R_X86_64_GOT32";
case R_X86_64_PLT32: return "R_X86_64_PLT32";
case R_X86_64_COPY: return "R_X86_64_COPY";
case R_X86_64_GLOB_DAT: return "R_X86_64_GLOB_DAT";
case R_X86_64_JUMP_SLOT: return "R_X86_64_JUMP_SLOT";
case R_X86_64_RELATIVE: return "R_X86_64_RELATIVE";
case R_X86_64_GOTPCREL: return "R_X86_64_GOTPCREL";
case R_X86_64_32: return "R_X86_64_32";
case R_X86_64_32S: return "R_X86_64_32S";
case R_X86_64_16: return "R_X86_64_16";
case R_X86_64_PC16: return "R_X86_64_PC16";
case R_X86_64_8: return "R_X86_64_8";
case R_X86_64_PC8: return "R_X86_64_PC8";
case R_X86_64_DTPMOD64: return "R_X86_64_DTPMOD64";
case R_X86_64_DTPOFF64: return "R_X86_64_DTPOFF64";
case R_X86_64_TPOFF64: return "R_X86_64_TPOFF64";
case R_X86_64_TLSGD: return "R_X86_64_TLSGD";
case R_X86_64_TLSLD: return "R_X86_64_TLSLD";
case R_X86_64_DTPOFF32: return "R_X86_64_DTPOFF32";
case R_X86_64_GOTTPOFF: return "R_X86_64_GOTTPOFF";
case R_X86_64_TPOFF32: return "R_X86_64_TPOFF32";
case R_X86_64_PC64: return "R_X86_64_PC64";
case R_X86_64_GOTOFF64: return "R_X86_64_GOTOFF64";
case R_X86_64_GOTPC32: return "R_X86_64_GOTPC32";
case R_X86_64_GOT64: return "R_X86_64_GOT64";
case R_X86_64_GOTPCREL64: return "R_X86_64_GOTPCREL64";
case R_X86_64_GOTPC64: return "R_X86_64_GOTPC64";
case R_X86_64_GOTPLT64: return "R_X86_64_GOTPLT64";
case R_X86_64_PLTOFF64: return "R_X86_64_PLTOFF64";
case R_X86_64_SIZE32: return "R_X86_64_SIZE32";
case R_X86_64_SIZE64: return "R_X86_64_SIZE64";
case R_X86_64_GOTPC32_TLSDESC: return "R_X86_64_GOTPC32_TLSDESC";
case R_X86_64_TLSDESC_CALL: return "R_X86_64_TLSDESC_CALL";
case R_X86_64_TLSDESC: return "R_X86_64_TLSDESC";
case R_X86_64_IRELATIVE: return "R_X86_64_IRELATIVE";
case R_X86_64_GOTPCRELX: return "R_X86_64_GOTPCRELX";
case R_X86_64_REX_GOTPCRELX: return "R_X86_64_REX_GOTPCRELX";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<I386>(u32 r_type) {
switch (r_type) {
case R_386_NONE: return "R_386_NONE";
case R_386_32: return "R_386_32";
case R_386_PC32: return "R_386_PC32";
case R_386_GOT32: return "R_386_GOT32";
case R_386_PLT32: return "R_386_PLT32";
case R_386_COPY: return "R_386_COPY";
case R_386_GLOB_DAT: return "R_386_GLOB_DAT";
case R_386_JUMP_SLOT: return "R_386_JUMP_SLOT";
case R_386_RELATIVE: return "R_386_RELATIVE";
case R_386_GOTOFF: return "R_386_GOTOFF";
case R_386_GOTPC: return "R_386_GOTPC";
case R_386_32PLT: return "R_386_32PLT";
case R_386_TLS_TPOFF: return "R_386_TLS_TPOFF";
case R_386_TLS_IE: return "R_386_TLS_IE";
case R_386_TLS_GOTIE: return "R_386_TLS_GOTIE";
case R_386_TLS_LE: return "R_386_TLS_LE";
case R_386_TLS_GD: return "R_386_TLS_GD";
case R_386_TLS_LDM: return "R_386_TLS_LDM";
case R_386_16: return "R_386_16";
case R_386_PC16: return "R_386_PC16";
case R_386_8: return "R_386_8";
case R_386_PC8: return "R_386_PC8";
case R_386_TLS_GD_32: return "R_386_TLS_GD_32";
case R_386_TLS_GD_PUSH: return "R_386_TLS_GD_PUSH";
case R_386_TLS_GD_CALL: return "R_386_TLS_GD_CALL";
case R_386_TLS_GD_POP: return "R_386_TLS_GD_POP";
case R_386_TLS_LDM_32: return "R_386_TLS_LDM_32";
case R_386_TLS_LDM_PUSH: return "R_386_TLS_LDM_PUSH";
case R_386_TLS_LDM_CALL: return "R_386_TLS_LDM_CALL";
case R_386_TLS_LDM_POP: return "R_386_TLS_LDM_POP";
case R_386_TLS_LDO_32: return "R_386_TLS_LDO_32";
case R_386_TLS_IE_32: return "R_386_TLS_IE_32";
case R_386_TLS_LE_32: return "R_386_TLS_LE_32";
case R_386_TLS_DTPMOD32: return "R_386_TLS_DTPMOD32";
case R_386_TLS_DTPOFF32: return "R_386_TLS_DTPOFF32";
case R_386_TLS_TPOFF32: return "R_386_TLS_TPOFF32";
case R_386_SIZE32: return "R_386_SIZE32";
case R_386_TLS_GOTDESC: return "R_386_TLS_GOTDESC";
case R_386_TLS_DESC_CALL: return "R_386_TLS_DESC_CALL";
case R_386_TLS_DESC: return "R_386_TLS_DESC";
case R_386_IRELATIVE: return "R_386_IRELATIVE";
case R_386_GOT32X: return "R_386_GOT32X";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<ARM64>(u32 r_type) {
switch (r_type) {
case R_AARCH64_NONE: return "R_AARCH64_NONE";
case R_AARCH64_ABS64: return "R_AARCH64_ABS64";
case R_AARCH64_ABS32: return "R_AARCH64_ABS32";
case R_AARCH64_ABS16: return "R_AARCH64_ABS16";
case R_AARCH64_PREL64: return "R_AARCH64_PREL64";
case R_AARCH64_PREL32: return "R_AARCH64_PREL32";
case R_AARCH64_PREL16: return "R_AARCH64_PREL16";
case R_AARCH64_MOVW_UABS_G0: return "R_AARCH64_MOVW_UABS_G0";
case R_AARCH64_MOVW_UABS_G0_NC: return "R_AARCH64_MOVW_UABS_G0_NC";
case R_AARCH64_MOVW_UABS_G1: return "R_AARCH64_MOVW_UABS_G1";
case R_AARCH64_MOVW_UABS_G1_NC: return "R_AARCH64_MOVW_UABS_G1_NC";
case R_AARCH64_MOVW_UABS_G2: return "R_AARCH64_MOVW_UABS_G2";
case R_AARCH64_MOVW_UABS_G2_NC: return "R_AARCH64_MOVW_UABS_G2_NC";
case R_AARCH64_MOVW_UABS_G3: return "R_AARCH64_MOVW_UABS_G3";
case R_AARCH64_MOVW_SABS_G0: return "R_AARCH64_MOVW_SABS_G0";
case R_AARCH64_MOVW_SABS_G1: return "R_AARCH64_MOVW_SABS_G1";
case R_AARCH64_MOVW_SABS_G2: return "R_AARCH64_MOVW_SABS_G2";
case R_AARCH64_LD_PREL_LO19: return "R_AARCH64_LD_PREL_LO19";
case R_AARCH64_ADR_PREL_LO21: return "R_AARCH64_ADR_PREL_LO21";
case R_AARCH64_ADR_PREL_PG_HI21: return "R_AARCH64_ADR_PREL_PG_HI21";
case R_AARCH64_ADR_PREL_PG_HI21_NC: return "R_AARCH64_ADR_PREL_PG_HI21_NC";
case R_AARCH64_ADD_ABS_LO12_NC: return "R_AARCH64_ADD_ABS_LO12_NC";
case R_AARCH64_LDST8_ABS_LO12_NC: return "R_AARCH64_LDST8_ABS_LO12_NC";
case R_AARCH64_TSTBR14: return "R_AARCH64_TSTBR14";
case R_AARCH64_CONDBR19: return "R_AARCH64_CONDBR19";
case R_AARCH64_JUMP26: return "R_AARCH64_JUMP26";
case R_AARCH64_CALL26: return "R_AARCH64_CALL26";
case R_AARCH64_LDST16_ABS_LO12_NC: return "R_AARCH64_LDST16_ABS_LO12_NC";
case R_AARCH64_LDST32_ABS_LO12_NC: return "R_AARCH64_LDST32_ABS_LO12_NC";
case R_AARCH64_LDST64_ABS_LO12_NC: return "R_AARCH64_LDST64_ABS_LO12_NC";
case R_AARCH64_MOVW_PREL_G0: return "R_AARCH64_MOVW_PREL_G0";
case R_AARCH64_MOVW_PREL_G0_NC: return "R_AARCH64_MOVW_PREL_G0_NC";
case R_AARCH64_MOVW_PREL_G1: return "R_AARCH64_MOVW_PREL_G1";
case R_AARCH64_MOVW_PREL_G1_NC: return "R_AARCH64_MOVW_PREL_G1_NC";
case R_AARCH64_MOVW_PREL_G2: return "R_AARCH64_MOVW_PREL_G2";
case R_AARCH64_MOVW_PREL_G2_NC: return "R_AARCH64_MOVW_PREL_G2_NC";
case R_AARCH64_MOVW_PREL_G3: return "R_AARCH64_MOVW_PREL_G3";
case R_AARCH64_LDST128_ABS_LO12_NC: return "R_AARCH64_LDST128_ABS_LO12_NC";
case R_AARCH64_ADR_GOT_PAGE: return "R_AARCH64_ADR_GOT_PAGE";
case R_AARCH64_LD64_GOT_LO12_NC: return "R_AARCH64_LD64_GOT_LO12_NC";
case R_AARCH64_LD64_GOTPAGE_LO15: return "R_AARCH64_LD64_GOTPAGE_LO15";
case R_AARCH64_PLT32: return "R_AARCH64_PLT32";
case R_AARCH64_TLSGD_ADR_PREL21: return "R_AARCH64_TLSGD_ADR_PREL21";
case R_AARCH64_TLSGD_ADR_PAGE21: return "R_AARCH64_TLSGD_ADR_PAGE21";
case R_AARCH64_TLSGD_ADD_LO12_NC: return "R_AARCH64_TLSGD_ADD_LO12_NC";
case R_AARCH64_TLSGD_MOVW_G1: return "R_AARCH64_TLSGD_MOVW_G1";
case R_AARCH64_TLSGD_MOVW_G0_NC: return "R_AARCH64_TLSGD_MOVW_G0_NC";
case R_AARCH64_TLSLD_ADR_PREL21: return "R_AARCH64_TLSLD_ADR_PREL21";
case R_AARCH64_TLSLD_ADR_PAGE21: return "R_AARCH64_TLSLD_ADR_PAGE21";
case R_AARCH64_TLSLD_ADD_LO12_NC: return "R_AARCH64_TLSLD_ADD_LO12_NC";
case R_AARCH64_TLSLD_MOVW_G1: return "R_AARCH64_TLSLD_MOVW_G1";
case R_AARCH64_TLSLD_MOVW_G0_NC: return "R_AARCH64_TLSLD_MOVW_G0_NC";
case R_AARCH64_TLSLD_LD_PREL19: return "R_AARCH64_TLSLD_LD_PREL19";
case R_AARCH64_TLSLD_MOVW_DTPREL_G2: return "R_AARCH64_TLSLD_MOVW_DTPREL_G2";
case R_AARCH64_TLSLD_MOVW_DTPREL_G1: return "R_AARCH64_TLSLD_MOVW_DTPREL_G1";
case R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC: return "R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC";
case R_AARCH64_TLSLD_MOVW_DTPREL_G0: return "R_AARCH64_TLSLD_MOVW_DTPREL_G0";
case R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC: return "R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC";
case R_AARCH64_TLSLD_ADD_DTPREL_HI12: return "R_AARCH64_TLSLD_ADD_DTPREL_HI12";
case R_AARCH64_TLSLD_ADD_DTPREL_LO12: return "R_AARCH64_TLSLD_ADD_DTPREL_LO12";
case R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST8_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST8_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST16_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST16_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST32_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST32_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST64_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST64_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC";
case R_AARCH64_TLSIE_MOVW_GOTTPREL_G1: return "R_AARCH64_TLSIE_MOVW_GOTTPREL_G1";
case R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC: return "R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC";
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: return "R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21";
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: return "R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC";
case R_AARCH64_TLSIE_LD_GOTTPREL_PREL19: return "R_AARCH64_TLSIE_LD_GOTTPREL_PREL19";
case R_AARCH64_TLSLE_MOVW_TPREL_G2: return "R_AARCH64_TLSLE_MOVW_TPREL_G2";
case R_AARCH64_TLSLE_MOVW_TPREL_G1: return "R_AARCH64_TLSLE_MOVW_TPREL_G1";
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC: return "R_AARCH64_TLSLE_MOVW_TPREL_G1_NC";
case R_AARCH64_TLSLE_MOVW_TPREL_G0: return "R_AARCH64_TLSLE_MOVW_TPREL_G0";
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC: return "R_AARCH64_TLSLE_MOVW_TPREL_G0_NC";
case R_AARCH64_TLSLE_ADD_TPREL_HI12: return "R_AARCH64_TLSLE_ADD_TPREL_HI12";
case R_AARCH64_TLSLE_ADD_TPREL_LO12: return "R_AARCH64_TLSLE_ADD_TPREL_LO12";
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: return "R_AARCH64_TLSLE_ADD_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST8_TPREL_LO12: return "R_AARCH64_TLSLE_LDST8_TPREL_LO12";
case R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST16_TPREL_LO12: return "R_AARCH64_TLSLE_LDST16_TPREL_LO12";
case R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST32_TPREL_LO12: return "R_AARCH64_TLSLE_LDST32_TPREL_LO12";
case R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST64_TPREL_LO12: return "R_AARCH64_TLSLE_LDST64_TPREL_LO12";
case R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC";
case R_AARCH64_TLSDESC_ADR_PAGE21: return "R_AARCH64_TLSDESC_ADR_PAGE21";
case R_AARCH64_TLSDESC_LD64_LO12: return "R_AARCH64_TLSDESC_LD64_LO12";
case R_AARCH64_TLSDESC_ADD_LO12: return "R_AARCH64_TLSDESC_ADD_LO12";
case R_AARCH64_TLSDESC_CALL: return "R_AARCH64_TLSDESC_CALL";
case R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC";
case R_AARCH64_COPY: return "R_AARCH64_COPY";
case R_AARCH64_GLOB_DAT: return "R_AARCH64_GLOB_DAT";
case R_AARCH64_JUMP_SLOT: return "R_AARCH64_JUMP_SLOT";
case R_AARCH64_RELATIVE: return "R_AARCH64_RELATIVE";
case R_AARCH64_TLS_DTPMOD64: return "R_AARCH64_TLS_DTPMOD64";
case R_AARCH64_TLS_DTPREL64: return "R_AARCH64_TLS_DTPREL64";
case R_AARCH64_TLS_TPREL64: return "R_AARCH64_TLS_TPREL64";
case R_AARCH64_TLSDESC: return "R_AARCH64_TLSDESC";
case R_AARCH64_IRELATIVE: return "R_AARCH64_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<ARM32>(u32 r_type) {
switch (r_type) {
case R_ARM_NONE: return "R_ARM_NONE";
case R_ARM_PC24: return "R_ARM_PC24";
case R_ARM_ABS32: return "R_ARM_ABS32";
case R_ARM_REL32: return "R_ARM_REL32";
case R_ARM_LDR_PC_G0: return "R_ARM_LDR_PC_G0";
case R_ARM_ABS16: return "R_ARM_ABS16";
case R_ARM_ABS12: return "R_ARM_ABS12";
case R_ARM_THM_ABS5: return "R_ARM_THM_ABS5";
case R_ARM_ABS8: return "R_ARM_ABS8";
case R_ARM_SBREL32: return "R_ARM_SBREL32";
case R_ARM_THM_CALL: return "R_ARM_THM_CALL";
case R_ARM_THM_PC8: return "R_ARM_THM_PC8";
case R_ARM_BREL_ADJ: return "R_ARM_BREL_ADJ";
case R_ARM_TLS_DESC: return "R_ARM_TLS_DESC";
case R_ARM_THM_SWI8: return "R_ARM_THM_SWI8";
case R_ARM_XPC25: return "R_ARM_XPC25";
case R_ARM_THM_XPC22: return "R_ARM_THM_XPC22";
case R_ARM_TLS_DTPMOD32: return "R_ARM_TLS_DTPMOD32";
case R_ARM_TLS_DTPOFF32: return "R_ARM_TLS_DTPOFF32";
case R_ARM_TLS_TPOFF32: return "R_ARM_TLS_TPOFF32";
case R_ARM_COPY: return "R_ARM_COPY";
case R_ARM_GLOB_DAT: return "R_ARM_GLOB_DAT";
case R_ARM_JUMP_SLOT: return "R_ARM_JUMP_SLOT";
case R_ARM_RELATIVE: return "R_ARM_RELATIVE";
case R_ARM_GOTOFF32: return "R_ARM_GOTOFF32";
case R_ARM_BASE_PREL: return "R_ARM_BASE_PREL";
case R_ARM_GOT_BREL: return "R_ARM_GOT_BREL";
case R_ARM_PLT32: return "R_ARM_PLT32";
case R_ARM_CALL: return "R_ARM_CALL";
case R_ARM_JUMP24: return "R_ARM_JUMP24";
case R_ARM_THM_JUMP24: return "R_ARM_THM_JUMP24";
case R_ARM_BASE_ABS: return "R_ARM_BASE_ABS";
case R_ARM_ALU_PCREL_7_0: return "R_ARM_ALU_PCREL_7_0";
case R_ARM_ALU_PCREL_15_8: return "R_ARM_ALU_PCREL_15_8";
case R_ARM_ALU_PCREL_23_15: return "R_ARM_ALU_PCREL_23_15";
case R_ARM_LDR_SBREL_11_0_NC: return "R_ARM_LDR_SBREL_11_0_NC";
case R_ARM_ALU_SBREL_19_12_NC: return "R_ARM_ALU_SBREL_19_12_NC";
case R_ARM_ALU_SBREL_27_20_CK: return "R_ARM_ALU_SBREL_27_20_CK";
case R_ARM_TARGET1: return "R_ARM_TARGET1";
case R_ARM_SBREL31: return "R_ARM_SBREL31";
case R_ARM_V4BX: return "R_ARM_V4BX";
case R_ARM_TARGET2: return "R_ARM_TARGET2";
case R_ARM_PREL31: return "R_ARM_PREL31";
case R_ARM_MOVW_ABS_NC: return "R_ARM_MOVW_ABS_NC";
case R_ARM_MOVT_ABS: return "R_ARM_MOVT_ABS";
case R_ARM_MOVW_PREL_NC: return "R_ARM_MOVW_PREL_NC";
case R_ARM_MOVT_PREL: return "R_ARM_MOVT_PREL";
case R_ARM_THM_MOVW_ABS_NC: return "R_ARM_THM_MOVW_ABS_NC";
case R_ARM_THM_MOVT_ABS: return "R_ARM_THM_MOVT_ABS";
case R_ARM_THM_MOVW_PREL_NC: return "R_ARM_THM_MOVW_PREL_NC";
case R_ARM_THM_MOVT_PREL: return "R_ARM_THM_MOVT_PREL";
case R_ARM_THM_JUMP19: return "R_ARM_THM_JUMP19";
case R_ARM_THM_JUMP6: return "R_ARM_THM_JUMP6";
case R_ARM_THM_ALU_PREL_11_0: return "R_ARM_THM_ALU_PREL_11_0";
case R_ARM_THM_PC12: return "R_ARM_THM_PC12";
case R_ARM_ABS32_NOI: return "R_ARM_ABS32_NOI";
case R_ARM_REL32_NOI: return "R_ARM_REL32_NOI";
case R_ARM_ALU_PC_G0_NC: return "R_ARM_ALU_PC_G0_NC";
case R_ARM_ALU_PC_G0: return "R_ARM_ALU_PC_G0";
case R_ARM_ALU_PC_G1_NC: return "R_ARM_ALU_PC_G1_NC";
case R_ARM_ALU_PC_G1: return "R_ARM_ALU_PC_G1";
case R_ARM_ALU_PC_G2: return "R_ARM_ALU_PC_G2";
case R_ARM_LDR_PC_G1: return "R_ARM_LDR_PC_G1";
case R_ARM_LDR_PC_G2: return "R_ARM_LDR_PC_G2";
case R_ARM_LDRS_PC_G0: return "R_ARM_LDRS_PC_G0";
case R_ARM_LDRS_PC_G1: return "R_ARM_LDRS_PC_G1";
case R_ARM_LDRS_PC_G2: return "R_ARM_LDRS_PC_G2";
case R_ARM_LDC_PC_G0: return "R_ARM_LDC_PC_G0";
case R_ARM_LDC_PC_G1: return "R_ARM_LDC_PC_G1";
case R_ARM_LDC_PC_G2: return "R_ARM_LDC_PC_G2";
case R_ARM_ALU_SB_G0_NC: return "R_ARM_ALU_SB_G0_NC";
case R_ARM_ALU_SB_G0: return "R_ARM_ALU_SB_G0";
case R_ARM_ALU_SB_G1_NC: return "R_ARM_ALU_SB_G1_NC";
case R_ARM_ALU_SB_G1: return "R_ARM_ALU_SB_G1";
case R_ARM_ALU_SB_G2: return "R_ARM_ALU_SB_G2";
case R_ARM_LDR_SB_G0: return "R_ARM_LDR_SB_G0";
case R_ARM_LDR_SB_G1: return "R_ARM_LDR_SB_G1";
case R_ARM_LDR_SB_G2: return "R_ARM_LDR_SB_G2";
case R_ARM_LDRS_SB_G0: return "R_ARM_LDRS_SB_G0";
case R_ARM_LDRS_SB_G1: return "R_ARM_LDRS_SB_G1";
case R_ARM_LDRS_SB_G2: return "R_ARM_LDRS_SB_G2";
case R_ARM_LDC_SB_G0: return "R_ARM_LDC_SB_G0";
case R_ARM_LDC_SB_G1: return "R_ARM_LDC_SB_G1";
case R_ARM_LDC_SB_G2: return "R_ARM_LDC_SB_G2";
case R_ARM_MOVW_BREL_NC: return "R_ARM_MOVW_BREL_NC";
case R_ARM_MOVT_BREL: return "R_ARM_MOVT_BREL";
case R_ARM_MOVW_BREL: return "R_ARM_MOVW_BREL";
case R_ARM_THM_MOVW_BREL_NC: return "R_ARM_THM_MOVW_BREL_NC";
case R_ARM_THM_MOVT_BREL: return "R_ARM_THM_MOVT_BREL";
case R_ARM_THM_MOVW_BREL: return "R_ARM_THM_MOVW_BREL";
case R_ARM_TLS_GOTDESC: return "R_ARM_TLS_GOTDESC";
case R_ARM_TLS_CALL: return "R_ARM_TLS_CALL";
case R_ARM_TLS_DESCSEQ: return "R_ARM_TLS_DESCSEQ";
case R_ARM_THM_TLS_CALL: return "R_ARM_THM_TLS_CALL";
case R_ARM_PLT32_ABS: return "R_ARM_PLT32_ABS";
case R_ARM_GOT_ABS: return "R_ARM_GOT_ABS";
case R_ARM_GOT_PREL: return "R_ARM_GOT_PREL";
case R_ARM_GOT_BREL12: return "R_ARM_GOT_BREL12";
case R_ARM_GOTOFF12: return "R_ARM_GOTOFF12";
case R_ARM_GOTRELAX: return "R_ARM_GOTRELAX";
case R_ARM_GNU_VTENTRY: return "R_ARM_GNU_VTENTRY";
case R_ARM_GNU_VTINHERIT: return "R_ARM_GNU_VTINHERIT";
case R_ARM_THM_JUMP11: return "R_ARM_THM_JUMP11";
case R_ARM_THM_JUMP8: return "R_ARM_THM_JUMP8";
case R_ARM_TLS_GD32: return "R_ARM_TLS_GD32";
case R_ARM_TLS_LDM32: return "R_ARM_TLS_LDM32";
case R_ARM_TLS_LDO32: return "R_ARM_TLS_LDO32";
case R_ARM_TLS_IE32: return "R_ARM_TLS_IE32";
case R_ARM_TLS_LE32: return "R_ARM_TLS_LE32";
case R_ARM_TLS_LDO12: return "R_ARM_TLS_LDO12";
case R_ARM_TLS_LE12: return "R_ARM_TLS_LE12";
case R_ARM_TLS_IE12GP: return "R_ARM_TLS_IE12GP";
case R_ARM_PRIVATE_0: return "R_ARM_PRIVATE_0";
case R_ARM_PRIVATE_1: return "R_ARM_PRIVATE_1";
case R_ARM_PRIVATE_2: return "R_ARM_PRIVATE_2";
case R_ARM_PRIVATE_3: return "R_ARM_PRIVATE_3";
case R_ARM_PRIVATE_4: return "R_ARM_PRIVATE_4";
case R_ARM_PRIVATE_5: return "R_ARM_PRIVATE_5";
case R_ARM_PRIVATE_6: return "R_ARM_PRIVATE_6";
case R_ARM_PRIVATE_7: return "R_ARM_PRIVATE_7";
case R_ARM_PRIVATE_8: return "R_ARM_PRIVATE_8";
case R_ARM_PRIVATE_9: return "R_ARM_PRIVATE_9";
case R_ARM_PRIVATE_10: return "R_ARM_PRIVATE_10";
case R_ARM_PRIVATE_11: return "R_ARM_PRIVATE_11";
case R_ARM_PRIVATE_12: return "R_ARM_PRIVATE_12";
case R_ARM_PRIVATE_13: return "R_ARM_PRIVATE_13";
case R_ARM_PRIVATE_14: return "R_ARM_PRIVATE_14";
case R_ARM_PRIVATE_15: return "R_ARM_PRIVATE_15";
case R_ARM_ME_TOO: return "R_ARM_ME_TOO";
case R_ARM_THM_TLS_DESCSEQ16: return "R_ARM_THM_TLS_DESCSEQ16";
case R_ARM_THM_TLS_DESCSEQ32: return "R_ARM_THM_TLS_DESCSEQ32";
case R_ARM_THM_BF16: return "R_ARM_THM_BF16";
case R_ARM_THM_BF12: return "R_ARM_THM_BF12";
case R_ARM_THM_BF18: return "R_ARM_THM_BF18";
case R_ARM_IRELATIVE: return "R_ARM_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<RV64LE>(u32 r_type) {
switch (r_type) {
case R_RISCV_NONE: return "R_RISCV_NONE";
case R_RISCV_32: return "R_RISCV_32";
case R_RISCV_64: return "R_RISCV_64";
case R_RISCV_RELATIVE: return "R_RISCV_RELATIVE";
case R_RISCV_COPY: return "R_RISCV_COPY";
case R_RISCV_JUMP_SLOT: return "R_RISCV_JUMP_SLOT";
case R_RISCV_TLS_DTPMOD32: return "R_RISCV_TLS_DTPMOD32";
case R_RISCV_TLS_DTPMOD64: return "R_RISCV_TLS_DTPMOD64";
case R_RISCV_TLS_DTPREL32: return "R_RISCV_TLS_DTPREL32";
case R_RISCV_TLS_DTPREL64: return "R_RISCV_TLS_DTPREL64";
case R_RISCV_TLS_TPREL32: return "R_RISCV_TLS_TPREL32";
case R_RISCV_TLS_TPREL64: return "R_RISCV_TLS_TPREL64";
case R_RISCV_BRANCH: return "R_RISCV_BRANCH";
case R_RISCV_JAL: return "R_RISCV_JAL";
case R_RISCV_CALL: return "R_RISCV_CALL";
case R_RISCV_CALL_PLT: return "R_RISCV_CALL_PLT";
case R_RISCV_GOT_HI20: return "R_RISCV_GOT_HI20";
case R_RISCV_TLS_GOT_HI20: return "R_RISCV_TLS_GOT_HI20";
case R_RISCV_TLS_GD_HI20: return "R_RISCV_TLS_GD_HI20";
case R_RISCV_PCREL_HI20: return "R_RISCV_PCREL_HI20";
case R_RISCV_PCREL_LO12_I: return "R_RISCV_PCREL_LO12_I";
case R_RISCV_PCREL_LO12_S: return "R_RISCV_PCREL_LO12_S";
case R_RISCV_HI20: return "R_RISCV_HI20";
case R_RISCV_LO12_I: return "R_RISCV_LO12_I";
case R_RISCV_LO12_S: return "R_RISCV_LO12_S";
case R_RISCV_TPREL_HI20: return "R_RISCV_TPREL_HI20";
case R_RISCV_TPREL_LO12_I: return "R_RISCV_TPREL_LO12_I";
case R_RISCV_TPREL_LO12_S: return "R_RISCV_TPREL_LO12_S";
case R_RISCV_TPREL_ADD: return "R_RISCV_TPREL_ADD";
case R_RISCV_ADD8: return "R_RISCV_ADD8";
case R_RISCV_ADD16: return "R_RISCV_ADD16";
case R_RISCV_ADD32: return "R_RISCV_ADD32";
case R_RISCV_ADD64: return "R_RISCV_ADD64";
case R_RISCV_SUB8: return "R_RISCV_SUB8";
case R_RISCV_SUB16: return "R_RISCV_SUB16";
case R_RISCV_SUB32: return "R_RISCV_SUB32";
case R_RISCV_SUB64: return "R_RISCV_SUB64";
case R_RISCV_ALIGN: return "R_RISCV_ALIGN";
case R_RISCV_RVC_BRANCH: return "R_RISCV_RVC_BRANCH";
case R_RISCV_RVC_JUMP: return "R_RISCV_RVC_JUMP";
case R_RISCV_RVC_LUI: return "R_RISCV_RVC_LUI";
case R_RISCV_RELAX: return "R_RISCV_RELAX";
case R_RISCV_SUB6: return "R_RISCV_SUB6";
case R_RISCV_SET6: return "R_RISCV_SET6";
case R_RISCV_SET8: return "R_RISCV_SET8";
case R_RISCV_SET16: return "R_RISCV_SET16";
case R_RISCV_SET32: return "R_RISCV_SET32";
case R_RISCV_32_PCREL: return "R_RISCV_32_PCREL";
case R_RISCV_IRELATIVE: return "R_RISCV_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<RV64BE>(u32 r_type) {
return rel_to_string<RV64LE>(r_type);
}
template <>
std::string rel_to_string<RV32LE>(u32 r_type) {
return rel_to_string<RV64LE>(r_type);
}
template <>
std::string rel_to_string<RV32BE>(u32 r_type) {
return rel_to_string<RV64LE>(r_type);
}
template <>
std::string rel_to_string<PPC32>(u32 r_type) {
switch (r_type) {
case R_PPC_NONE: return "R_PPC_NONE";
case R_PPC_ADDR32: return "R_PPC_ADDR32";
case R_PPC_ADDR24: return "R_PPC_ADDR24";
case R_PPC_ADDR16: return "R_PPC_ADDR16";
case R_PPC_ADDR16_LO: return "R_PPC_ADDR16_LO";
case R_PPC_ADDR16_HI: return "R_PPC_ADDR16_HI";
case R_PPC_ADDR16_HA: return "R_PPC_ADDR16_HA";
case R_PPC_ADDR14: return "R_PPC_ADDR14";
case R_PPC_ADDR14_BRTAKEN: return "R_PPC_ADDR14_BRTAKEN";
case R_PPC_ADDR14_BRNTAKEN: return "R_PPC_ADDR14_BRNTAKEN";
case R_PPC_REL24: return "R_PPC_REL24";
case R_PPC_REL14: return "R_PPC_REL14";
case R_PPC_REL14_BRTAKEN: return "R_PPC_REL14_BRTAKEN";
case R_PPC_REL14_BRNTAKEN: return "R_PPC_REL14_BRNTAKEN";
case R_PPC_GOT16: return "R_PPC_GOT16";
case R_PPC_GOT16_LO: return "R_PPC_GOT16_LO";
case R_PPC_GOT16_HI: return "R_PPC_GOT16_HI";
case R_PPC_GOT16_HA: return "R_PPC_GOT16_HA";
case R_PPC_PLTREL24: return "R_PPC_PLTREL24";
case R_PPC_COPY: return "R_PPC_COPY";
case R_PPC_GLOB_DAT: return "R_PPC_GLOB_DAT";
case R_PPC_JMP_SLOT: return "R_PPC_JMP_SLOT";
case R_PPC_RELATIVE: return "R_PPC_RELATIVE";
case R_PPC_LOCAL24PC: return "R_PPC_LOCAL24PC";
case R_PPC_UADDR32: return "R_PPC_UADDR32";
case R_PPC_UADDR16: return "R_PPC_UADDR16";
case R_PPC_REL32: return "R_PPC_REL32";
case R_PPC_PLT32: return "R_PPC_PLT32";
case R_PPC_PLTREL32: return "R_PPC_PLTREL32";
case R_PPC_PLT16_LO: return "R_PPC_PLT16_LO";
case R_PPC_PLT16_HI: return "R_PPC_PLT16_HI";
case R_PPC_PLT16_HA: return "R_PPC_PLT16_HA";
case R_PPC_SDAREL16: return "R_PPC_SDAREL16";
case R_PPC_SECTOFF: return "R_PPC_SECTOFF";
case R_PPC_SECTOFF_LO: return "R_PPC_SECTOFF_LO";
case R_PPC_SECTOFF_HI: return "R_PPC_SECTOFF_HI";
case R_PPC_SECTOFF_HA: return "R_PPC_SECTOFF_HA";
case R_PPC_ADDR30: return "R_PPC_ADDR30";
case R_PPC_TLS: return "R_PPC_TLS";
case R_PPC_DTPMOD32: return "R_PPC_DTPMOD32";
case R_PPC_TPREL16: return "R_PPC_TPREL16";
case R_PPC_TPREL16_LO: return "R_PPC_TPREL16_LO";
case R_PPC_TPREL16_HI: return "R_PPC_TPREL16_HI";
case R_PPC_TPREL16_HA: return "R_PPC_TPREL16_HA";
case R_PPC_TPREL32: return "R_PPC_TPREL32";
case R_PPC_DTPREL16: return "R_PPC_DTPREL16";
case R_PPC_DTPREL16_LO: return "R_PPC_DTPREL16_LO";
case R_PPC_DTPREL16_HI: return "R_PPC_DTPREL16_HI";
case R_PPC_DTPREL16_HA: return "R_PPC_DTPREL16_HA";
case R_PPC_DTPREL32: return "R_PPC_DTPREL32";
case R_PPC_GOT_TLSGD16: return "R_PPC_GOT_TLSGD16";
case R_PPC_GOT_TLSGD16_LO: return "R_PPC_GOT_TLSGD16_LO";
case R_PPC_GOT_TLSGD16_HI: return "R_PPC_GOT_TLSGD16_HI";
case R_PPC_GOT_TLSGD16_HA: return "R_PPC_GOT_TLSGD16_HA";
case R_PPC_GOT_TLSLD16: return "R_PPC_GOT_TLSLD16";
case R_PPC_GOT_TLSLD16_LO: return "R_PPC_GOT_TLSLD16_LO";
case R_PPC_GOT_TLSLD16_HI: return "R_PPC_GOT_TLSLD16_HI";
case R_PPC_GOT_TLSLD16_HA: return "R_PPC_GOT_TLSLD16_HA";
case R_PPC_GOT_TPREL16: return "R_PPC_GOT_TPREL16";
case R_PPC_GOT_TPREL16_LO: return "R_PPC_GOT_TPREL16_LO";
case R_PPC_GOT_TPREL16_HI: return "R_PPC_GOT_TPREL16_HI";
case R_PPC_GOT_TPREL16_HA: return "R_PPC_GOT_TPREL16_HA";
case R_PPC_GOT_DTPREL16: return "R_PPC_GOT_DTPREL16";
case R_PPC_GOT_DTPREL16_LO: return "R_PPC_GOT_DTPREL16_LO";
case R_PPC_GOT_DTPREL16_HI: return "R_PPC_GOT_DTPREL16_HI";
case R_PPC_GOT_DTPREL16_HA: return "R_PPC_GOT_DTPREL16_HA";
case R_PPC_TLSGD: return "R_PPC_TLSGD";
case R_PPC_TLSLD: return "R_PPC_TLSLD";
case R_PPC_PLTSEQ: return "R_PPC_PLTSEQ";
case R_PPC_PLTCALL: return "R_PPC_PLTCALL";
case R_PPC_IRELATIVE: return "R_PPC_IRELATIVE";
case R_PPC_REL16: return "R_PPC_REL16";
case R_PPC_REL16_LO: return "R_PPC_REL16_LO";
case R_PPC_REL16_HI: return "R_PPC_REL16_HI";
case R_PPC_REL16_HA: return "R_PPC_REL16_HA";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<PPC64V1>(u32 r_type) {
switch (r_type) {
case R_PPC64_NONE: return "R_PPC64_NONE";
case R_PPC64_ADDR32: return "R_PPC64_ADDR32";
case R_PPC64_ADDR24: return "R_PPC64_ADDR24";
case R_PPC64_ADDR16: return "R_PPC64_ADDR16";
case R_PPC64_ADDR16_LO: return "R_PPC64_ADDR16_LO";
case R_PPC64_ADDR16_HI: return "R_PPC64_ADDR16_HI";
case R_PPC64_ADDR16_HA: return "R_PPC64_ADDR16_HA";
case R_PPC64_ADDR14: return "R_PPC64_ADDR14";
case R_PPC64_ADDR14_BRTAKEN: return "R_PPC64_ADDR14_BRTAKEN";
case R_PPC64_ADDR14_BRNTAKEN: return "R_PPC64_ADDR14_BRNTAKEN";
case R_PPC64_REL24: return "R_PPC64_REL24";
case R_PPC64_REL14: return "R_PPC64_REL14";
case R_PPC64_REL14_BRTAKEN: return "R_PPC64_REL14_BRTAKEN";
case R_PPC64_REL14_BRNTAKEN: return "R_PPC64_REL14_BRNTAKEN";
case R_PPC64_GOT16: return "R_PPC64_GOT16";
case R_PPC64_GOT16_LO: return "R_PPC64_GOT16_LO";
case R_PPC64_GOT16_HI: return "R_PPC64_GOT16_HI";
case R_PPC64_GOT16_HA: return "R_PPC64_GOT16_HA";
case R_PPC64_COPY: return "R_PPC64_COPY";
case R_PPC64_GLOB_DAT: return "R_PPC64_GLOB_DAT";
case R_PPC64_JMP_SLOT: return "R_PPC64_JMP_SLOT";
case R_PPC64_RELATIVE: return "R_PPC64_RELATIVE";
case R_PPC64_REL32: return "R_PPC64_REL32";
case R_PPC64_PLT16_LO: return "R_PPC64_PLT16_LO";
case R_PPC64_PLT16_HI: return "R_PPC64_PLT16_HI";
case R_PPC64_PLT16_HA: return "R_PPC64_PLT16_HA";
case R_PPC64_ADDR64: return "R_PPC64_ADDR64";
case R_PPC64_ADDR16_HIGHER: return "R_PPC64_ADDR16_HIGHER";
case R_PPC64_ADDR16_HIGHERA: return "R_PPC64_ADDR16_HIGHERA";
case R_PPC64_ADDR16_HIGHEST: return "R_PPC64_ADDR16_HIGHEST";
case R_PPC64_ADDR16_HIGHESTA: return "R_PPC64_ADDR16_HIGHESTA";
case R_PPC64_REL64: return "R_PPC64_REL64";
case R_PPC64_TOC16: return "R_PPC64_TOC16";
case R_PPC64_TOC16_LO: return "R_PPC64_TOC16_LO";
case R_PPC64_TOC16_HI: return "R_PPC64_TOC16_HI";
case R_PPC64_TOC16_HA: return "R_PPC64_TOC16_HA";
case R_PPC64_TOC: return "R_PPC64_TOC";
case R_PPC64_ADDR16_DS: return "R_PPC64_ADDR16_DS";
case R_PPC64_ADDR16_LO_DS: return "R_PPC64_ADDR16_LO_DS";
case R_PPC64_GOT16_DS: return "R_PPC64_GOT16_DS";
case R_PPC64_GOT16_LO_DS: return "R_PPC64_GOT16_LO_DS";
case R_PPC64_PLT16_LO_DS: return "R_PPC64_PLT16_LO_DS";
case R_PPC64_TOC16_DS: return "R_PPC64_TOC16_DS";
case R_PPC64_TOC16_LO_DS: return "R_PPC64_TOC16_LO_DS";
case R_PPC64_TLS: return "R_PPC64_TLS";
case R_PPC64_DTPMOD64: return "R_PPC64_DTPMOD64";
case R_PPC64_TPREL16: return "R_PPC64_TPREL16";
case R_PPC64_TPREL16_LO: return "R_PPC64_TPREL16_LO";
case R_PPC64_TPREL16_HI: return "R_PPC64_TPREL16_HI";
case R_PPC64_TPREL16_HA: return "R_PPC64_TPREL16_HA";
case R_PPC64_TPREL64: return "R_PPC64_TPREL64";
case R_PPC64_DTPREL16: return "R_PPC64_DTPREL16";
case R_PPC64_DTPREL16_LO: return "R_PPC64_DTPREL16_LO";
case R_PPC64_DTPREL16_HI: return "R_PPC64_DTPREL16_HI";
case R_PPC64_DTPREL16_HA: return "R_PPC64_DTPREL16_HA";
case R_PPC64_DTPREL64: return "R_PPC64_DTPREL64";
case R_PPC64_GOT_TLSGD16: return "R_PPC64_GOT_TLSGD16";
case R_PPC64_GOT_TLSGD16_LO: return "R_PPC64_GOT_TLSGD16_LO";
case R_PPC64_GOT_TLSGD16_HI: return "R_PPC64_GOT_TLSGD16_HI";
case R_PPC64_GOT_TLSGD16_HA: return "R_PPC64_GOT_TLSGD16_HA";
case R_PPC64_GOT_TLSLD16: return "R_PPC64_GOT_TLSLD16";
case R_PPC64_GOT_TLSLD16_LO: return "R_PPC64_GOT_TLSLD16_LO";
case R_PPC64_GOT_TLSLD16_HI: return "R_PPC64_GOT_TLSLD16_HI";
case R_PPC64_GOT_TLSLD16_HA: return "R_PPC64_GOT_TLSLD16_HA";
case R_PPC64_GOT_TPREL16_DS: return "R_PPC64_GOT_TPREL16_DS";
case R_PPC64_GOT_TPREL16_LO_DS: return "R_PPC64_GOT_TPREL16_LO_DS";
case R_PPC64_GOT_TPREL16_HI: return "R_PPC64_GOT_TPREL16_HI";
case R_PPC64_GOT_TPREL16_HA: return "R_PPC64_GOT_TPREL16_HA";
case R_PPC64_GOT_DTPREL16_DS: return "R_PPC64_GOT_DTPREL16_DS";
case R_PPC64_GOT_DTPREL16_LO_DS: return "R_PPC64_GOT_DTPREL16_LO_DS";
case R_PPC64_GOT_DTPREL16_HI: return "R_PPC64_GOT_DTPREL16_HI";
case R_PPC64_GOT_DTPREL16_HA: return "R_PPC64_GOT_DTPREL16_HA";
case R_PPC64_TPREL16_DS: return "R_PPC64_TPREL16_DS";
case R_PPC64_TPREL16_LO_DS: return "R_PPC64_TPREL16_LO_DS";
case R_PPC64_TPREL16_HIGHER: return "R_PPC64_TPREL16_HIGHER";
case R_PPC64_TPREL16_HIGHERA: return "R_PPC64_TPREL16_HIGHERA";
case R_PPC64_TPREL16_HIGHEST: return "R_PPC64_TPREL16_HIGHEST";
case R_PPC64_TPREL16_HIGHESTA: return "R_PPC64_TPREL16_HIGHESTA";
case R_PPC64_DTPREL16_DS: return "R_PPC64_DTPREL16_DS";
case R_PPC64_DTPREL16_LO_DS: return "R_PPC64_DTPREL16_LO_DS";
case R_PPC64_DTPREL16_HIGHER: return "R_PPC64_DTPREL16_HIGHER";
case R_PPC64_DTPREL16_HIGHERA: return "R_PPC64_DTPREL16_HIGHERA";
case R_PPC64_DTPREL16_HIGHEST: return "R_PPC64_DTPREL16_HIGHEST";
case R_PPC64_DTPREL16_HIGHESTA: return "R_PPC64_DTPREL16_HIGHESTA";
case R_PPC64_TLSGD: return "R_PPC64_TLSGD";
case R_PPC64_TLSLD: return "R_PPC64_TLSLD";
case R_PPC64_ADDR16_HIGH: return "R_PPC64_ADDR16_HIGH";
case R_PPC64_ADDR16_HIGHA: return "R_PPC64_ADDR16_HIGHA";
case R_PPC64_TPREL16_HIGH: return "R_PPC64_TPREL16_HIGH";
case R_PPC64_TPREL16_HIGHA: return "R_PPC64_TPREL16_HIGHA";
case R_PPC64_DTPREL16_HIGH: return "R_PPC64_DTPREL16_HIGH";
case R_PPC64_DTPREL16_HIGHA: return "R_PPC64_DTPREL16_HIGHA";
case R_PPC64_REL24_NOTOC: return "R_PPC64_REL24_NOTOC";
case R_PPC64_PLTSEQ: return "R_PPC64_PLTSEQ";
case R_PPC64_PLTCALL: return "R_PPC64_PLTCALL";
case R_PPC64_PCREL_OPT: return "R_PPC64_PCREL_OPT";
case R_PPC64_PCREL34: return "R_PPC64_PCREL34";
case R_PPC64_GOT_PCREL34: return "R_PPC64_GOT_PCREL34";
case R_PPC64_TPREL34: return "R_PPC64_TPREL34";
case R_PPC64_DTPREL34: return "R_PPC64_DTPREL34";
case R_PPC64_GOT_TLSGD_PCREL34: return "R_PPC64_GOT_TLSGD_PCREL34";
case R_PPC64_GOT_TLSLD_PCREL34: return "R_PPC64_GOT_TLSLD_PCREL34";
case R_PPC64_GOT_TPREL_PCREL34: return "R_PPC64_GOT_TPREL_PCREL34";
case R_PPC64_IRELATIVE: return "R_PPC64_IRELATIVE";
case R_PPC64_REL16: return "R_PPC64_REL16";
case R_PPC64_REL16_LO: return "R_PPC64_REL16_LO";
case R_PPC64_REL16_HI: return "R_PPC64_REL16_HI";
case R_PPC64_REL16_HA: return "R_PPC64_REL16_HA";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<PPC64V2>(u32 r_type) {
return rel_to_string<PPC64V1>(r_type);
}
template <>
std::string rel_to_string<SPARC64>(u32 r_type) {
switch (r_type) {
case R_SPARC_NONE: return "R_SPARC_NONE";
case R_SPARC_8: return "R_SPARC_8";
case R_SPARC_16: return "R_SPARC_16";
case R_SPARC_32: return "R_SPARC_32";
case R_SPARC_DISP8: return "R_SPARC_DISP8";
case R_SPARC_DISP16: return "R_SPARC_DISP16";
case R_SPARC_DISP32: return "R_SPARC_DISP32";
case R_SPARC_WDISP30: return "R_SPARC_WDISP30";
case R_SPARC_WDISP22: return "R_SPARC_WDISP22";
case R_SPARC_HI22: return "R_SPARC_HI22";
case R_SPARC_22: return "R_SPARC_22";
case R_SPARC_13: return "R_SPARC_13";
case R_SPARC_LO10: return "R_SPARC_LO10";
case R_SPARC_GOT10: return "R_SPARC_GOT10";
case R_SPARC_GOT13: return "R_SPARC_GOT13";
case R_SPARC_GOT22: return "R_SPARC_GOT22";
case R_SPARC_PC10: return "R_SPARC_PC10";
case R_SPARC_PC22: return "R_SPARC_PC22";
case R_SPARC_WPLT30: return "R_SPARC_WPLT30";
case R_SPARC_COPY: return "R_SPARC_COPY";
case R_SPARC_GLOB_DAT: return "R_SPARC_GLOB_DAT";
case R_SPARC_JMP_SLOT: return "R_SPARC_JMP_SLOT";
case R_SPARC_RELATIVE: return "R_SPARC_RELATIVE";
case R_SPARC_UA32: return "R_SPARC_UA32";
case R_SPARC_PLT32: return "R_SPARC_PLT32";
case R_SPARC_HIPLT22: return "R_SPARC_HIPLT22";
case R_SPARC_LOPLT10: return "R_SPARC_LOPLT10";
case R_SPARC_PCPLT32: return "R_SPARC_PCPLT32";
case R_SPARC_PCPLT22: return "R_SPARC_PCPLT22";
case R_SPARC_PCPLT10: return "R_SPARC_PCPLT10";
case R_SPARC_10: return "R_SPARC_10";
case R_SPARC_11: return "R_SPARC_11";
case R_SPARC_64: return "R_SPARC_64";
case R_SPARC_OLO10: return "R_SPARC_OLO10";
case R_SPARC_HH22: return "R_SPARC_HH22";
case R_SPARC_HM10: return "R_SPARC_HM10";
case R_SPARC_LM22: return "R_SPARC_LM22";
case R_SPARC_PC_HH22: return "R_SPARC_PC_HH22";
case R_SPARC_PC_HM10: return "R_SPARC_PC_HM10";
case R_SPARC_PC_LM22: return "R_SPARC_PC_LM22";
case R_SPARC_WDISP16: return "R_SPARC_WDISP16";
case R_SPARC_WDISP19: return "R_SPARC_WDISP19";
case R_SPARC_7: return "R_SPARC_7";
case R_SPARC_5: return "R_SPARC_5";
case R_SPARC_6: return "R_SPARC_6";
case R_SPARC_DISP64: return "R_SPARC_DISP64";
case R_SPARC_PLT64: return "R_SPARC_PLT64";
case R_SPARC_HIX22: return "R_SPARC_HIX22";
case R_SPARC_LOX10: return "R_SPARC_LOX10";
case R_SPARC_H44: return "R_SPARC_H44";
case R_SPARC_M44: return "R_SPARC_M44";
case R_SPARC_L44: return "R_SPARC_L44";
case R_SPARC_REGISTER: return "R_SPARC_REGISTER";
case R_SPARC_UA64: return "R_SPARC_UA64";
case R_SPARC_UA16: return "R_SPARC_UA16";
case R_SPARC_TLS_GD_HI22: return "R_SPARC_TLS_GD_HI22";
case R_SPARC_TLS_GD_LO10: return "R_SPARC_TLS_GD_LO10";
case R_SPARC_TLS_GD_ADD: return "R_SPARC_TLS_GD_ADD";
case R_SPARC_TLS_GD_CALL: return "R_SPARC_TLS_GD_CALL";
case R_SPARC_TLS_LDM_HI22: return "R_SPARC_TLS_LDM_HI22";
case R_SPARC_TLS_LDM_LO10: return "R_SPARC_TLS_LDM_LO10";
case R_SPARC_TLS_LDM_ADD: return "R_SPARC_TLS_LDM_ADD";
case R_SPARC_TLS_LDM_CALL: return "R_SPARC_TLS_LDM_CALL";
case R_SPARC_TLS_LDO_HIX22: return "R_SPARC_TLS_LDO_HIX22";
case R_SPARC_TLS_LDO_LOX10: return "R_SPARC_TLS_LDO_LOX10";
case R_SPARC_TLS_LDO_ADD: return "R_SPARC_TLS_LDO_ADD";
case R_SPARC_TLS_IE_HI22: return "R_SPARC_TLS_IE_HI22";
case R_SPARC_TLS_IE_LO10: return "R_SPARC_TLS_IE_LO10";
case R_SPARC_TLS_IE_LD: return "R_SPARC_TLS_IE_LD";
case R_SPARC_TLS_IE_LDX: return "R_SPARC_TLS_IE_LDX";
case R_SPARC_TLS_IE_ADD: return "R_SPARC_TLS_IE_ADD";
case R_SPARC_TLS_LE_HIX22: return "R_SPARC_TLS_LE_HIX22";
case R_SPARC_TLS_LE_LOX10: return "R_SPARC_TLS_LE_LOX10";
case R_SPARC_TLS_DTPMOD32: return "R_SPARC_TLS_DTPMOD32";
case R_SPARC_TLS_DTPMOD64: return "R_SPARC_TLS_DTPMOD64";
case R_SPARC_TLS_DTPOFF32: return "R_SPARC_TLS_DTPOFF32";
case R_SPARC_TLS_DTPOFF64: return "R_SPARC_TLS_DTPOFF64";
case R_SPARC_TLS_TPOFF32: return "R_SPARC_TLS_TPOFF32";
case R_SPARC_TLS_TPOFF64: return "R_SPARC_TLS_TPOFF64";
case R_SPARC_GOTDATA_HIX22: return "R_SPARC_GOTDATA_HIX22";
case R_SPARC_GOTDATA_LOX10: return "R_SPARC_GOTDATA_LOX10";
case R_SPARC_GOTDATA_OP_HIX22: return "R_SPARC_GOTDATA_OP_HIX22";
case R_SPARC_GOTDATA_OP_LOX10: return "R_SPARC_GOTDATA_OP_LOX10";
case R_SPARC_GOTDATA_OP: return "R_SPARC_GOTDATA_OP";
case R_SPARC_IRELATIVE: return "R_SPARC_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<S390X>(u32 r_type) {
switch (r_type) {
case R_390_NONE: return "R_390_NONE";
case R_390_8: return "R_390_8";
case R_390_12: return "R_390_12";
case R_390_16: return "R_390_16";
case R_390_32: return "R_390_32";
case R_390_PC32: return "R_390_PC32";
case R_390_GOT12: return "R_390_GOT12";
case R_390_GOT32: return "R_390_GOT32";
case R_390_PLT32: return "R_390_PLT32";
case R_390_COPY: return "R_390_COPY";
case R_390_GLOB_DAT: return "R_390_GLOB_DAT";
case R_390_JMP_SLOT: return "R_390_JMP_SLOT";
case R_390_RELATIVE: return "R_390_RELATIVE";
case R_390_GOTOFF32: return "R_390_GOTOFF32";
case R_390_GOTPC: return "R_390_GOTPC";
case R_390_GOT16: return "R_390_GOT16";
case R_390_PC16: return "R_390_PC16";
case R_390_PC16DBL: return "R_390_PC16DBL";
case R_390_PLT16DBL: return "R_390_PLT16DBL";
case R_390_PC32DBL: return "R_390_PC32DBL";
case R_390_PLT32DBL: return "R_390_PLT32DBL";
case R_390_GOTPCDBL: return "R_390_GOTPCDBL";
case R_390_64: return "R_390_64";
case R_390_PC64: return "R_390_PC64";
case R_390_GOT64: return "R_390_GOT64";
case R_390_PLT64: return "R_390_PLT64";
case R_390_GOTENT: return "R_390_GOTENT";
case R_390_GOTOFF16: return "R_390_GOTOFF16";
case R_390_GOTOFF64: return "R_390_GOTOFF64";
case R_390_GOTPLT12: return "R_390_GOTPLT12";
case R_390_GOTPLT16: return "R_390_GOTPLT16";
case R_390_GOTPLT32: return "R_390_GOTPLT32";
case R_390_GOTPLT64: return "R_390_GOTPLT64";
case R_390_GOTPLTENT: return "R_390_GOTPLTENT";
case R_390_PLTOFF16: return "R_390_PLTOFF16";
case R_390_PLTOFF32: return "R_390_PLTOFF32";
case R_390_PLTOFF64: return "R_390_PLTOFF64";
case R_390_TLS_LOAD: return "R_390_TLS_LOAD";
case R_390_TLS_GDCALL: return "R_390_TLS_GDCALL";
case R_390_TLS_LDCALL: return "R_390_TLS_LDCALL";
case R_390_TLS_GD32: return "R_390_TLS_GD32";
case R_390_TLS_GD64: return "R_390_TLS_GD64";
case R_390_TLS_GOTIE12: return "R_390_TLS_GOTIE12";
case R_390_TLS_GOTIE32: return "R_390_TLS_GOTIE32";
case R_390_TLS_GOTIE64: return "R_390_TLS_GOTIE64";
case R_390_TLS_LDM32: return "R_390_TLS_LDM32";
case R_390_TLS_LDM64: return "R_390_TLS_LDM64";
case R_390_TLS_IE32: return "R_390_TLS_IE32";
case R_390_TLS_IE64: return "R_390_TLS_IE64";
case R_390_TLS_IEENT: return "R_390_TLS_IEENT";
case R_390_TLS_LE32: return "R_390_TLS_LE32";
case R_390_TLS_LE64: return "R_390_TLS_LE64";
case R_390_TLS_LDO32: return "R_390_TLS_LDO32";
case R_390_TLS_LDO64: return "R_390_TLS_LDO64";
case R_390_TLS_DTPMOD: return "R_390_TLS_DTPMOD";
case R_390_TLS_DTPOFF: return "R_390_TLS_DTPOFF";
case R_390_TLS_TPOFF: return "R_390_TLS_TPOFF";
case R_390_20: return "R_390_20";
case R_390_GOT20: return "R_390_GOT20";
case R_390_GOTPLT20: return "R_390_GOTPLT20";
case R_390_TLS_GOTIE20: return "R_390_TLS_GOTIE20";
case R_390_IRELATIVE: return "R_390_IRELATIVE";
case R_390_PC12DBL: return "R_390_PC12DBL";
case R_390_PLT12DBL: return "R_390_PLT12DBL";
case R_390_PC24DBL: return "R_390_PC24DBL";
case R_390_PLT24DBL: return "R_390_PLT24DBL";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<M68K>(u32 r_type) {
switch (r_type) {
case R_68K_NONE: return "R_68K_NONE";
case R_68K_32: return "R_68K_32";
case R_68K_16: return "R_68K_16";
case R_68K_8: return "R_68K_8";
case R_68K_PC32: return "R_68K_PC32";
case R_68K_PC16: return "R_68K_PC16";
case R_68K_PC8: return "R_68K_PC8";
case R_68K_GOTPCREL32: return "R_68K_GOTPCREL32";
case R_68K_GOTPCREL16: return "R_68K_GOTPCREL16";
case R_68K_GOTPCREL8: return "R_68K_GOTPCREL8";
case R_68K_GOTOFF32: return "R_68K_GOTOFF32";
case R_68K_GOTOFF16: return "R_68K_GOTOFF16";
case R_68K_GOTOFF8: return "R_68K_GOTOFF8";
case R_68K_PLT32: return "R_68K_PLT32";
case R_68K_PLT16: return "R_68K_PLT16";
case R_68K_PLT8: return "R_68K_PLT8";
case R_68K_PLTOFF32: return "R_68K_PLTOFF32";
case R_68K_PLTOFF16: return "R_68K_PLTOFF16";
case R_68K_PLTOFF8: return "R_68K_PLTOFF8";
case R_68K_COPY: return "R_68K_COPY";
case R_68K_GLOB_DAT: return "R_68K_GLOB_DAT";
case R_68K_JMP_SLOT: return "R_68K_JMP_SLOT";
case R_68K_RELATIVE: return "R_68K_RELATIVE";
case R_68K_TLS_GD32: return "R_68K_TLS_GD32";
case R_68K_TLS_GD16: return "R_68K_TLS_GD16";
case R_68K_TLS_GD8: return "R_68K_TLS_GD8";
case R_68K_TLS_LDM32: return "R_68K_TLS_LDM32";
case R_68K_TLS_LDM16: return "R_68K_TLS_LDM16";
case R_68K_TLS_LDM8: return "R_68K_TLS_LDM8";
case R_68K_TLS_LDO32: return "R_68K_TLS_LDO32";
case R_68K_TLS_LDO16: return "R_68K_TLS_LDO16";
case R_68K_TLS_LDO8: return "R_68K_TLS_LDO8";
case R_68K_TLS_IE32: return "R_68K_TLS_IE32";
case R_68K_TLS_IE16: return "R_68K_TLS_IE16";
case R_68K_TLS_IE8: return "R_68K_TLS_IE8";
case R_68K_TLS_LE32: return "R_68K_TLS_LE32";
case R_68K_TLS_LE16: return "R_68K_TLS_LE16";
case R_68K_TLS_LE8: return "R_68K_TLS_LE8";
case R_68K_TLS_DTPMOD32: return "R_68K_TLS_DTPMOD32";
case R_68K_TLS_DTPREL32: return "R_68K_TLS_DTPREL32";
case R_68K_TLS_TPREL32: return "R_68K_TLS_TPREL32";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<SH4>(u32 r_type) {
switch (r_type) {
case R_SH_NONE: return "R_SH_NONE";
case R_SH_DIR32: return "R_SH_DIR32";
case R_SH_REL32: return "R_SH_REL32";
case R_SH_DIR8WPN: return "R_SH_DIR8WPN";
case R_SH_IND12W: return "R_SH_IND12W";
case R_SH_DIR8WPL: return "R_SH_DIR8WPL";
case R_SH_DIR8WPZ: return "R_SH_DIR8WPZ";
case R_SH_DIR8BP: return "R_SH_DIR8BP";
case R_SH_DIR8W: return "R_SH_DIR8W";
case R_SH_DIR8L: return "R_SH_DIR8L";
case R_SH_TLS_GD_32: return "R_SH_TLS_GD_32";
case R_SH_TLS_LD_32: return "R_SH_TLS_LD_32";
case R_SH_TLS_LDO_32: return "R_SH_TLS_LDO_32";
case R_SH_TLS_IE_32: return "R_SH_TLS_IE_32";
case R_SH_TLS_LE_32: return "R_SH_TLS_LE_32";
case R_SH_TLS_DTPMOD32: return "R_SH_TLS_DTPMOD32";
case R_SH_TLS_DTPOFF32: return "R_SH_TLS_DTPOFF32";
case R_SH_TLS_TPOFF32: return "R_SH_TLS_TPOFF32";
case R_SH_GOT32: return "R_SH_GOT32";
case R_SH_PLT32: return "R_SH_PLT32";
case R_SH_COPY: return "R_SH_COPY";
case R_SH_GLOB_DAT: return "R_SH_GLOB_DAT";
case R_SH_JMP_SLOT: return "R_SH_JMP_SLOT";
case R_SH_RELATIVE: return "R_SH_RELATIVE";
case R_SH_GOTOFF: return "R_SH_GOTOFF";
case R_SH_GOTPC: return "R_SH_GOTPC";
case R_SH_GOTPLT32: return "R_SH_GOTPLT32";
}
return "unknown (" + std::to_string(r_type) + ")";
}
template <>
std::string rel_to_string<ALPHA>(u32 r_type) {
switch (r_type) {
case R_ALPHA_NONE: return "R_ALPHA_NONE";
case R_ALPHA_REFLONG: return "R_ALPHA_REFLONG";
case R_ALPHA_REFQUAD: return "R_ALPHA_REFQUAD";
case R_ALPHA_GPREL32: return "R_ALPHA_GPREL32";
case R_ALPHA_LITERAL: return "R_ALPHA_LITERAL";
case R_ALPHA_LITUSE: return "R_ALPHA_LITUSE";
case R_ALPHA_GPDISP: return "R_ALPHA_GPDISP";
case R_ALPHA_BRADDR: return "R_ALPHA_BRADDR";
case R_ALPHA_HINT: return "R_ALPHA_HINT";
case R_ALPHA_SREL16: return "R_ALPHA_SREL16";
case R_ALPHA_SREL32: return "R_ALPHA_SREL32";
case R_ALPHA_SREL64: return "R_ALPHA_SREL64";
case R_ALPHA_GPRELHIGH: return "R_ALPHA_GPRELHIGH";
case R_ALPHA_GPRELLOW: return "R_ALPHA_GPRELLOW";
case R_ALPHA_GPREL16: return "R_ALPHA_GPREL16";
case R_ALPHA_COPY: return "R_ALPHA_COPY";
case R_ALPHA_GLOB_DAT: return "R_ALPHA_GLOB_DAT";
case R_ALPHA_JMP_SLOT: return "R_ALPHA_JMP_SLOT";
case R_ALPHA_RELATIVE: return "R_ALPHA_RELATIVE";
case R_ALPHA_BRSGP: return "R_ALPHA_BRSGP";
case R_ALPHA_TLSGD: return "R_ALPHA_TLSGD";
case R_ALPHA_TLSLDM: return "R_ALPHA_TLSLDM";
case R_ALPHA_DTPMOD64: return "R_ALPHA_DTPMOD64";
case R_ALPHA_GOTDTPREL: return "R_ALPHA_GOTDTPREL";
case R_ALPHA_DTPREL64: return "R_ALPHA_DTPREL64";
case R_ALPHA_DTPRELHI: return "R_ALPHA_DTPRELHI";
case R_ALPHA_DTPRELLO: return "R_ALPHA_DTPRELLO";
case R_ALPHA_DTPREL16: return "R_ALPHA_DTPREL16";
case R_ALPHA_GOTTPREL: return "R_ALPHA_GOTTPREL";
case R_ALPHA_TPREL64: return "R_ALPHA_TPREL64";
case R_ALPHA_TPRELHI: return "R_ALPHA_TPRELHI";
case R_ALPHA_TPRELLO: return "R_ALPHA_TPRELLO";
case R_ALPHA_TPREL16: return "R_ALPHA_TPREL16";
}
return "unknown (" + std::to_string(r_type) + ")";
}
} // namespace mold::elf
-197
View File
@@ -1,197 +0,0 @@
// This file implements a mark-sweep garbage collector for -gc-sections.
// In this algorithm, vertices are sections and edges are relocations.
// Any section that is reachable from a root section is considered alive.
#include "mold.h"
#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
template <typename E>
static bool should_keep(const InputSection<E> &isec) {
u32 type = isec.shdr().sh_type;
u32 flags = isec.shdr().sh_flags;
std::string_view name = isec.name();
return (flags & SHF_GNU_RETAIN) ||
type == SHT_NOTE ||
type == SHT_INIT_ARRAY ||
type == SHT_FINI_ARRAY ||
type == SHT_PREINIT_ARRAY ||
(is_arm32<E> && type == SHT_ARM_EXIDX) ||
name.starts_with(".ctors") ||
name.starts_with(".dtors") ||
name.starts_with(".init") ||
name.starts_with(".fini") ||
is_c_identifier(name);
}
template <typename E>
static bool mark_section(InputSection<E> *isec) {
return isec && isec->is_alive && fast_mark(isec->is_visited);
}
template <typename E>
static void visit(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
assert(isec->is_visited);
// If this is a text section, .eh_frame may contain records
// describing how to handle exceptions for that function.
// We want to keep associated .eh_frame records.
for (FdeRecord<E> &fde : isec->get_fdes())
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
if (mark_section(sym->get_input_section()))
feeder.add(sym->get_input_section());
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
// Symbol can refer either a section fragment or an input section.
// Mark a fragment as alive.
if (SectionFragment<E> *frag = sym.get_frag()) {
frag->is_alive.store(true, std::memory_order_relaxed);
continue;
}
if (!mark_section(sym.get_input_section()))
continue;
// Mark a section alive. For better performacne, we don't call
// `feeder.add` too often.
if (depth < 3)
visit(ctx, sym.get_input_section(), feeder, depth + 1);
else
feeder.add(sym.get_input_section());
}
}
template <typename E>
static void collect_root_set(Context<E> &ctx,
tbb::concurrent_vector<InputSection<E> *> &rootset) {
Timer t(ctx, "collect_root_set");
auto enqueue_section = [&](InputSection<E> *isec) {
if (mark_section(isec))
rootset.push_back(isec);
};
auto enqueue_symbol = [&](Symbol<E> *sym) {
if (sym) {
if (SectionFragment<E> *frag = sym->get_frag())
frag->is_alive.store(true, std::memory_order_relaxed);
else
enqueue_section(sym->get_input_section());
}
};
// Add sections that are not subject to garbage collection.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (!isec || !isec->is_alive)
continue;
// -gc-sections discards only SHF_ALLOC sections. If you want to
// reduce the amount of non-memory-mapped segments, you should
// use `strip` command, compile without debug info or use
// -strip-all linker option.
u32 flags = isec->shdr().sh_flags;
if (!(flags & SHF_ALLOC))
isec->is_visited = true;
if (should_keep(*isec))
enqueue_section(isec.get());
}
});
// Add sections containing exported symbols
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
if (sym->file == file && sym->is_exported)
enqueue_symbol(sym);
});
// Add sections referenced by root symbols.
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
for (std::string_view name : ctx.arg.undefined)
enqueue_symbol(get_symbol(ctx, name));
for (std::string_view name : ctx.arg.require_defined)
enqueue_symbol(get_symbol(ctx, name));
// .eh_frame consists of variable-length records called CIE and FDE
// records, and they are a unit of inclusion or exclusion.
// We just keep all CIEs and everything that are referenced by them.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (CieRecord<E> &cie : file->cies)
for (const ElfRel<E> &rel : cie.get_rels())
enqueue_symbol(file->symbols[rel.r_sym]);
});
}
// Mark all reachable sections
template <typename E>
static void mark(Context<E> &ctx,
tbb::concurrent_vector<InputSection<E> *> &rootset) {
Timer t(ctx, "mark");
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder) {
visit(ctx, isec, feeder, 0);
});
}
// Remove unreachable sections
template <typename E>
static void sweep(Context<E> &ctx) {
Timer t(ctx, "sweep");
static Counter counter("garbage_sections");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && !isec->is_visited) {
if (ctx.arg.print_gc_sections)
SyncOut(ctx) << "removing unused section " << *isec;
isec->kill();
counter++;
}
}
});
}
// Non-alloc section fragments are not subject of garbage collection.
// This function marks such fragments.
template <typename E>
static void mark_nonalloc_fragments(Context<E> &ctx) {
Timer t(ctx, "mark_nonalloc_fragments");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
if (m)
for (SectionFragment<E> *frag : m->fragments)
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
frag->is_alive.store(true, std::memory_order_relaxed);
});
}
template <typename E>
void gc_sections(Context<E> &ctx) {
Timer t(ctx, "gc");
mark_nonalloc_fragments(ctx);
tbb::concurrent_vector<InputSection<E> *> rootset;
collect_root_set(ctx, rootset);
mark(ctx, rootset);
sweep(ctx);
}
using E = MOLD_TARGET;
template void gc_sections(Context<E> &ctx);
} // namespace mold::elf
-479
View File
@@ -1,479 +0,0 @@
#include "mold.h"
#include <limits>
#include <zlib.h>
#include <zstd.h>
namespace mold::elf {
typedef enum {
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL, BASEREL, IFUNC,
} Action;
template <typename E>
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
if (get_contents() != other.get_contents())
return false;
std::span<const ElfRel<E>> x = get_rels();
std::span<const ElfRel<E>> y = other.get_rels();
if (x.size() != y.size())
return false;
for (i64 i = 0; i < x.size(); i++) {
if (x[i].r_offset - input_offset != y[i].r_offset - other.input_offset ||
x[i].r_type != y[i].r_type ||
file.symbols[x[i].r_sym] != other.file.symbols[y[i].r_sym] ||
get_addend(input_section, x[i]) != get_addend(other.input_section, y[i]))
return false;
}
return true;
}
static i64 to_p2align(u64 alignment) {
if (alignment == 0)
return 0;
return std::countr_zero(alignment);
}
template <typename E>
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
std::string_view name, 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};
if (shdr().sh_flags & SHF_COMPRESSED) {
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
sh_size = chdr.ch_size;
p2align = to_p2align(chdr.ch_addralign);
} else {
sh_size = shdr().sh_size;
p2align = to_p2align(shdr().sh_addralign);
}
// Sections may have been compressed. We usually uncompress them
// directly into the mmap'ed output file, but we want to uncompress
// early for REL-type ELF types to read relocation addends from
// section contents. For RELA-type, we don't need to do this because
// addends are in relocations.
if constexpr (!E::is_rela)
uncompress(ctx);
}
template <typename E>
void InputSection<E>::uncompress(Context<E> &ctx) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
return;
u8 *buf = new u8[sh_size];
uncompress_to(ctx, buf);
contents = std::string_view((char *)buf, sh_size);
ctx.string_pool.emplace_back(buf);
uncompressed = true;
}
template <typename E>
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
memcpy(buf, contents.data(), contents.size());
return;
}
if (contents.size() < sizeof(ElfChdr<E>))
Fatal(ctx) << *this << ": corrupted compressed section";
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
switch (hdr.ch_type) {
case ELFCOMPRESS_ZLIB: {
unsigned long size = sh_size;
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
Fatal(ctx) << *this << ": uncompress failed";
assert(size == sh_size);
break;
}
case ELFCOMPRESS_ZSTD:
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
Fatal(ctx) << *this << ": ZSTD_decompress failed";
break;
default:
Fatal(ctx) << *this << ": unsupported compression type: 0x"
<< std::hex << hdr.ch_type;
}
}
template <typename E>
static Action get_rel_action(Context<E> &ctx, Symbol<E> &sym,
const Action table[3][4]) {
auto get_output_type = [&] {
if (ctx.arg.shared)
return 0;
if (ctx.arg.pie)
return 1;
return 2;
};
auto get_sym_type = [&] {
if (sym.is_absolute())
return 0;
if (!sym.is_imported)
return 1;
if (sym.get_type() != STT_FUNC)
return 2;
return 3;
};
return table[get_output_type()][get_sym_type()];
}
template <typename E>
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
const ElfRel<E> &rel, Action action) {
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
auto error = [&] {
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
<< std::hex << rel.r_offset << " against symbol `"
<< sym << "' can not be used; recompile with " << msg;
};
auto check_textrel = [&] {
if (!writable) {
if (ctx.arg.z_text) {
error();
} else if (ctx.arg.warn_textrel) {
Warn(ctx) << isec << ": relocation against symbol `" << sym
<< "' in read-only section";
}
ctx.has_textrel = true;
}
};
auto copyrel = [&] {
assert(sym.is_imported);
if (sym.esym().st_visibility == STV_PROTECTED) {
Error(ctx) << isec
<< ": cannot make copy relocation for protected symbol '" << sym
<< "', defined in " << *sym.file << "; recompile with -fPIC";
}
sym.flags |= NEEDS_COPYREL;
};
auto dynrel = [&] {
check_textrel();
isec.file.num_dynrel++;
};
switch (action) {
case NONE:
break;
case ERROR:
error();
break;
case COPYREL:
if (!ctx.arg.z_copyreloc)
error();
copyrel();
break;
case DYN_COPYREL:
if (writable || !ctx.arg.z_copyreloc)
dynrel();
else
copyrel();
break;
case PLT:
sym.flags |= NEEDS_PLT;
break;
case CPLT:
sym.flags |= NEEDS_CPLT;
break;
case DYN_CPLT:
if (writable)
dynrel();
else
sym.flags |= NEEDS_CPLT;
break;
case DYNREL:
dynrel();
break;
case BASEREL:
check_textrel();
if (!isec.is_relr_reloc(ctx, rel))
isec.file.num_dynrel++;
break;
case IFUNC:
dynrel();
ctx.num_ifunc_dynrels.fetch_add(1, std::memory_order_relaxed);
break;
default:
unreachable();
}
}
template <typename E>
static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
// We cannot promote them to dynamic relocations because the dynamic
// linker generally does not support PC-relative relocations.
constexpr static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, PLT }, // Shared object
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
}
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
// generally does not support dynamic relocations smaller than the
// word size, we need to report an error if a relocation cannot be
// resolved at link-time.
constexpr static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, ERROR, ERROR, ERROR }, // Shared object
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
}
template <typename E>
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC;
// This is a decision table for absolute relocations for the word
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
// a dynamic relocation if we cannot resolve an address at link-time.
constexpr static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
{ NONE, NONE, DYN_COPYREL, DYN_CPLT }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
}
template <typename E>
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC;
// As a special case, we do not create copy relocations nor canonical
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
// GOT-like section, and no user-generated code directly uses values
// in it.
constexpr static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
};
return get_rel_action(ctx, sym, table);
}
template <typename E>
void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_pcrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_absrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::scan_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_dyn_absrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
}
template <typename E>
static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P, ElfRel<E> *&dynrel,
Action action) {
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
auto apply_dynrel = [&] {
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = A;
};
switch (action) {
case COPYREL:
case CPLT:
case NONE:
*(Word<E> *)loc = S + A;
break;
case BASEREL:
if (isec.is_relr_reloc(ctx, rel)) {
*(Word<E> *)loc = S + A;
} else {
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = S + A;
}
break;
case DYN_COPYREL:
if (writable || !ctx.arg.z_copyreloc)
apply_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYN_CPLT:
if (writable)
apply_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYNREL:
apply_dynrel();
break;
case IFUNC: {
if constexpr (supports_ifunc<E>) {
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = addr;
} else {
unreachable();
}
break;
}
default:
unreachable();
}
}
template <typename E>
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P,
ElfRel<E> *&dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
get_dyn_absrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::apply_toc_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P,
ElfRel<E> *&dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
get_ppc64_toc_action(ctx, sym));
}
template <typename E>
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
return;
// Copy data
if constexpr (is_riscv<E>) {
copy_contents_riscv(ctx, buf);
} else {
uncompress_to(ctx, buf);
}
// Apply relocations
if (!ctx.arg.relocatable) {
if (shdr().sh_flags & SHF_ALLOC)
apply_reloc_alloc(ctx, buf);
else
apply_reloc_nonalloc(ctx, buf);
}
}
// Get the name of a function containin a given offset.
template <typename E>
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
for (const ElfSym<E> &esym : file.elf_syms) {
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
std::string_view name = file.symbol_strtab.data() + esym.st_name;
if (ctx.arg.demangle)
return demangle(name);
return name;
}
}
return "";
}
// Record an undefined symbol error which will be displayed all at
// once by report_undef_errors().
template <typename E>
void InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
std::stringstream ss;
if (std::string_view source = file.get_source_name(); !source.empty())
ss << ">>> referenced by " << source << "\n";
else
ss << ">>> referenced by " << *this << "\n";
ss << ">>> " << file;
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
ss << ":(" << func << ")";
Symbol<E> &sym = *file.symbols[rel.r_sym];
typename decltype(ctx.undef_errors)::accessor acc;
ctx.undef_errors.insert(acc, {sym.name(), {}});
acc->second.push_back(ss.str());
}
// Report all undefined symbols, grouped by symbol.
template <typename E>
void report_undef_errors(Context<E> &ctx) {
constexpr i64 max_errors = 3;
for (auto &pair : ctx.undef_errors) {
std::string_view sym_name = pair.first;
std::span<std::string> errors = pair.second;
if (ctx.arg.demangle)
sym_name = demangle(sym_name);
std::stringstream ss;
ss << "undefined symbol: " << sym_name << "\n";
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
ss << errors[i];
if (errors.size() > max_errors)
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
Error(ctx) << ss.str();
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
Warn(ctx) << ss.str();
}
ctx.checkpoint();
}
using E = MOLD_TARGET;
template struct CieRecord<E>;
template class InputSection<E>;
template void report_undef_errors(Context<E> &);
} // namespace mold::elf
-424
View File
@@ -1,424 +0,0 @@
// On Linux, /usr/lib/x86_64-linux-gnu/libc.so is not actually
// a shared object file but an ASCII text file containing a linker
// script to include a "real" libc.so file. Therefore, we need to
// support a (very limited) subset of the linker script language.
#include "mold.h"
#include <cctype>
#include <iomanip>
namespace mold::elf {
template <typename E>
static thread_local MappedFile<Context<E>> *current_file;
template <typename E>
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
static std::string_view get_line(std::string_view input, const char *pos) {
assert(input.data() <= pos);
assert(pos < input.data() + input.size());
i64 start = input.rfind('\n', pos - input.data());
if (start == input.npos)
start = 0;
else
start++;
i64 end = input.find('\n', pos - input.data());
if (end == input.npos)
end = input.size();
return input.substr(start, end - start);
}
template <typename E>
class SyntaxError {
public:
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
std::string_view contents = current_file<E>->get_contents();
std::string_view line = get_line(contents, errpos.data());
i64 lineno = 1;
for (i64 i = 0; contents.data() + i < line.data(); i++)
if (contents[i] == '\n')
lineno++;
i64 column = errpos.data() - line.data();
std::stringstream ss;
ss << current_file<E>->name << ":" << lineno << ": ";
i64 indent = (i64)ss.tellp() + strlen("mold: ");
ss << line << "\n" << std::setw(indent + column) << " " << "^ ";
out << ss.str();
}
template <class T> SyntaxError &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
[[noreturn]] ~SyntaxError() = default;
Fatal<Context<E>> out;
};
template <typename E>
static std::vector<std::string_view>
tokenize(Context<E> &ctx, std::string_view input) {
std::vector<std::string_view> vec;
while (!input.empty()) {
if (isspace(input[0])) {
input = input.substr(1);
continue;
}
if (input.starts_with("/*")) {
i64 pos = input.find("*/", 2);
if (pos == std::string_view::npos)
SyntaxError(ctx, input) << "unclosed comment";
input = input.substr(pos + 2);
continue;
}
if (input[0] == '#') {
i64 pos = input.find("\n", 1);
if (pos == std::string_view::npos)
break;
input = input.substr(pos + 1);
continue;
}
if (input[0] == '"') {
i64 pos = input.find('"', 1);
if (pos == std::string_view::npos)
SyntaxError(ctx, input) << "unclosed string literal";
vec.push_back(input.substr(0, pos + 1));
input = input.substr(pos + 1);
continue;
}
i64 pos = input.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789_.$/\\~=+[]*?-!^:");
if (pos == 0)
pos = 1;
else if (pos == input.npos)
pos = input.size();
vec.push_back(input.substr(0, pos));
input = input.substr(pos);
}
return vec;
}
template <typename E>
static std::span<std::string_view>
skip(Context<E> &ctx, std::span<std::string_view> tok, std::string_view str) {
if (tok.empty())
Fatal(ctx) << current_file<E>->name << ": expected '" << str
<< "', but got EOF";
if (tok[0] != str)
SyntaxError(ctx, tok[0]) << "expected '" << str << "'";
return tok.subspan(1);
}
static std::string_view unquote(std::string_view s) {
if (s.size() > 0 && s[0] == '"') {
assert(s[s.size() - 1] == '"');
return s.substr(1, s.size() - 2);
}
return s;
}
template <typename E>
static std::span<std::string_view>
read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
tok = skip(ctx, tok, "(");
while (!tok.empty() && tok[0] != ")")
tok = tok.subspan(1);
if (tok.empty())
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
std::string rel = to_abs_path(path)
.lexically_relative(to_abs_path(ctx.arg.sysroot))
.string();
return rel != "." && !rel.starts_with("../");
}
template <typename E>
static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view tok) {
std::string str(unquote(tok));
// GNU ld prepends the sysroot if a pathname starts with '/' and the
// script being processed is in the sysroot. We do the same.
if (str.starts_with('/') && is_in_sysroot(ctx, current_file<E>->name))
return MappedFile<Context<E>>::must_open(ctx, ctx.arg.sysroot + str);
if (str.starts_with('=')) {
std::string path;
if (ctx.arg.sysroot.empty())
path = str.substr(1);
else
path = ctx.arg.sysroot + str.substr(1);
return MappedFile<Context<E>>::must_open(ctx, path);
}
if (str.starts_with("-l"))
return find_library(ctx, str.substr(2));
if (MappedFile<Context<E>> *mf = open_library(ctx, str))
return mf;
for (std::string_view dir : ctx.arg.library_paths) {
std::string path = std::string(dir) + "/" + str;
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
return mf;
}
SyntaxError(ctx, tok) << "library not found: " << str;
}
template <typename E>
static std::span<std::string_view>
read_group(Context<E> &ctx, std::span<std::string_view> tok) {
tok = skip(ctx, tok, "(");
while (!tok.empty() && tok[0] != ")") {
if (tok[0] == "AS_NEEDED") {
bool orig = ctx.as_needed;
ctx.as_needed = true;
tok = read_group(ctx, tok.subspan(1));
ctx.as_needed = orig;
continue;
}
MappedFile<Context<E>> *mf = resolve_path(ctx, tok[0]);
read_file(ctx, mf);
tok = tok.subspan(1);
}
if (tok.empty())
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
while (!tok.empty()) {
if (tok[0] == "OUTPUT_FORMAT") {
tok = read_output_format(ctx, tok.subspan(1));
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
tok = read_group(ctx, tok.subspan(1));
} else if (tok[0] == "VERSION") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script(ctx, tok);
tok = skip(ctx, tok, "}");
} else if (tok.size() > 3 && tok[1] == "=" && tok[3] == ";") {
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
get_symbol(ctx, unquote(tok[2])));
tok = tok.subspan(4);
} else if (tok[0] == ";") {
tok = tok.subspan(1);
} else {
SyntaxError(ctx, tok[0]) << "unknown linker script token";
}
}
}
template <typename E>
std::string_view
get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
if (tok[2] == "elf64-x86-64")
return X86_64::target_name;
if (tok[2] == "elf32-i386")
return I386::target_name;
}
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
tok[1] == "(")
if (MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::open(ctx, std::string(unquote(tok[2]))))
return get_machine_type(ctx, mf);
return "";
}
static bool read_label(std::span<std::string_view> &tok,
std::string label) {
if (tok.size() >= 1 && tok[0] == label + ":") {
tok = tok.subspan(1);
return true;
}
if (tok.size() >= 2 && tok[0] == label && tok[1] == ":") {
tok = tok.subspan(2);
return true;
}
return false;
}
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;
while (!tok.empty() && tok[0] != "}") {
if (read_label(tok, "global")) {
is_global = true;
continue;
}
if (read_label(tok, "local")) {
is_global = false;
continue;
}
if (tok[0] == "extern") {
tok = tok.subspan(1);
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);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
}
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
continue;
}
if (tok[0] == "*") {
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
ctx.default_version_from_version_script = true;
} else if (is_global) {
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
ver_str, ver_idx, is_cpp});
} else {
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
ver_str, VER_NDX_LOCAL, is_cpp});
}
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "}")
return;
tok = skip(ctx, tok, ";");
}
}
template <typename E>
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
while (!tok.empty() && tok[0] != "}") {
std::string_view ver_str;
u16 ver_idx;
if (tok[0] == "{") {
ver_str = "global";
ver_idx = VER_NDX_GLOBAL;
} else {
ver_str = tok[0];
ver_idx = next_ver++;
ctx.arg.version_definitions.push_back(std::string(tok[0]));
tok = tok.subspan(1);
}
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, false);
tok = skip(ctx, tok, "}");
if (!tok.empty() && tok[0] != ";")
tok = tok.subspan(1);
tok = skip(ctx, tok, ";");
}
}
template <typename E>
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
read_version_script(ctx, tok);
if (!tok.empty())
SyntaxError(ctx, tok[0]) << "trailing garbage token";
}
template <typename E>
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (tok[0] == "extern") {
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, true);
}
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
continue;
}
if (tok[0] == "*")
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
"global", VER_NDX_GLOBAL, is_cpp});
tok = skip(ctx, tok.subspan(1), ";");
}
}
template <typename E>
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, false);
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
if (!tok.empty())
SyntaxError(ctx, tok[0]) << "trailing garbage token";
}
using E = MOLD_TARGET;
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
} // namespace mold::elf
-5
View File
@@ -1,5 +0,0 @@
#ifdef _WIN32
# include "lto-win32.cc"
#else
# include "lto-unix.cc"
#endif
-773
View File
@@ -1,773 +0,0 @@
#include "mold.h"
#include "../archive-file.h"
#include "../cmdline.h"
#include "../output-file.h"
#include <cstring>
#include <functional>
#include <iomanip>
#include <map>
#include <regex>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/global_control.h>
#include <tbb/parallel_for_each.h>
#include <unordered_set>
#ifdef _WIN32
# include <direct.h>
# define _chdir chdir
#else
# include <unistd.h>
#endif
namespace mold::elf {
// Read the beginning of a given file and returns its machine type
// (e.g. EM_X86_64 or EM_386).
template <typename E>
std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
auto get_elf_type = [&](u8 *buf) -> std::string_view {
bool is_le = (((ElfEhdr<I386> *)buf)->e_ident[EI_DATA] == ELFDATA2LSB);
bool is_64;
u32 e_machine;
if (is_le) {
auto &ehdr = *(ElfEhdr<I386> *)buf;
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
e_machine = ehdr.e_machine;
} else {
auto &ehdr = *(ElfEhdr<M68K> *)buf;
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
e_machine = ehdr.e_machine;
}
switch (e_machine) {
case EM_386:
return I386::target_name;
case EM_X86_64:
return X86_64::target_name;
case EM_ARM:
return ARM32::target_name;
case EM_AARCH64:
return ARM64::target_name;
case EM_RISCV:
if (is_le)
return is_64 ? RV64LE::target_name : RV32LE::target_name;
return is_64 ? RV64BE::target_name : RV32BE::target_name;
case EM_PPC:
return PPC32::target_name;
case EM_PPC64:
return is_le ? PPC64V2::target_name : PPC64V1::target_name;
case EM_S390X:
return S390X::target_name;
case EM_SPARC64:
return SPARC64::target_name;
case EM_68K:
return M68K::target_name;
case EM_SH:
return SH4::target_name;
case EM_ALPHA:
return ALPHA::target_name;
default:
return "";
}
};
bool opt_plugin = !ctx.arg.plugin.empty();
switch (get_file_type(mf, opt_plugin)) {
case FileType::ELF_OBJ:
case FileType::ELF_DSO:
case FileType::GCC_LTO_OBJ:
return get_elf_type(mf->data);
case FileType::AR:
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
return get_elf_type(child->data);
return "";
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
return get_elf_type(child->data);
return "";
case FileType::TEXT:
return get_script_output_type(ctx, mf);
default:
return "";
}
}
template <typename E>
static void
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
std::string_view target = get_machine_type(ctx, mf);
if (target != ctx.arg.emulation)
Fatal(ctx) << mf->name << ": incompatible file type: "
<< ctx.arg.emulation << " is expected but got " << target;
}
template <typename E>
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
std::string archive_name) {
static Counter count("parsed_objs");
count++;
check_file_compatibility(ctx, mf);
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
file->priority = ctx.file_priority++;
ctx.tg.run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
SyncOut(ctx) << "trace: " << *file;
return file;
}
template <typename E>
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
std::string archive_name) {
static Counter count("parsed_lto_objs");
count++;
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
return nullptr;
ObjectFile<E> *file = read_lto_object(ctx, mf);
file->priority = ctx.file_priority++;
file->archive_name = archive_name;
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
file->is_alive = !file->is_in_lib;
ctx.has_lto_object = true;
if (ctx.arg.trace)
SyncOut(ctx) << "trace: " << *file;
return file;
}
template <typename E>
static SharedFile<E> *
new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
check_file_compatibility(ctx, mf);
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
file->priority = ctx.file_priority++;
ctx.tg.run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
SyncOut(ctx) << "trace: " << *file;
return file;
}
template <typename E>
void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
if (ctx.visited.contains(mf->name))
return;
bool opt_plugin = !ctx.arg.plugin.empty();
FileType type = get_file_type(mf, opt_plugin);
switch (type) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, mf, ""));
return;
case FileType::ELF_DSO:
ctx.dsos.push_back(new_shared_file(ctx, mf));
ctx.visited.insert(mf->name);
return;
case FileType::AR:
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
switch (get_file_type(child, opt_plugin)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
break;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
ctx.objs.push_back(file);
break;
default:
break;
}
}
ctx.visited.insert(mf->name);
return;
case FileType::TEXT:
parse_linker_script(ctx, mf);
return;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
if (ObjectFile<E> *file = new_lto_obj(ctx, mf, ""))
ctx.objs.push_back(file);
return;
default:
Fatal(ctx) << mf->name << ": unknown file type";
}
}
template <typename E>
static std::string_view
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
for (std::string_view arg : args)
if (!arg.starts_with('-'))
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
if (std::string_view target = get_machine_type(ctx, mf);
!target.empty())
return target;
Fatal(ctx) << "-m option is missing";
}
template <typename E>
MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
if (!mf)
return nullptr;
std::string_view target = get_machine_type(ctx, mf);
if (target.empty() || target == E::target_name)
return mf;
Warn(ctx) << path << ": skipping incompatible file " << target
<< " " << (int)E::e_machine;
return nullptr;
}
template <typename E>
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
if (name.starts_with(':')) {
for (std::string_view dir : ctx.arg.library_paths) {
std::string path = std::string(dir) + "/" + name.substr(1);
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
return mf;
}
Fatal(ctx) << "library not found: " << name;
}
for (std::string_view dir : ctx.arg.library_paths) {
std::string stem = std::string(dir) + "/lib" + name;
if (!ctx.is_static)
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".so"))
return mf;
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".a"))
return mf;
}
Fatal(ctx) << "library not found: " << name;
}
template <typename E>
MappedFile<Context<E>> *find_from_search_paths(Context<E> &ctx, std::string name) {
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, name))
return mf;
for (std::string_view dir : ctx.arg.library_paths)
if (MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::open(ctx, std::string(dir) + "/" + name))
return mf;
return nullptr;
}
template <typename E>
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
Timer t(ctx, "read_input_files");
std::vector<std::tuple<bool, bool, bool, bool>> state;
ctx.is_static = ctx.arg.is_static;
while (!args.empty()) {
std::string_view arg = args[0];
args = args.subspan(1);
if (arg == "--as-needed") {
ctx.as_needed = true;
} else if (arg == "--no-as-needed") {
ctx.as_needed = false;
} else if (arg == "--whole-archive") {
ctx.whole_archive = true;
} else if (arg == "--no-whole-archive") {
ctx.whole_archive = false;
} else if (arg == "--Bstatic") {
ctx.is_static = true;
} else if (arg == "--Bdynamic") {
ctx.is_static = false;
} else if (arg == "--start-lib") {
ctx.in_lib = true;
} else if (arg == "--end-lib") {
ctx.in_lib = false;
} else if (remove_prefix(arg, "--version-script=")) {
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--version-script: file not found: " << arg;
parse_version_script(ctx, mf);
} else if (remove_prefix(arg, "--dynamic-list=")) {
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
parse_dynamic_list(ctx, mf);
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
if (arg == "*")
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
"global", VER_NDX_GLOBAL, false});
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
parse_dynamic_list(ctx, mf);
} else if (arg == "--push-state") {
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
ctx.in_lib});
} else if (arg == "--pop-state") {
if (state.empty())
Fatal(ctx) << "no state pushed before popping";
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
state.back();
state.pop_back();
} else if (remove_prefix(arg, "-l")) {
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
mf->given_fullpath = false;
read_file(ctx, mf);
} else {
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(arg)));
}
}
if (ctx.objs.empty())
Fatal(ctx) << "no input files";
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);
unreachable();
}
template <typename E>
int elf_main(int argc, char **argv) {
Context<E> ctx;
// Process -run option first. process_run_subcommand() does not return.
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv)) {
#if defined(_WIN32) || defined(__APPLE__)
Fatal(ctx) << "-run is supported only on Unix";
#endif
process_run_subcommand(ctx, argc, argv);
}
// Parse non-positional command line options
ctx.cmdline_args = expand_response_files(ctx, argv);
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
// If no -m option is given, deduce it from input files.
if (ctx.arg.emulation.empty())
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
// 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);
Timer t_all(ctx, "all");
install_signal_handler();
if (!ctx.arg.directory.empty())
if (chdir(ctx.arg.directory.c_str()) == -1)
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
<< ": " << errno_string();
// Fork a subprocess unless --no-fork is given.
std::function<void()> on_complete;
#if !defined(_WIN32) && !defined(__APPLE__)
if (ctx.arg.fork)
on_complete = fork_child();
#endif
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
// Handle --wrap options if any.
for (std::string_view name : ctx.arg.wrap)
get_symbol(ctx, name)->is_wrapped = true;
// Handle --retain-symbols-file options if any.
if (ctx.arg.retain_symbols_file)
for (std::string_view name : *ctx.arg.retain_symbols_file)
get_symbol(ctx, name)->write_to_symtab = true;
for (std::string_view arg : ctx.arg.trace_symbol)
get_symbol(ctx, arg)->is_traced = true;
// Parse input files
read_input_files(ctx, file_args);
// Uniquify shared object files by soname
{
std::unordered_set<std::string_view> seen;
std::erase_if(ctx.dsos, [&](SharedFile<E> *file) {
return !seen.insert(file->soname).second;
});
}
Timer t_total(ctx, "total");
Timer t_before_copy(ctx, "before_copy");
// Apply -exclude-libs
apply_exclude_libs(ctx);
// Create a dummy file containing linker-synthesized symbols.
if (!ctx.arg.relocatable)
create_internal_file(ctx);
// resolve_symbols is 4 things in 1 phase:
//
// - Determine the set of object files to extract from archives.
// - Remove redundant COMDAT sections (e.g. duplicate inline functions).
// - Finally, the actual symbol resolution.
// - LTO, which requires preliminary symbol resolution before running
// and a follow-up re-resolution after the LTO objects are emitted.
//
// These passes have complex interactions, and unfortunately has to be
// put together in a single phase.
resolve_symbols(ctx);
// Resolve mergeable section pieces to merge them.
resolve_section_pieces(ctx);
// Handle --relocatable. Since the linker's behavior is quite different
// from the normal one when the option is given, the logic is implemented
// to a separate file.
if (ctx.arg.relocatable) {
combine_objects(ctx);
return 0;
}
// Create .bss sections for common symbols.
convert_common_symbols(ctx);
// Apply version scripts.
apply_version_script(ctx);
// Parse symbol version suffixes (e.g. "foo@ver1").
parse_symbol_version(ctx);
// 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);
// Garbage-collect unreachable sections.
if (ctx.arg.gc_sections)
gc_sections(ctx);
// Merge identical read-only sections.
if (ctx.arg.icf)
icf_sections(ctx);
// Compute sizes of sections containing mergeable strings.
compute_merged_section_sizes(ctx);
// Create linker-synthesized sections such as .got or .plt.
create_synthetic_sections(ctx);
// Make sure that there's no duplicate symbol
if (!ctx.arg.allow_multiple_definition)
check_duplicate_symbols(ctx);
// Warn if symbols with different types are defined under the same name.
check_symbol_types(ctx);
if constexpr (is_ppc64v1<E>)
ppc64v1_rewrite_opd(ctx);
// Bin input sections into output sections.
create_output_sections(ctx);
// Add synthetic symbols such as __ehdr_start or __end.
add_synthetic_symbols(ctx);
// Beyond this point, no new files will be added to ctx.objs
// or ctx.dsos.
// Handle `-z cet-report`.
if (ctx.arg.z_cet_report != CET_REPORT_NONE)
check_cet_errors(ctx);
// Handle `-z execstack-if-needed`.
if (ctx.arg.z_execstack_if_needed)
for (ObjectFile<E> *file : ctx.objs)
if (file->needs_executable_stack)
ctx.arg.z_execstack = true;
// If we are linking a .so file, remaining undefined symbols does
// not cause a linker error. Instead, they are treated as if they
// were imported symbols.
//
// If we are linking an executable, weak undefs are converted to
// weakly imported symbols so that they'll have another chance to be
// resolved.
claim_unresolved_symbols(ctx);
// Beyond this point, no new symbols will be added to the result.
// Handle --print-dependencies
if (ctx.arg.print_dependencies == 1)
print_dependencies(ctx);
else if (ctx.arg.print_dependencies == 2)
print_dependencies_full(ctx);
// Handle -repro
if (ctx.arg.repro)
write_repro_file(ctx);
// Handle --require-defined
for (std::string_view name : ctx.arg.require_defined)
if (!get_symbol(ctx, name)->file)
Error(ctx) << "--require-defined: undefined symbol: " << name;
// .init_array and .fini_array contents have to be sorted by
// a special rule. Sort them.
sort_init_fini(ctx);
// Likewise, .ctors and .dtors have to be sorted. They are rare
// because they are superceded by .init_array/.fini_array, though.
sort_ctor_dtor(ctx);
// Handle --shuffle-sections
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
shuffle_sections(ctx);
// Copy string referred by .dynamic to .dynstr.
for (SharedFile<E> *file : ctx.dsos)
ctx.dynstr->add_string(file->soname);
for (std::string_view str : ctx.arg.auxiliary)
ctx.dynstr->add_string(str);
for (std::string_view str : ctx.arg.filter)
ctx.dynstr->add_string(str);
if (!ctx.arg.rpaths.empty())
ctx.dynstr->add_string(ctx.arg.rpaths);
if (!ctx.arg.soname.empty())
ctx.dynstr->add_string(ctx.arg.soname);
if constexpr (is_ppc64v1<E>)
ppc64v1_scan_symbols(ctx);
// Scan relocations to find symbols that need entries in .got, .plt,
// .got.plt, .dynsym, .dynstr, etc.
scan_relocations(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
// Sort sections by section attributes so that we'll have to
// create as few segments as possible.
sort_output_sections(ctx);
// If --packed_dyn_relocs=relr was given, base relocations are stored
// to a .relr.dyn section in a compressed form. Construct a compressed
// relocations now so that we can fix section sizes and file layout.
if (ctx.arg.pack_dyn_relocs_relr)
construct_relr(ctx);
// Reserve a space for dynamic symbol strings in .dynstr and sort
// .dynsym contents if necessary. Beyond this point, no symbol will
// be added to .dynsym.
ctx.dynsym->finalize(ctx);
// Print reports about undefined symbols, if needed.
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
report_undef_errors(ctx);
// Fill .gnu.version_d section contents.
if (ctx.verdef)
ctx.verdef->construct(ctx);
// Fill .gnu.version_r section contents.
ctx.verneed->construct(ctx);
// Compute .symtab and .strtab sizes for each file.
create_output_symtab(ctx);
// .eh_frame is a special section from the linker's point of view,
// as its contents are parsed and reconstructed by the linker,
// unlike other sections that are regarded as opaque bytes.
// 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)
create_reloc_sections(ctx);
// Compute the section header values for all sections.
compute_section_headers(ctx);
// Assign offsets to output sections
i64 filesize = set_osec_offsets(ctx);
// On RISC-V, branches are encode using multiple instructions so
// that they can jump to anywhere in ±2 GiB by default. They may
// be replaced with shorter instruction sequences if destinations
// are close enough. Do this optimization.
if constexpr (is_riscv<E>)
filesize = riscv_resize_sections(ctx);
// At this point, memory layout is fixed.
// Set actual addresses to linker-synthesized symbols.
fix_synthetic_symbols(ctx);
// Beyond this, you can assume that symbol addresses including their
// GOT or PLT addresses have a correct final value.
// If --compress-debug-sections is given, compress .debug_* sections
// using zlib.
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
filesize = compress_debug_sections(ctx);
// At this point, both memory and file layouts are fixed.
t_before_copy.stop();
// Create an output file
ctx.output_file =
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0777);
ctx.buf = ctx.output_file->buf;
Timer t_copy(ctx, "copy");
// 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);
// Zero-clear paddings between sections
clear_padding(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.
if (ctx.buildid)
ctx.buildid->write_buildid(ctx);
t_copy.stop();
ctx.checkpoint();
// Close the output file. This is the end of the linker's main job.
ctx.output_file->close(ctx);
// Handle --dependency-file
if (!ctx.arg.dependency_file.empty())
write_dependency_file(ctx);
if (ctx.has_lto_object)
lto_cleanup(ctx);
t_total.stop();
t_all.stop();
if (ctx.arg.print_map)
print_map(ctx);
// Show stats numbers
if (ctx.arg.stats)
show_stats(ctx);
if (ctx.arg.perf)
print_timer_records(ctx.timer_records);
std::cout << std::flush;
std::cerr << std::flush;
if (on_complete)
on_complete();
if (ctx.arg.quick_exit)
_exit(0);
for (std::function<void()> &fn : ctx.on_exit)
fn();
ctx.checkpoint();
return 0;
}
using E = MOLD_TARGET;
template void read_file(Context<E> &, MappedFile<Context<E>> *);
#ifdef MOLD_X86_64
extern template int elf_main<I386>(int, char **);
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 **);
int main(int argc, char **argv) {
return elf_main<X86_64>(argc, argv);
}
#else
template int elf_main<E>(int, char **);
#endif
} // namespace mold::elf
-3194
View File
File diff suppressed because it is too large. Load diff
-2546
View File
File diff suppressed because it is too large. Load diff
-274
View File
@@ -1,274 +0,0 @@
// RISC instructions are usually up to 4 bytes long, so the immediates of
// their branch instructions are naturally smaller than 32 bits. This is
// contrary to x86-64 on which branch instructions take 4 bytes immediates
// and can jump to anywhere within PC ± 2 GiB.
//
// In fact, ARM32's branch instructions can jump only within ±16 MiB and
// ARM64's ±128 MiB, for example. If a branch target is further than that,
// we need to let it branch to a linker-synthesized code sequence that
// construct a full 32 bit address in a register and jump there. That
// linker-synthesized code is called "thunk".
//
// The function in this file creates thunks.
//
// Note that although thunks play an important role in an executable, they
// don't take up too much space in it. For example, among the clang-16's
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
// need more thunks on ARM32 whose branch range is shorter than ARM64.
// That said, the total size of thunks still isn't that much. Therefore,
// we don't need to try too hard to reduce thunk size to the absolute
// minimum.
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
// Branch reach in bytes.
//
// ARM64's branch has 26 bits immediate, and it's scaled by 4 because all
// instructions are 4 bytes aligned, so it's effectively 28 bits long.
// That means the range is [-2^27, 2^27).
//
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
// aligned to 2, so it's effectively 25 bits. ARM32's non-Thumb branches
// have twice longer range than its Thumb counterparts, but we
// conservatively use the Thumb's limitation.
//
// PPC's branch has 24 bits immediate, and the instructions are aligned
// to 4, therefore 26.
//
// Here is the summary of branch instructions reaches:
//
// ARM64: PC ± 128 MiB
// ARM32: PC ± 16 MiB
// PPC: PC ± 32 MiB
template <typename E>
static constexpr i64 max_distance =
1LL << (is_arm64<E> ? 27 : is_arm32<E> ? 24 : 25);
// 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;
// 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;
} else if constexpr (is_ppc32<E>) {
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
} else {
static_assert(is_ppc64<E>);
return ty == R_PPC64_REL24;
}
}
template <typename E>
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel) {
// We create thunks with a pessimistic assumption that all
// out-of-section relocations would be out-of-range.
InputSection<E> *isec2 = sym.get_input_section();
if (!isec2 || isec.output_section != isec2->output_section)
return false;
// Even if the target is the same section, we branch to its PLT
// if it has one. So a symbol with a PLT is also considered an
// out-of-section reference.
if (sym.has_plt(ctx))
return false;
// If the target section is in the same output section but
// hasn't got any address yet, that's unreacahble.
if (isec2->offset == -1)
return false;
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are within their ranges.
if constexpr (is_arm32<E>) {
bool is_thumb = sym.get_addr(ctx) & 1;
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
return false;
}
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 S = sym.get_addr(ctx, NO_OPD);
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -max_distance<E> <= val && val < max_distance<E>;
}
template <typename E>
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
for (Symbol<E> *sym : thunk.symbols) {
sym->extra.thunk_idx = -1;
sym->extra.thunk_sym_idx = -1;
sym->flags = 0;
}
}
// Scan relocations to collect symbols that need thunks.
template <typename E>
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
RangeExtensionThunk<E> &thunk) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
range_extn.resize(rels.size());
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (!needs_thunk_rel(rel))
continue;
// Skip if the symbol is undefined. apply_reloc() will report an error.
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.file)
continue;
// Skip if the destination is within reach.
if (is_reachable(ctx, isec, sym, rel))
continue;
// This relocation needs a thunk. If the symbol is already in a
// previous thunk, reuse it.
if (sym.extra.thunk_idx != -1) {
range_extn[i].thunk_idx = sym.extra.thunk_idx;
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
continue;
}
// Otherwise, add the symbol to the current thunk if it's not
// added already.
range_extn[i].thunk_idx = thunk.thunk_idx;
range_extn[i].sym_idx = -1;
if (sym.flags.exchange(-1) == 0) {
std::scoped_lock lock(thunk.mu);
thunk.symbols.push_back(&sym);
}
}
}
template <typename E>
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
std::span<InputSection<E> *> m = osec.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;
});
// We create thunks from the beginning of the section to the end.
// We manage progress using four offsets which increase monotonically.
// The locations they point to are always A <= B <= C <= D.
i64 a = 0;
i64 b = 0;
i64 c = 0;
i64 d = 0;
i64 offset = 0;
while (b < m.size()) {
// Move D foward as far as we can jump from B to anywhere in a thunk after D.
while (d < m.size() &&
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
m[b]->offset + max_distance<E>) {
offset = align_to(offset, 1 << m[d]->p2align);
m[d]->offset = offset;
offset += m[d]->sh_size;
d++;
}
// Move C forward so that C is apart from B by BATCH_SIZE. We want
// 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>)
c++;
// Move A forward so that A is reachable from C.
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
while (a < osec.thunks.size() &&
osec.thunks[a]->offset + max_distance<E> < c_offset)
reset_thunk(*osec.thunks[a++]);
// Create a thunk for input sections between B and C and place it at D.
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
thunk.thunk_idx = osec.thunks.size() - 1;
offset = align_to(offset, thunk.alignment);
thunk.offset = offset;
// Scan relocations between B and C to collect symbols that need thunks.
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
scan_rels(ctx, *isec, thunk);
});
// Now that we know the number of symbols in the thunk, we can compute
// its size.
assert(thunk.size() < max_thunk_size);
offset += thunk.size();
// Sort symbols added to the thunk to make the output deterministic.
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
return std::tuple{a->file->priority, a->sym_idx} <
std::tuple{b->file->priority, b->sym_idx};
});
// Assign offsets within the thunk to the symbols.
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
sym->extra.thunk_idx = thunk.thunk_idx;
sym->extra.thunk_sym_idx = i++;
}
// Scan relocations again to fix symbol offsets in the last thunk.
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
std::span<Symbol<E> *> syms = isec->file.symbols;
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
for (i64 i = 0; i < rels.size(); i++)
if (range_extn[i].thunk_idx == thunk.thunk_idx)
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
});
// Move B forward to point to the begining of the next batch.
b = c;
}
while (a < osec.thunks.size())
reset_thunk(*osec.thunks[a++]);
osec.shdr.sh_size = offset;
}
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
using E = MOLD_TARGET;
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
#endif
} // namespace mold::elf
-27
View File
@@ -1,27 +0,0 @@
#include "mold.h"
#include <filesystem>
#include <sys/stat.h>
namespace mold {
std::string get_realpath(std::string_view path) {
std::error_code ec;
std::string ret = std::filesystem::canonical(path, ec).string();
return ec ? std::string(path) : ret;
}
// Removes redundant '/..' or '/.' from a given path.
// The transformation is done purely by lexical processing.
// This function does not access file system.
std::string path_clean(std::string_view path) {
return filepath(path).lexically_normal().string();
}
std::filesystem::path to_abs_path(std::filesystem::path path) {
if (path.is_absolute())
return path.lexically_normal();
return (std::filesystem::current_path() / path).lexically_normal();
}
} // namespace mold
-191
View File
@@ -1,191 +0,0 @@
#pragma once
#include "mold.h"
#include "elf/elf.h"
namespace mold {
enum class FileType {
UNKNOWN,
EMPTY,
ELF_OBJ,
ELF_DSO,
MACH_OBJ,
MACH_EXE,
MACH_DYLIB,
MACH_BUNDLE,
MACH_UNIVERSAL,
AR,
THIN_AR,
TAPI,
TEXT,
GCC_LTO_OBJ,
LLVM_BITCODE,
};
template <typename C>
bool is_text_file(MappedFile<C> *mf) {
u8 *data = mf->data;
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
isprint(data[2]) && isprint(data[3]);
}
template <typename E, typename C>
inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
using namespace mold::elf;
const char *data = mf->get_contents().data();
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(data + ehdr.e_shoff);
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
// e_shstrndx is a 16-bit field. If .shstrtab's section index is
// too large, the actual number is stored to sh_link field.
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
? sh_begin->sh_link : ehdr.e_shstrndx;
for (ElfShdr<E> &sec : shdrs) {
// GCC LTO object contains only sections symbols followed by a common
// symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1` for older
// GCC releases).
//
// However, FAT LTO objects don't have any of the above mentioned symbols
// and can identify LTO by `.gnu.lto_.symtab.` section, similarly
// to what lto-plugin does. However, if LTO linker plug-in is not available,
// use the emitted assembly instead.
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
if (opt_plugin && name.starts_with (".gnu.lto_.symtab."))
return true;
if (sec.sh_type != SHT_SYMTAB)
continue;
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
auto skip = [](u8 type) {
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
};
i64 i = 1;
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
i++;
if (i < elf_syms.size() && elf_syms[i].st_shndx == SHN_COMMON) {
std::string_view name =
data + shdrs[sec.sh_link].sh_offset + elf_syms[i].st_name;
if (name.starts_with("__gnu_lto_"))
return true;
}
break;
}
return false;
}
template <typename C>
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
using namespace elf;
std::string_view data = mf->get_contents();
if (data.empty())
return FileType::EMPTY;
if (data.starts_with("\177ELF")) {
u8 byte_order = ((ElfEhdr<I386> *)data.data())->e_ident[EI_DATA];
if (byte_order == ELFDATA2LSB) {
auto &ehdr = *(ElfEhdr<I386> *)data.data();
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<I386>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<X86_64>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
}
if (ehdr.e_type == ET_DYN)
return FileType::ELF_DSO;
} else {
auto &ehdr = *(ElfEhdr<M68K> *)data.data();
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<M68K>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<SPARC64>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
}
if (ehdr.e_type == ET_DYN)
return FileType::ELF_DSO;
}
return FileType::UNKNOWN;
}
if (data.starts_with("\xcf\xfa\xed\xfe")) {
switch (*(ul32 *)(data.data() + 12)) {
case 1: // MH_OBJECT
return FileType::MACH_OBJ;
case 2: // MH_EXECUTE
return FileType::MACH_EXE;
case 6: // MH_DYLIB
return FileType::MACH_DYLIB;
case 8: // MH_BUNDLE
return FileType::MACH_BUNDLE;
}
return FileType::UNKNOWN;
}
if (data.starts_with("!<arch>\n"))
return FileType::AR;
if (data.starts_with("!<thin>\n"))
return FileType::THIN_AR;
if (data.starts_with("--- !tapi-tbd"))
return FileType::TAPI;
if (data.starts_with("\xca\xfe\xba\xbe"))
return FileType::MACH_UNIVERSAL;
if (is_text_file(mf))
return FileType::TEXT;
if (data.starts_with("\xde\xc0\x17\x0b"))
return FileType::LLVM_BITCODE;
if (data.starts_with("BC\xc0\xde"))
return FileType::LLVM_BITCODE;
return FileType::UNKNOWN;
}
inline std::string filetype_to_string(FileType type) {
switch (type) {
case FileType::UNKNOWN: return "UNKNOWN";
case FileType::EMPTY: return "EMPTY";
case FileType::ELF_OBJ: return "ELF_OBJ";
case FileType::ELF_DSO: return "ELF_DSO";
case FileType::MACH_EXE: return "MACH_EXE";
case FileType::MACH_OBJ: return "MACH_OBJ";
case FileType::MACH_DYLIB: return "MACH_DYLIB";
case FileType::MACH_BUNDLE: return "MACH_BUNDLE";
case FileType::MACH_UNIVERSAL: return "MACH_UNIVERSAL";
case FileType::AR: return "AR";
case FileType::THIN_AR: return "THIN_AR";
case FileType::TAPI: return "TAPI";
case FileType::TEXT: return "TEXT";
case FileType::GCC_LTO_OBJ: return "GCC_LTO_OBJ";
case FileType::LLVM_BITCODE: return "LLVM_BITCODE";
}
return "UNKNOWN";
}
inline std::ostream &operator<<(std::ostream &out, FileType type) {
out << filetype_to_string(type);
return out;
}
} // namespace mold
-149
View File
@@ -1,149 +0,0 @@
#include "mold.h"
#include <cstring>
namespace mold {
std::optional<Glob> Glob::compile(std::string_view pat) {
std::vector<Element> vec;
while (!pat.empty()) {
u8 c = pat[0];
pat = pat.substr(1);
switch (c) {
case '[': {
// Here are a few bracket pattern examples:
//
// [abc]: a, b or c
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [^a-z]: Any character except lowercase letters
vec.push_back({BRACKET});
std::bitset<256> &bitset = vec.back().bitset;
bool negate = false;
if (!pat.empty() && pat[0] == '^') {
negate = true;
pat = pat.substr(1);
}
bool closed = false;
while (!pat.empty()) {
if (pat[0] == ']') {
pat = pat.substr(1);
closed = true;
break;
}
if (pat[0] == '\\') {
pat = pat.substr(1);
if (pat.empty())
return {};
}
if (pat.size() >= 3 && pat[1] == '-') {
u8 start = pat[0];
u8 end = pat[2];
pat = pat.substr(3);
if (end == '\\') {
if (pat.empty())
return {};
end = pat[0];
pat = pat.substr(1);
}
if (end < start)
return {};
for (i64 i = start; i <= end; i++)
bitset[i] = true;
} else {
bitset[(u8)pat[0]] = true;
pat = pat.substr(1);
}
}
if (!closed)
return {};
if (negate)
bitset.flip();
break;
}
case '?':
vec.push_back({QUESTION});
break;
case '*':
vec.push_back({STAR});
break;
default:
if (vec.empty() || vec.back().kind != STRING)
vec.push_back({STRING});
vec.back().str += c;
break;
}
}
return {Glob{std::move(vec)}};
}
bool Glob::match(std::string_view str) {
return do_match(str, elements);
}
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
while (!elements.empty()) {
Element &e = elements[0];
elements = elements.subspan(1);
switch (e.kind) {
case STRING:
if (str.empty() || !str.starts_with(e.str))
return false;
str = str.substr(e.str.size());
break;
case STAR:
if (elements.empty())
return true;
// Patterns like "*foo*bar*" should be much more common than more
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
// the former case here.
if (elements[0].kind == STRING) {
for (;;) {
size_t pos = str.find(elements[0].str);
if (pos == str.npos)
break;
if (do_match(str.substr(pos + elements[0].str.size()),
elements.subspan(1)))
return true;
str = str.substr(pos + 1);
}
return false;
}
// Other cases are handled here.
for (i64 j = 0; j < str.size(); j++)
if (do_match(str.substr(j), elements))
return true;
return false;
case QUESTION:
if (str.empty())
return false;
str = str.substr(1);
break;
case BRACKET:
if (str.empty() || !e.bitset[str[0]])
return false;
str = str.substr(1);
break;
}
}
return str.empty();
}
} // namespace mold
+40 -30
View File
@@ -1,44 +1,54 @@
#!/bin/bash
#!/bin/sh
# This script installs binary packages needed to build mold.
# Feel free to send me a PR if your OS is not on this list.
set -e
source /etc/os-release
. /etc/os-release
set -x
# The first line for each distro installs a build dependency.
# The second line installs extra packages for unittests.
#
# Feel free to send me a PR if your OS is not on this list.
case "$ID-$VERSION_ID" in
ubuntu-20.* | pop-20.*)
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
apt-get install -y file
apt-get install -y cmake gcc g++ clang gdb
;;
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
apt-get install -y file
fedora | fedora-* | amzn | rhel | centos)
dnf install -y gcc-c++ cmake glibc-static libstdc++-static diffutils util-linux tar
;;
fedora-*)
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
dnf install -y glibc-static file libstdc++-static diffutils util-linux
rocky | ol)
dnf install -y gcc-c++ cmake diffutils util-linux
;;
opensuse-leap-*)
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
zypper install -y glibc-devel-static tar diffutils util-linux
opensuse-*)
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux gawk
;;
opensuse-tumbleweed-*)
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
zypper install -y glibc-devel-static tar diffutils util-linux
;;
gentoo-*)
gentoo)
emerge-webrsync
emerge dev-util/cmake sys-libs/zlib
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
;;
arch-*)
pacman -Sy
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
arch | archarm | artix | endeavouros | manjaro | cachyos)
pacman -Sy --needed --noconfirm base-devel cmake util-linux
;;
void)
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
;;
alpine)
apk update
apk add bash make linux-headers cmake gcc g++
;;
clear-linux-os)
swupd update
swupd bundle-add c-basic diffutils
;;
almalinux)
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel cmake diffutils
;;
altlinux)
apt-get update
apt-get install -y gcc-c++ make cmake ctest diffutils util-linux
;;
freebsd)
pkg update
pkg install -y cmake bash binutils gcc
;;
*)
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" in
ubuntu | pop | linuxmint | debian | raspbian)
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
exit 1
esac
-221
View File
@@ -1,221 +0,0 @@
// This file defines integral types for file input/output. We need to use
// these types instead of the plain integers (such as uint32_t or int32_t)
// when reading from/writing to an mmap'ed file area for the following
// reasons:
//
// 1. mold is always a cross linker and should not depend on what host it
// is running on. Users should be able to run mold on a big-endian
// SPARC machine to create a little-endian RV64 binary, for example.
//
// 2. Even though data members in all ELF data strucutres are naturally
// aligned, they are not guaranteed to be aligned on memory. Because
// archive file (.a file) aligns each member only to a 2 byte boundary,
// anything larger than 2 bytes may be unaligned in an mmap'ed memory.
// Unaligned access is an undefined behavior in C/C++, so we shouldn't
// cast an arbitrary pointer to a uint32_t, for example, to read a
// 32-bits value.
//
// The data types defined in this file don't depend on host byte order and
// don't do unaligned access.
#pragma once
#include <bit>
#include <cstdint>
#include <cstring>
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __LITTLE_ENDIAN__ 1
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __BIG_ENDIAN__ 1
# else
# error "unknown host byte order"
# endif
#endif
namespace mold {
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
template <typename T>
static inline T bswap(T val) {
switch (sizeof(T)) {
case 2: return __builtin_bswap16(val);
case 4: return __builtin_bswap32(val);
case 8: return __builtin_bswap64(val);
default: __builtin_unreachable();
}
}
template <typename T, int SIZE = sizeof(T)>
class LittleEndian {
public:
LittleEndian() = default;
LittleEndian(T x) { *this = x; }
operator T() const {
if constexpr (sizeof(T) == SIZE) {
T x;
memcpy(&x, val, sizeof(T));
if constexpr (std::endian::native == std::endian::big)
x = bswap(x);
return x;
} else {
static_assert(SIZE == 3);
return (val[2] << 16) | (val[1] << 8) | val[0];
}
}
LittleEndian &operator=(T x) {
if constexpr (sizeof(T) == SIZE) {
if constexpr (std::endian::native == std::endian::big)
x = bswap(x);
memcpy(val, &x, sizeof(T));
} else {
static_assert(SIZE == 3);
val[2] = x >> 16;
val[1] = x >> 8;
val[0] = x;
}
return *this;
}
LittleEndian &operator++() {
return *this = *this + 1;
}
LittleEndian operator++(int) {
T ret = *this;
*this = *this + 1;
return ret;
}
LittleEndian &operator--() {
return *this = *this - 1;
}
LittleEndian operator--(int) {
T ret = *this;
*this = *this - 1;
return ret;
}
LittleEndian &operator+=(T x) {
return *this = *this + x;
}
LittleEndian &operator-=(T x) {
return *this = *this - x;
}
LittleEndian &operator&=(T x) {
return *this = *this & x;
}
LittleEndian &operator|=(T x) {
return *this = *this | x;
}
private:
u8 val[SIZE];
};
using il16 = LittleEndian<i16>;
using il32 = LittleEndian<i32>;
using il64 = LittleEndian<i64>;
using ul16 = LittleEndian<u16>;
using ul24 = LittleEndian<u32, 3>;
using ul32 = LittleEndian<u32>;
using ul64 = LittleEndian<u64>;
template <typename T, int SIZE = sizeof(T)>
class BigEndian {
public:
BigEndian() = default;
BigEndian(T x) { *this = x; }
operator T() const {
if constexpr (sizeof(T) == SIZE) {
T x;
memcpy(&x, val, sizeof(T));
if constexpr (std::endian::native == std::endian::little)
x = bswap(x);
return x;
} else {
static_assert(SIZE == 3);
return (val[0] << 16) | (val[1] << 8) | val[2];
}
}
BigEndian &operator=(T x) {
if constexpr (sizeof(T) == SIZE) {
if constexpr (std::endian::native == std::endian::little)
x = bswap(x);
memcpy(val, &x, sizeof(T));
} else {
static_assert(SIZE == 3);
val[0] = x >> 16;
val[1] = x >> 8;
val[2] = x;
}
return *this;
}
BigEndian &operator++() {
return *this = *this + 1;
}
BigEndian operator++(int) {
T ret = *this;
*this = *this + 1;
return ret;
}
BigEndian &operator--() {
return *this = *this - 1;
}
BigEndian operator--(int) {
T ret = *this;
*this = *this - 1;
return ret;
}
BigEndian &operator+=(T x) {
return *this = *this + x;
}
BigEndian &operator-=(T x) {
return *this = *this - x;
}
BigEndian &operator&=(T x) {
return *this = *this & x;
}
BigEndian &operator|=(T x) {
return *this = *this | x;
}
private:
u8 val[SIZE];
};
using ib16 = BigEndian<i16>;
using ib32 = BigEndian<i32>;
using ib64 = BigEndian<i64>;
using ub16 = BigEndian<u16>;
using ub24 = BigEndian<u32, 3>;
using ub32 = BigEndian<u32>;
using ub64 = BigEndian<u64>;
} // namespace mold
+132
View File
@@ -0,0 +1,132 @@
// This file implements the Aho-Corasick algorithm to search multiple
// strings within an input string simultaneously. It is essentially a
// trie with additional links. For details, see
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
//
// We use it for simple glob patterns in version scripts or dynamic
// list files. Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// Aho-Corasick can do only substring search, so it cannot handle
// complex glob patterns such as `*foo*bar*`. We handle such patterns
// with the Glob class.
#include "lib.h"
#include <queue>
namespace mold {
bool AhoCorasick::can_handle(std::string_view pat) {
if (pat.starts_with('*'))
pat.remove_prefix(1);
if (pat.ends_with('*'))
pat.remove_suffix(1);
return pat.find_first_of("*?[") == pat.npos;
}
i64 AhoCorasick::find(std::string_view str) {
if (nodes.empty())
return -1;
i64 idx = 0;
i64 val = -1;
auto walk = [&](u8 c) {
for (i64 j = idx; j != -1; j = nodes[j].suffix_link) {
i64 child = nodes[j].children[c];
if (child != -1) {
idx = child;
val = std::max(val, nodes[child].value);
return;
}
}
idx = 0;
};
walk('\0');
for (u8 c : str)
walk(c);
walk('\0');
return val;
}
bool AhoCorasick::add(std::string_view pat, i64 val) {
assert(can_handle(pat));
if (nodes.empty())
nodes.resize(1);
i64 idx = 0;
auto walk = [&](u8 c) {
if (nodes[idx].children[c] == -1) {
nodes[idx].children[c] = nodes.size();
nodes.resize(nodes.size() + 1);
}
idx = nodes[idx].children[c];
};
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
// substring matching, so we use \0 as a beginning/end-of-string
// markers.
if (!pat.starts_with('*'))
walk('\0');
for (u8 c : pat)
if (c != '*')
walk(c);
if (!pat.ends_with('*'))
walk('\0');
nodes[idx].value = std::max(nodes[idx].value, val);
return true;
}
void AhoCorasick::compile() {
if (nodes.empty())
return;
fix_suffix_links(0);
fix_values();
}
void AhoCorasick::fix_suffix_links(i64 idx) {
for (i64 i = 0; i < 256; i++) {
i64 child = nodes[idx].children[i];
if (child == -1)
continue;
i64 j = nodes[idx].suffix_link;
for (; j != -1; j = nodes[j].suffix_link) {
if (nodes[j].children[i] != -1) {
nodes[child].suffix_link = j;
break;
}
}
if (j == -1)
nodes[child].suffix_link = 0;
fix_suffix_links(child);
}
}
void AhoCorasick::fix_values() {
std::queue<i64> queue;
queue.push(0);
do {
i64 idx = queue.front();
queue.pop();
for (i64 child : nodes[idx].children) {
if (child != -1) {
i64 suffix = nodes[child].suffix_link;
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
queue.push(child);
}
}
} while (!queue.empty());
}
} // namespace mold
+50
View File
@@ -0,0 +1,50 @@
// This is the same as std::atomic except that the default memory
// order is relaxed instead of sequential consistency.
#pragma once
#include <atomic>
namespace mold {
template <typename T>
struct Atomic : std::atomic<T> {
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
using std::atomic<T>::atomic;
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
Atomic<T> &operator=(const Atomic<T> &other) {
store(other.load());
return *this;
}
void operator=(T val) { store(val); }
operator T() const { return load(); }
void store(T val, std::memory_order order = relaxed) {
std::atomic<T>::store(val, order);
}
T load(std::memory_order order = relaxed) const {
return std::atomic<T>::load(order);
}
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
bool test_and_set() {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return load() || exchange(true);
}
};
} // namespace mold
+78
View File
@@ -0,0 +1,78 @@
#pragma once
#include "integers.h"
#include <cassert>
#include <vector>
namespace mold {
class BitvectorProxy {
public:
BitvectorProxy(u64 &word, size_t pos)
: word(word), mask(1ULL << pos) {}
BitvectorProxy &operator=(bool val) {
if (val)
word |= mask;
else
word &= ~mask;
return *this;
}
BitvectorProxy &operator=(const BitvectorProxy &other) {
return *this = (bool)other;
}
operator bool() const {
return word & mask;
}
private:
u64 &word;
u64 mask;
};
class Bitvector {
public:
Bitvector() = default;
Bitvector(i64 n) : size(n), words((n + 63) / 64) {}
void resize(i64 n) {
words.clear();
words.resize((n + 63) / 64);
size = n;
}
Bitvector &operator|=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] |= x.words[i];
return *this;
}
Bitvector &operator&=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] &= x.words[i];
return *this;
}
Bitvector &operator<<=(size_t n) {
assert(n == 1);
for (i64 i = words.size() - 1; i > 0; i--)
words[i] = (words[i] << 1) | (words[i - 1] >> 63);
words[0] <<= 1;
return *this;
}
BitvectorProxy operator[](size_t pos) {
assert(pos < size);
return {words[pos / 64], pos % 64};
}
i64 size = 0;
std::vector<u64> words;
};
} // namespace mold
+44 -55
View File
@@ -14,7 +14,7 @@
// a little bit. However, if a shard size is large enough, that loss
// is negligible in practice.
#include "mold.h"
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
@@ -30,42 +30,41 @@ namespace mold {
static constexpr i64 SHARD_SIZE = 1024 * 1024;
static std::vector<std::string_view> split(std::string_view input) {
std::vector<std::string_view> shards;
while (input.size() >= SHARD_SIZE) {
shards.push_back(input.substr(0, SHARD_SIZE));
input = input.substr(SHARD_SIZE);
}
if (!input.empty())
shards.push_back(input);
return shards;
Compressor::~Compressor() {
for (std::span<u8> shard : shards)
delete[] shard.data();
}
static std::vector<u8> zlib_compress(std::string_view input) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, we chose compression
// level 1.
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
static std::vector<std::span<u8>> split(std::span<u8> input) {
std::vector<std::span<u8>> vec;
while (!input.empty()) {
i64 sz = std::min<i64>(SHARD_SIZE, input.size());
vec.push_back(input.subspan(0, sz));
input = input.subspan(sz);
}
return vec;
}
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
static std::span<u8> zlib_compress(std::span<u8> input, int level) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, the default level is 1.
z_stream strm = {};
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
// Set an input buffer
strm.avail_in = input.size();
strm.next_in = (u8 *)input.data();
strm.next_in = input.data();
// Set an output buffer. deflateBound() returns an upper bound
// on the compression size. +16 for Z_SYNC_FLUSH.
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
i64 bufsize = deflateBound(&strm, strm.avail_in) + 16;
u8 *buf = new u8[bufsize];
// Compress data. It writes all compressed bytes except the last
// partial byte, so up to 7 bits can be held to be written to the
// buffer.
strm.avail_out = buf.size();
strm.next_out = buf.data();
strm.avail_out = bufsize;
strm.next_out = buf;
CHECK(deflate(&strm, Z_BLOCK));
// This is a workaround for libbacktrace before 2022-04-06.
@@ -84,29 +83,26 @@ static std::vector<u8> zlib_compress(std::string_view input) {
// that the bit position is 7 in the next byte.
//
// https://github.com/ianlancetaylor/libbacktrace/pull/87
int nbits;
int nbits = 0;
deflatePending(&strm, Z_NULL, &nbits);
if (nbits == 5)
CHECK(deflatePrime(&strm, 10, 2));
CHECK(deflate(&strm, Z_SYNC_FLUSH));
assert(strm.avail_out > 0);
buf.resize(buf.size() - strm.avail_out);
buf.shrink_to_fit();
deflateEnd(&strm);
return buf;
return {buf, (size_t)(bufsize - strm.avail_out)};
}
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
std::vector<u64> adlers(inputs.size());
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
std::vector<u32> adlers(inputs.size());
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = zlib_compress(inputs[i]);
std::span<u8> in = inputs[i];
adlers[i] = adler32(1, in.data(), in.size());
shards[i] = zlib_compress(in, level);
});
// Combine checksums
@@ -116,7 +112,7 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
// Comput the total size
compressed_size = 8; // the header and the trailer
for (std::vector<u8> &shard : shards)
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
@@ -127,47 +123,40 @@ void ZlibCompressor::write_to(u8 *buf) {
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 2; // +2 for header
offsets[0] = 2; // +2 for the header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
});
// Write a trailer
u8 *end = buf + compressed_size;
end[-6] = 3;
end[-5] = 0;
// Write a checksum
*(ub32 *)(end - 4) = checksum;
}
static std::vector<u8> zstd_compress(std::string_view input) {
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
constexpr int level = 3; // compression level; must be between 1 to 22
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
level);
static std::span<u8> zstd_compress(std::span<u8> input, int level) {
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
u8 *buf = new u8[bufsize];
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
assert(!ZSTD_isError(sz));
buf.resize(sz);
buf.shrink_to_fit();
return buf;
return {buf, sz};
}
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size, i64 level) {
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
shards[i] = zstd_compress(inputs[i]);
shards[i] = zstd_compress(inputs[i], level);
});
compressed_size = 0;
for (std::vector<u8> &shard : shards)
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
@@ -178,7 +167,7 @@ void ZstdCompressor::write_to(u8 *buf) {
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
});
}
+29
View File
@@ -0,0 +1,29 @@
#define MOLD_VERSION "@mold_VERSION@"
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
#define MOLD_FIRST_TARGET @MOLD_FIRST_TARGET@
#cmakedefine01 HAVE_MADVISE
#cmakedefine01 HAVE_UNAME
#cmakedefine01 MOLD_USE_MIMALLOC
#cmakedefine01 MOLD_USE_SYSTEM_MIMALLOC
#cmakedefine01 HAVE_TARGET_X86_64
#cmakedefine01 HAVE_TARGET_I386
#cmakedefine01 HAVE_TARGET_ARM64LE
#cmakedefine01 HAVE_TARGET_ARM64BE
#cmakedefine01 HAVE_TARGET_ARM32LE
#cmakedefine01 HAVE_TARGET_ARM32BE
#cmakedefine01 HAVE_TARGET_RV32LE
#cmakedefine01 HAVE_TARGET_RV32BE
#cmakedefine01 HAVE_TARGET_RV64LE
#cmakedefine01 HAVE_TARGET_RV64BE
#cmakedefine01 HAVE_TARGET_PPC32
#cmakedefine01 HAVE_TARGET_PPC64V1
#cmakedefine01 HAVE_TARGET_PPC64V2
#cmakedefine01 HAVE_TARGET_S390X
#cmakedefine01 HAVE_TARGET_SPARC64
#cmakedefine01 HAVE_TARGET_M68K
#cmakedefine01 HAVE_TARGET_SH4LE
#cmakedefine01 HAVE_TARGET_SH4BE
#cmakedefine01 HAVE_TARGET_LOONGARCH64
#cmakedefine01 HAVE_TARGET_LOONGARCH32
+60
View File
@@ -0,0 +1,60 @@
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
namespace mold {
// This function "forges" a CRC. That is, given the current and a desired
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
// the original data to yield the desired CRC. Trailing garbage is ignored
// by many bianry file formats, so you can create a file with a desired
// CRC using crc32_solve(). We need it for --separate-debug-file.
std::vector<u8> crc32_solve(u32 current, u32 desired) {
constexpr u32 poly = 0xedb88320;
u32 x = ~desired;
// Each iteration computes x = (x * x^-1) mod poly.
for (i64 i = 0; i < 32; i++) {
x = std::rotl(x, 1);
x ^= (x & 1) * (poly << 1);
}
x ^= ~current;
std::vector<u8> out(4);
out[0] = x;
out[1] = x >> 8;
out[2] = x >> 16;
out[3] = x >> 24;
return out;
}
// Compute a CRC for given data in parallel
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
struct Shard {
u8 *buf;
i64 len;
u32 crc;
};
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
std::vector<Shard> shards;
while (len > 0) {
i64 sz = std::min(len, shard_size);
shards.push_back({buf, sz, 0});
buf += sz;
len -= sz;
}
tbb::parallel_for_each(shards, [](Shard &shard) {
shard.crc = crc32(0, shard.buf, shard.len);
});
for (Shard &shard : shards)
crc = crc32_combine(crc, shard.crc, shard.len);
return crc;
}
} // namespace mold
+42
View File
@@ -0,0 +1,42 @@
#include "lib.h"
#include <cstdlib>
#ifndef _WIN32
#include <cxxabi.h>
#endif
#include "../third-party/rust-demangle/rust-demangle.h"
namespace mold {
std::optional<std::string_view> demangle_cpp(std::string_view name) {
static thread_local char *buf;
static thread_local size_t buflen;
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
#ifndef _WIN32
if (name.starts_with("_Z")) {
int status;
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
if (status == 0) {
buf = p;
return p;
}
}
#endif
return {};
}
std::optional<std::string_view> demangle_rust(std::string_view name) {
static thread_local char *buf;
free(buf);
buf = rust_demangle(std::string(name).c_str(), 0);
if (buf)
return buf;
return {};
}
} // namespace mold
+42
View File
@@ -0,0 +1,42 @@
#include "lib.h"
#include <filesystem>
#include <sys/stat.h>
#ifdef __APPLE__
# include <mach-o/dyld.h>
#endif
#ifdef __FreeBSD__
# include <sys/sysctl.h>
#endif
namespace mold {
// Returns the path of the mold executable itself
std::string get_self_path() {
#if __APPLE__ || _WIN32
fprintf(stderr, "mold: get_self_path is not supported");
exit(1);
#elif __FreeBSD__
// /proc may not be mounted on FreeBSD. The proper way to get the
// current executable's path is to use sysctl(2).
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t size;
sysctl(mib, 4, NULL, &size, NULL, 0);
std::string path;
path.resize(size);
sysctl(mib, 4, path.data(), &size, NULL, 0);
return path;
#else
return std::filesystem::read_symlink("/proc/self/exe").string();
#endif
}
} // namespace mold
+16 -15
View File
@@ -1,14 +1,14 @@
#!/bin/bash
#
# This test script takes a Gentoo package name and tries to build it
# using mold in a Docker environment. We chose Gentoo Linux as a test
# using mold in a Podman environment. We chose Gentoo Linux as a test
# target, because its source-based package allows us to build programs
# locally and run their test suites without any hassle.
#
# You can get a complete list of Gentoo packages availalbe for testing
# with the following command:
#
# docker run --rm mold-gentoo emerge --color n -s '' | \
# podman run --rm mold-gentoo emerge --color n -s '' | \
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
package="$1"
@@ -20,47 +20,48 @@ fi
set -x
# Create a Docker image
if ! docker image ls mold-gentoo | grep -q mold-gentoo; then
# Create a Podman image
if ! podman image ls mold-gentoo | grep -q mold-gentoo; then
set -e
cat <<EOF | docker build -t mold-gentoo -
FROM gentoo/stage3
cat <<EOF | podman build -t mold-gentoo -
FROM docker.io/gentoo/stage3
RUN emerge-webrsync
RUN echo 'USE="X ssl elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary"' >> /etc/portage/make.conf && \
RUN echo 'USE="X ssl elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary -perl"' >> /etc/portage/make.conf && \
echo 'ACCEPT_KEYWORDS="~amd64"' >> /etc/portage/make.conf && \
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
echo 'FEATURES="\${FEATURE} noclean nostrip ccache -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf && \
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf
RUN emerge gdb lld clang vim emacs strace ccache xeyes dev-util/cmake dev-vcs/git && rm -rf /var/tmp/portage
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf && \
FEATURES='getbinpkg binpkg-request-signature' emerge gdb lld llvm-core/clang vim emacs strace ccache xeyes dev-build/cmake dev-vcs/git && \
rm -rf /var/tmp/portage
EOF
set +e
fi
git_hash=$(./mold --version | perl -ne '/\((\w+)/; print $1;')
git_hash=$(./dist/mold --version | perl -ne '/\((\w+)/; print $1;')
if [ "$package" = dev-libs/concurrencykit ]; then
echo "Skipping known broken package: $package"
exit 0
fi
# Build a given package in Docker
cmd1='(cd /usr/bin; ln -sf /mold/mold $(realpath ld))'
# Build a given package in Podman
cmd1='(cd /usr/bin; ln -sf /mold/dist/mold $(realpath ld))'
cmd2="MAKEOPTS=-'j$(nproc) --load-average=100' emerge --onlydeps $package"
cmd3="MAKEOPTS='-j$(nproc) --load-average=100' FEATURES=test emerge $package"
filename=`echo "$package" | sed 's!/!_!g'`
docker="docker run --rm --cap-add=SYS_PTRACE -v `pwd`:/mold -v /var/cache/ccache-gentoo:/ccache mold-gentoo timeout -v -k 15s 1h"
podman="podman run --rm --pids-limit=-1 --cap-add=SYS_PTRACE -v `pwd`:/mold:ro -v /var/cache/ccache-gentoo:/ccache mold-gentoo timeout -v -k 15s 3h"
dir=gentoo/$git_hash
mkdir -p "$dir"/success "$dir"/failure
$docker nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
$podman chrt --idle 0 nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
if [ $? = 0 ]; then
mv "$dir"/"$filename".mold "$dir"/success
else
mv "$dir"/"$filename".mold "$dir"/failure
fi
$docker nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
$podman chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
if [ $? = 0 ]; then
mv "$dir"/"$filename".ld "$dir"/success
else
+256
View File
@@ -0,0 +1,256 @@
// This file implements a glob matcher that can run multiple glob patterns
// against an input string. mold uses the glob matcher for symbol name
// patterns in a version script or a dynamic list file. Since we may need to
// match hundreds of glob patterns against millions of symbol names, the
// speed of the matcher is very important.
//
// The pattern match implemented in this file is NFA-based, although the
// cost of the function is O(n*m), where n is the number of NFA states and m
// is the length of the input string. We do not use recursion or
// backtracking, unlike a generic NFA-based regular expression matcher. This
// is doable because glob patterns are very limited subsets of regexes.
//
// Here is the explanation of the algorithm. Observe that the only "tricky"
// meta-character in a glob pattern is "*", which matches zero or more
// characters. Other characters and meta-characters always match a single
// input character. So the key of the algorithm is to handle "*" efficiently.
//
// We can represent a glob pattern "a*b*" with three NFA states: q_start, q1
// and q_accept, with the following transition functions:
//
// δ(q_start, "a") = q1
// δ(q1, <any character>) = q1
// δ(q1, "b") = q_accept
// δ(q_accept, <any character>) = q_accept
//
// We can construct such an NFA in a straightforward manner. We maintain NFA
// states as a list, with the initial contents being the start state. Each
// character except for "*" creates a new NFA state, adds a transition from
// the last state in the list to the new one, and appends the new state at
// the end of the list. "*" sets the "is_star" flag on the last NFA state.
// The flag indicates that the state machine can remain in the state for any
// input character.
//
// An NFA constructed this way doesn't have any complicated loops,
// ε-transitions, or anything like that. Each state has only one incoming
// edge. The only loops in the state transition are the self-loops on states
// followed by a "*". Aside from that, the state machine progresses linearly
// from the start state to the accept state.
//
// Each state of an NFA can be represented by a single bit. If a bit is 1,
// the non-deterministic state machine is in that state. Otherwise, it's
// not. Observe that a state with the "is_star" flag will continued to be 1
// once it becomes 1, since the state machine can loop over the state on any
// input character.
//
// With that observation, we can represent an NFA with a bit vector of N
// bits, where N is the number of NFA states. For each input character, bit
// M becomes 1 if
//
// - bit M-1 is 1 and there's a transition from state_{M-1} to state_M
// with the given character, or
// - bit M is 1 and state_M's "is_star" flag is 1.
//
// Initially, the 0th bit is 1 for the start state. At each step, the bits
// propagate from least significant to most significant positions, at most
// one bit at a time. If the most significant bit is 1 after the entire
// input has been processed, the string matches.
//
// This propagation can be implemented with bitwise OR, bitwise AND, and a
// one-bit bit shift on the bit vector. All these operations are very cheap.
//
// We can combine multiple glob matchers into a single matcher by simply
// concatenating the bit vectors of their state machines.
#include "lib.h"
#include <cstring>
namespace mold {
static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
std::vector<MultiGlob::State> vec(1);
while (!pat.empty()) {
u8 c = pat[0];
pat = pat.substr(1);
std::bitset<256> chars;
switch (c) {
case '*':
vec.back().is_star = true;
continue;
case '?':
chars.set();
break;
case '\\':
if (pat.empty())
return {};
chars[pat[0]] = true;
pat = pat.substr(1);
break;
case '[': {
// Here are a few bracket pattern examples:
//
// [abc]: a, b or c
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [!a-z]: Any character except lowercase letters
//
// Both `!` and `^` are accepted as negation markers. `!` is the
// POSIX/shell convention used by other linkers. `^` was mold's
// original syntax and is kept for backward compatibility.
bool negate = false;
bool closed = false;
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
negate = true;
pat = pat.substr(1);
}
while (!pat.empty()) {
if (pat[0] == ']') {
pat = pat.substr(1);
closed = true;
break;
}
if (pat[0] == '\\') {
pat = pat.substr(1);
if (pat.empty())
return {};
}
if (pat.size() >= 3 && pat[1] == '-') {
u8 start = pat[0];
u8 end = pat[2];
pat = pat.substr(3);
if (end == '\\') {
if (pat.empty())
return {};
end = pat[0];
pat = pat.substr(1);
}
if (end < start)
return {};
for (i64 i = start; i <= end; i++)
chars[i] = true;
} else {
chars[pat[0]] = true;
pat = pat.substr(1);
}
}
if (!closed)
return {};
if (negate)
chars.flip();
break;
}
default:
chars[c] = true;
break;
}
vec.push_back({chars, false});
}
return vec;
}
// Instead of returning just a match/no match boolean value, our glob
// matcher returns an integer value associated with each given pattern.
// If multiple patterns match at the same time, the largest associated
// value will be returned by find().
bool MultiGlob::add(std::string_view pat, i64 val) {
std::vector<State> vec = parse_glob(pat);
if (vec.empty())
return false;
patterns.push_back({std::move(vec), val});
return true;
}
void MultiGlob::compile() {
if (patterns.empty())
return;
ranges::stable_sort(patterns, ranges::greater(), &GlobPattern::value);
std::vector<State> states;
for (GlobPattern &p : patterns)
append(states, p.states);
i64 sz = states.size();
start_states.resize(sz);
for (i64 pos = 0; GlobPattern &p : patterns) {
start_states[pos] = true;
pos += p.states.size();
}
star_mask.resize(sz);
for (i64 i = 0; i < sz; i++)
if (states[i].is_star)
star_mask[i] = true;
for (i64 i = 0; i < 256; i++) {
char_mask[i].resize(sz);
for (i64 j = 0; j < sz; j++)
if (states[j].incoming_edge[i])
char_mask[i][j] = true;
}
}
i64 MultiGlob::find(std::string_view str) {
if (patterns.empty())
return -1;
Bitvector bits = start_states;
Bitvector tmp;
for (u8 c : str) {
// This is equivalent to
//
// bits = (bits & star_mask) | ((bits << 1) & char_mask[c])
//
// but we update the existing objects in place to avoid allocating
// temporary objects.
tmp = bits;
tmp &= star_mask;
bits <<= 1;
bits &= char_mask[c];
bits |= tmp;
}
for (i64 pos = 0; GlobPattern &p : patterns) {
pos += p.states.size();
if (bits[pos - 1])
return p.value;
}
return -1;
}
bool Glob::add(std::string_view pat, i64 val) {
assert(val >= 0);
assert(!is_compiled);
// If the pattern requires only a single substring search, the
// Aho-Corasick algorithm is even faster than our glob matcher.
if (aho_corasick.can_handle(pat))
return aho_corasick.add(pat, val);
return multi_glob.add(pat, val);
}
i64 Glob::find(std::string_view str) {
std::call_once(once, [&] {
multi_glob.compile();
aho_corasick.compile();
is_compiled = true;
});
return std::max(multi_glob.find(str), aho_corasick.find(str));
}
} // namespace mold
+2 -2
View File
@@ -4,7 +4,7 @@
// For more info, read
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
#include "mold.h"
#include "lib.h"
#include <cmath>
@@ -13,7 +13,7 @@ namespace mold {
i64 HyperLogLog::get_cardinality() const {
double z = 0;
for (i64 val : buckets)
z += pow(2, -val);
z += std::ldexp(1.0, -val);
return ALPHA * NBUCKETS * NBUCKETS / z;
}
+127
View File
@@ -0,0 +1,127 @@
// This file defines integral types for file input/output. We need to use
// these types instead of the plain integers (such as uint32_t or int32_t)
// when reading from/writing to an mmap'ed file area for the following
// reasons:
//
// 1. mold is always a cross linker and should not depend on what host it
// is running on. For example, users should be able to run mold on a
// little-endian x86 machine to create a big-endian s390x binary.
//
// 2. Even though data members in all ELF data strucutres are naturally
// aligned, they are not guaranteed to be aligned on memory because of
// archive files. Archive files (.a files) align each file only to a
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
// in an mmap'ed memory. Misaligned access is an undefined behavior in
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
// example, to read a 32 bit value.
//
// The data types defined in this file are independent of the host byte
// order and are designed to avoid unaligned access.
//
// Note that in C/C++, memcpy is a portable and efficient way to access
// unaligned data, as it is typically treated as an intrinsic. Compilers
// can easily optimize memcpy calls in this file into a single load or
// store instruction.
#pragma once
#include <bit>
#include <cstdint>
#include <cstring>
#include <type_traits>
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __LITTLE_ENDIAN__ 1
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __BIG_ENDIAN__ 1
# else
# error "unknown host byte order"
# endif
#endif
namespace mold {
template <typename T, bool is_le, int size = sizeof(T)>
class Integer {
public:
constexpr Integer() = default;
constexpr Integer(T v) {
if (std::is_constant_evaluated() || size == 3) {
for (int i = 0; i < size; i++)
buf[is_le ? i : (size - i - 1)] = v >> (i * 8);
} else {
if (!is_native)
v = bswap(v);
memcpy(buf, &v, size);
}
}
operator T() const {
if (size == 3) {
if (is_le)
return buf[2] << 16 | buf[1] << 8 | buf[0];
return buf[0] << 16 | buf[1] << 8 | buf[2];
}
T v;
memcpy(&v, buf, size);
return is_native ? v : bswap(v);
}
Integer &operator=(T v) { new (this) Integer(v); return *this; }
Integer &operator++() { return *this = *this + 1; }
Integer operator++(int) { auto x = *this; ++*this; return x; }
Integer &operator--() { return *this = *this - 1; }
Integer operator--(int) { auto x = *this; --*this; return x; }
Integer &operator+=(T v) { return *this = *this + v; }
Integer &operator-=(T v) { return *this = *this - v; }
Integer &operator&=(T v) { return *this = *this & v; }
Integer &operator|=(T v) { return *this = *this | v; }
private:
static constexpr bool is_native =
(std::endian::native == (is_le ? std::endian::little : std::endian::big));
static T bswap(T v) {
switch (size) {
case 2: return __builtin_bswap16(v);
case 4: return __builtin_bswap32(v);
case 8: return __builtin_bswap64(v);
default: __builtin_unreachable();
}
}
uint8_t buf[size];
};
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using il16 = Integer<i16, true>;
using il32 = Integer<i32, true>;
using il64 = Integer<i64, true>;
using ul16 = Integer<u16, true>;
using ul24 = Integer<u32, true, 3>;
using ul32 = Integer<u32, true>;
using ul64 = Integer<u64, true>;
using ib16 = Integer<i16, false>;
using ib32 = Integer<i32, false>;
using ib64 = Integer<i64, false>;
using ub16 = Integer<u16, false>;
using ub24 = Integer<u32, false, 3>;
using ub32 = Integer<u32, false>;
using ub64 = Integer<u64, false>;
} // namespace mold
+709
View File
@@ -0,0 +1,709 @@
#pragma once
#include "atomics.h"
#include "integers.h"
#include "bitvector.h"
#include <array>
#include <atomic>
#include <bit>
#include <bitset>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <optional>
#include <span>
#include <sstream>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/concurrent_vector.h>
#include <tbb/enumerable_thread_specific.h>
#include <tbb/parallel_for.h>
#include <vector>
#ifdef _WIN32
# include <io.h>
#else
# include <sys/mman.h>
# include <unistd.h>
#endif
#define XXH_INLINE_ALL 1
#include "../third-party/xxhash/xxhash.h"
#ifdef NDEBUG
# define unreachable() __builtin_unreachable()
#else
# define unreachable() assert(0 && "unreachable")
#endif
inline uint64_t hash_string(std::string_view str) {
return XXH3_64bits(str.data(), str.size());
}
class HashCmp {
public:
static size_t hash(const std::string_view &k) {
return hash_string(k);
}
static bool equal(const std::string_view &k1, const std::string_view &k2) {
return k1 == k2;
}
};
namespace mold {
namespace ranges = std::ranges;
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
inline u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
//
// 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.emplace_back(record);
}
Timer(const Timer &) = delete;
~Timer() {
record->stop();
}
void stop() {
record->stop();
}
private:
TimerRecord *record;
};
//
// Utility functions
//
// Some C++ libraries haven't implemented std::has_single_bit yet.
inline bool has_single_bit(u64 val) {
return std::popcount(val) == 1;
}
// Some C++ libraries haven't implemented std::bit_ceil yet.
inline u64 bit_ceil(u64 val) {
if (has_single_bit(val))
return val;
return 1LL << (64 - std::countl_zero(val));
}
inline u64 align_to(u64 val, u64 align) {
if (align == 0)
return val;
assert(has_single_bit(align));
return (val + align - 1) & ~(align - 1);
}
inline u64 align_down(u64 val, u64 align) {
assert(has_single_bit(align));
return val & ~(align - 1);
}
inline u64 bit(u64 val, i64 pos) {
return (val >> pos) & 1;
};
// Returns [hi:lo] bits of val.
inline u64 bits(u64 val, u64 hi, u64 lo) {
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
}
// Cast val to a signed N bit integer.
// For example, sign_extend(x, 32) == (i32)x for any integer x.
inline i64 sign_extend(u64 val, i64 n) {
return (i64)(val << (64 - n)) >> (64 - n);
}
inline bool is_int(u64 val, i64 n) {
return sign_extend(val, n) == val;
}
template <typename T, typename Compare = std::less<T>>
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(new_val, old_val) &&
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
template <typename T, typename Compare = std::less<T>>
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(old_val, new_val) &&
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
template <typename T>
inline void append(std::vector<T> &x, const auto &y) {
x.insert(x.end(), y.begin(), y.end());
}
template <typename T>
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
i64 size = 0;
for (std::vector<T> &v : vec)
size += v.size();
std::vector<T> ret;
ret.reserve(size);
for (std::vector<T> &v : vec)
append(ret, v);
return ret;
}
template <typename T>
inline void remove_duplicates(std::vector<T> &vec) {
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
}
inline i64 write_string(void *buf, std::string_view str) {
memcpy(buf, str.data(), str.size());
*((u8 *)buf + str.size()) = '\0';
return str.size() + 1;
}
template <typename T>
inline void write_vector(void *buf, const std::vector<T> &vec) {
if (!vec.empty())
memcpy(buf, vec.data(), vec.size() * sizeof(T));
}
inline void encode_uleb(std::vector<u8> &vec, u64 val) {
do {
u8 byte = val & 0x7f;
val >>= 7;
vec.push_back(val ? (byte | 0x80) : byte);
} while (val);
}
inline void encode_sleb(std::vector<u8> &vec, i64 val) {
for (;;) {
u8 byte = val & 0x7f;
val >>= 7;
bool neg = (byte & 0x40);
if ((val == 0 && !neg) || (val == -1 && neg)) {
vec.push_back(byte);
break;
}
vec.push_back(byte | 0x80);
}
}
inline i64 write_uleb(u8 *buf, u64 val) {
i64 i = 0;
do {
u8 byte = val & 0x7f;
val >>= 7;
buf[i++] = val ? (byte | 0x80) : byte;
} while (val);
return i;
}
inline u64 read_uleb(u8 **buf) {
u64 val = 0;
u8 shift = 0;
u8 byte;
do {
byte = *(*buf)++;
val |= (byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
return val;
}
inline u64 read_uleb(u8 *buf) {
u8 *tmp = buf;
return read_uleb(&tmp);
}
inline i64 read_sleb(u8 **buf) {
u64 val = 0;
u8 shift = 0;
u8 byte;
do {
byte = *(*buf)++;
val |= (byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
return sign_extend(val, shift);
}
inline i64 read_sleb(u8 *buf) {
u8 *tmp = buf;
return read_sleb(&tmp);
}
inline u64 read_uleb(std::string_view *str) {
u8 *start = (u8 *)str->data();
u8 *ptr = start;
u64 val = read_uleb(&ptr);
*str = str->substr(ptr - start);
return val;
}
inline u64 read_uleb(std::string_view str) {
std::string_view tmp = str;
return read_uleb(&tmp);
}
inline i64 uleb_size(u64 val) {
for (int i = 1; i < 9; i++)
if (val < (1LL << (7 * i)))
return i;
return 9;
}
inline void overwrite_uleb(u8 *loc, u64 val) {
while (*loc & 0b1000'0000) {
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
val >>= 7;
}
*loc = val & 0b0111'1111;
}
static inline void pause() {
#if defined(__x86_64__)
asm volatile("pause");
#elif defined(__aarch64__)
asm volatile("yield");
#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_8A__)
asm volatile("yield");
#endif
}
//
// Concurrent Map
//
// This is an implementation of a fast concurrent hash map. Unlike
// ordinary hash tables, this impl just aborts if it becomes full.
// So you need to give a correct estimation of the final size before
// using it. We use this hash map to uniquify pieces of data in
// mergeable sections.
//
// We've implemented this ourselves because the performance of
// conrurent hash map is critical for our linker.
template <typename T>
class ConcurrentMap {
public:
ConcurrentMap() = default;
ConcurrentMap(i64 nbuckets) {
resize(nbuckets);
}
~ConcurrentMap() {
if (entries) {
#ifdef _WIN32
_aligned_free(entries);
#else
munmap(entries, sizeof(Entry) * nbuckets);
#endif
}
}
// 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;
u32 keylen;
T value;
};
void resize(i64 nbuckets) {
assert(!entries);
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
i64 bufsize = sizeof(Entry) * this->nbuckets;
// Allocate a zero-initialized buffer. mmap() is faster than
// malloc()+memset(), and MAP_POPULATE pre-faults pages here to
// avoid CoW faults and TLB shootdowns under concurrent inserts.
#ifdef _WIN32
entries = (Entry *)_aligned_malloc(bufsize, alignof(Entry));
memset((void *)entries, 0, bufsize);
#elif MAP_POPULATE
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0);
#else
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
#endif
}
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
assert(has_single_bit(nbuckets));
u64 begin = hash & (nbuckets - 1);
u64 mask = nbuckets / NUM_SHARDS - 1;
for (i64 i = 0; i < MAX_RETRY; i++) {
u64 idx = (begin & ~mask) | ((begin + i) & mask);
Entry &ent = entries[idx];
// It seems avoiding compare-and-swap is faster overall at least
// on my Zen4 machine, so do it.
if (const char *ptr = ent.key.load(std::memory_order_acquire);
ptr != nullptr && ptr != (char *)-1) {
if (key == std::string_view(ptr, ent.keylen))
return {&ent.value, false};
continue;
}
// Otherwise, use CAS to atomically claim the ownership of the slot.
const char *ptr = nullptr;
bool claimed = ent.key.compare_exchange_strong(ptr, (char *)-1,
std::memory_order_acquire);
// If we successfully claimed the ownership of the 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};
}
// 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 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};
}
std::cerr << "ConcurrentMap is full\n";
abort();
}
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 (begin < end && 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 = 4096;
static constexpr i64 NUM_SHARDS = 16;
static constexpr i64 MAX_RETRY = 256;
Entry *entries = nullptr;
u64 nbuckets = 0;
};
//
// random.cc
//
void get_random_bytes(u8 *buf, i64 size);
//
// hyperloglog.cc
//
class HyperLogLog {
public:
void insert(u64 hash) {
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
}
i64 get_cardinality() const;
void merge(const HyperLogLog &other) {
for (i64 i = 0; i < NBUCKETS; i++)
update_maximum(buckets[i], other.buckets[i]);
}
private:
static constexpr i64 NBUCKETS = 2048;
static constexpr double ALPHA = 0.79402;
Atomic<u8> buckets[NBUCKETS];
};
//
// aho-corasick.cc
//
class AhoCorasick {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return nodes.empty(); }
void compile();
i64 find(std::string_view str);
static bool can_handle(std::string_view str);
private:
struct TrieNode {
TrieNode() { children.fill(-1); }
i64 value = -1;
i32 suffix_link = -1;
std::array<i32, 256> children;
};
void fix_suffix_links(i64 idx);
void fix_values();
std::vector<TrieNode> nodes;
};
//
// glob.cc
//
class MultiGlob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return patterns.empty(); }
void compile();
i64 find(std::string_view str);
struct State {
std::bitset<256> incoming_edge;
bool is_star = false;
};
struct GlobPattern {
std::vector<State> states;
i64 value = -1;
};
private:
std::vector<GlobPattern> patterns;
Bitvector start_states;
Bitvector star_mask;
Bitvector char_mask[256];
};
class Glob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return multi_glob.empty() && aho_corasick.empty(); }
i64 find(std::string_view str);
private:
std::once_flag once;
bool is_compiled = false;
MultiGlob multi_glob;
AhoCorasick aho_corasick;
};
//
// filepath.cc
//
inline std::filesystem::path path_dirname(std::string_view path) {
return std::filesystem::path(path).parent_path();
}
inline std::string path_filename(std::string_view path) {
return std::filesystem::path(path).filename().string();
}
inline std::string path_clean(std::string_view path) {
return std::filesystem::path(path).lexically_normal().string();
}
std::string get_self_path();
//
// demangle.cc
//
std::optional<std::string_view> demangle_cpp(std::string_view name);
std::optional<std::string_view> demangle_rust(std::string_view name);
//
// crc32.cc
//
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
std::vector<u8> crc32_solve(u32 current, u32 desired);
//
// compress.cc
//
class Compressor {
public:
virtual void write_to(u8 *buf) = 0;
virtual ~Compressor();
i64 compressed_size = 0;
protected:
std::vector<std::span<u8>> shards;
};
class ZlibCompressor : public Compressor {
public:
ZlibCompressor(u8 *buf, i64 size, i64 level);
void write_to(u8 *buf) override;
private:
u32 checksum = 0;
};
class ZstdCompressor : public Compressor {
public:
ZstdCompressor(u8 *buf, i64 size, i64 level);
void write_to(u8 *buf) override;
};
//
// tar.cc
//
// TarFile is a class to create a tar file.
//
// If you pass `--repro` to mold, mold collects all input files and
// put them into `<output-file-path>.repro.tar`, so that it is easy to
// run the same command with the same command line arguments.
class TarWriter {
public:
static std::unique_ptr<TarWriter>
open(std::string output_path, std::string basedir);
~TarWriter();
void append(std::string path, std::string_view data);
private:
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
FILE *out = nullptr;
std::string basedir;
};
} // namespace mold
+12 -19
View File
@@ -1,10 +1,13 @@
#include "mold.h"
#include "lib.h"
#include <functional>
#include <iomanip>
#include <ios>
#include <tbb/concurrent_vector.h>
#ifndef _WIN32
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/resource.h>
#include <sys/time.h>
#endif
@@ -16,9 +19,7 @@ i64 Counter::get_value() {
}
void Counter::print() {
sort(instances, [](Counter *a, Counter *b) {
return a->get_value() > b->get_value();
});
ranges::stable_sort(instances, {}, [](Counter *x) { return x->get_value(); });
for (Counter *c : instances)
std::cout << std::setw(20) << std::right << c->name
@@ -26,19 +27,13 @@ void Counter::print() {
}
static i64 now_nsec() {
#ifdef _WIN32
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (i64)t.tv_sec * 1000000000 + t.tv_nsec;
#endif
}
static std::pair<i64, i64> get_usage() {
#ifdef _WIN32
auto to_nsec = [](FILETIME t) -> i64 {
return ((u64)t.dwHighDateTime << 32 + (u64)t.dwLowDateTime) * 100;
return (((u64)t.dwHighDateTime << 32) + (u64)t.dwLowDateTime) * 100;
};
FILETIME creation, exit, kernel, user;
@@ -46,7 +41,7 @@ static std::pair<i64, i64> get_usage() {
return {to_nsec(user), to_nsec(kernel)};
#else
auto to_nsec = [](struct timeval t) -> i64 {
return (i64)t.tv_sec * 1000000000 + t.tv_usec * 1000;
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
};
struct rusage ru;
@@ -79,15 +74,13 @@ void TimerRecord::stop() {
static void print_rec(TimerRecord &rec, i64 indent) {
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
((double)rec.user / 1000000000),
((double)rec.sys / 1000000000),
(((double)rec.end - rec.start) / 1000000000),
((double)rec.user / 1'000'000'000),
((double)rec.sys / 1'000'000'000),
(((double)rec.end - rec.start) / 1'000'000'000),
std::string(indent * 2, ' ').c_str(),
rec.name.c_str());
sort(rec.children, [](TimerRecord *a, TimerRecord *b) {
return a->start < b->start;
});
ranges::stable_sort(rec.children, {}, &TimerRecord::start);
for (TimerRecord *child : rec.children)
print_rec(*child, indent + 1);
+20
View File
@@ -0,0 +1,20 @@
#include "lib.h"
#include <random>
namespace mold {
void get_random_bytes(u8 *buf, i64 size) {
std::random_device rand;
i64 i = 0;
for (; i < size - 4; i += 4) {
u32 val = rand();
memcpy(buf + i, &val, 4);
}
u32 val = rand();
memcpy(buf + i, &val, size - i);
}
} // namespace mold
+144
View File
@@ -0,0 +1,144 @@
// This is a header-only C++20 implementation of SipHash based on the
// reference implementation. To use, just copy this header file into
// your project and #include it.
//
// https://github.com/rui314/siphash/blob/main/siphash.h
#include <bit>
#include <cstdint>
#include <cstring>
template <int C_ROUNDS, int D_ROUNDS, int OUTLEN>
class SipHashTmpl {
public:
static_assert(OUTLEN == 64 || OUTLEN == 128);
SipHashTmpl(void *key) {
uint64_t k0 = read64(key);
uint64_t k1 = read64((char *)key + 8);
v0 = 0x736f6d6570736575 ^ k0;
v1 = 0x646f72616e646f6d ^ k1;
v2 = 0x6c7967656e657261 ^ k0;
v3 = 0x7465646279746573 ^ k1;
if (OUTLEN == 128)
v1 ^= 0xee;
}
void update(void *msgp, int64_t msglen) {
char *msg = (char *)msgp;
sum += msglen;
if (buflen) {
if (buflen + msglen < 8) {
memcpy(buf + buflen, msg, msglen);
buflen += msglen;
return;
}
int j = 8 - buflen;
memcpy(buf + buflen, msg, j);
compress(read64(buf));
msg += j;
msglen -= j;
buflen = 0;
}
while (msglen >= 8) {
compress(read64(msg));
msg += 8;
msglen -= 8;
}
memcpy(buf, msg, msglen);
buflen = msglen;
}
void finish(void *out) {
memset(buf + buflen, 0, 8 - buflen);
compress(((uint64_t)sum << 56) | read64(buf));
v2 ^= (OUTLEN == 128) ? 0xee : 0xff;
finalize();
write64(out, v0 ^ v1 ^ v2 ^ v3);
if (OUTLEN == 128) {
v1 ^= 0xdd;
finalize();
write64((char *)out + 8, v0 ^ v1 ^ v2 ^ v3);
}
}
static void hash(void *out, void *key, void *in, int inlen) {
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN> h(key);
h.update(in, inlen);
h.finish(out);
}
private:
uint64_t v0, v1, v2, v3;
uint8_t buf[8];
uint8_t buflen = 0;
uint8_t sum = 0;
uint64_t read64(void *loc) {
uint64_t val;
memcpy(&val, loc, 8);
if (std::endian::native == std::endian::big)
val = bswap(val);
return val;
}
void write64(void *loc, uint64_t val) {
if (std::endian::native == std::endian::big)
val = bswap(val);
memcpy(loc, &val, 8);
}
uint64_t bswap(uint64_t val) {
return ((val << 56) & 0xff00000000000000) |
((val << 40) & 0x00ff000000000000) |
((val << 24) & 0x0000ff0000000000) |
((val << 8) & 0x000000ff00000000) |
((val >> 8) & 0x00000000ff000000) |
((val >> 24) & 0x0000000000ff0000) |
((val >> 40) & 0x000000000000ff00) |
((val >> 56) & 0x00000000000000ff);
}
void round() {
v0 += v1;
v1 = std::rotl(v1, 13);
v1 ^= v0;
v0 = std::rotl(v0, 32);
v2 += v3;
v3 = std::rotl(v3, 16);
v3 ^= v2;
v0 += v3;
v3 = std::rotl(v3, 21);
v3 ^= v0;
v2 += v1;
v1 = std::rotl(v1, 17);
v1 ^= v2;
v2 = std::rotl(v2, 32);
}
void compress(uint64_t m) {
v3 ^= m;
for (int i = 0; i < C_ROUNDS; i++)
round();
v0 ^= m;
}
void finalize() {
for (int i = 0; i < D_ROUNDS; i++)
round();
}
};
using SipHash = SipHashTmpl<2, 4, 64>;
using SipHash128 = SipHashTmpl<2, 4, 128>;
using SipHash13 = SipHashTmpl<1, 3, 64>;
using SipHash13_128 = SipHashTmpl<1, 3, 128>;
+40 -30
View File
@@ -1,7 +1,15 @@
#include "mold.h"
// This file contains functions to create a tar file.
#include "lib.h"
#ifdef _WIN32
# define ftruncate _chsize_s
#endif
namespace mold {
static constexpr i64 BLOCK_SIZE = 512;
// A tar file consists of one or more Ustar header followed by data.
// Each Ustar header represents a single file in an archive.
//
@@ -11,23 +19,6 @@ namespace mold {
//
// For simplicity, we always emit a PAX header even for a short filename.
struct UstarHeader {
UstarHeader() {
memset(this, 0, sizeof(*this));
}
void finalize() {
memset(checksum, ' ', sizeof(checksum));
memcpy(magic, "ustar", 5);
memcpy(version, "00", 2);
// Compute checksum
int sum = 0;
for (i64 i = 0; i < sizeof(*this); i++)
sum += ((u8 *)this)[i];
assert(sum < 01000000);
snprintf(checksum, sizeof(checksum), "%06o", sum);
}
char name[100];
char mode[8];
char uid[8];
@@ -47,6 +38,25 @@ struct UstarHeader {
char pad[12];
};
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
static void finalize(UstarHeader &hdr) {
memset(hdr.checksum, ' ', sizeof(hdr.checksum));
memcpy(hdr.magic, "ustar", 5);
memcpy(hdr.version, "00", 2);
// Compute checksum
int sum = 0;
for (i64 i = 0; i < sizeof(hdr); i++)
sum += ((u8 *)&hdr)[i];
// We need to convince the compiler that sum isn't too big to silence
// -Werror=format-truncation.
if (sum >= 01'000'000)
unreachable();
snprintf(hdr.checksum, sizeof(hdr.checksum), "%06o", sum);
}
static std::string encode_path(std::string basedir, std::string path) {
path = path_clean(basedir + "/" + path);
@@ -73,13 +83,16 @@ TarWriter::~TarWriter() {
void TarWriter::append(std::string path, std::string_view data) {
// Write PAX header
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
UstarHeader pax;
UstarHeader pax = {};
std::string attr = encode_path(basedir, path);
if (attr.size() >= 0100'000'000'000)
unreachable();
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
pax.name[0] = '/';
pax.typeflag[0] = 'x';
pax.finalize();
finalize(pax);
fwrite(&pax, sizeof(pax), 1, out);
// Write pathname
@@ -87,23 +100,20 @@ void TarWriter::append(std::string path, std::string_view data) {
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
// Write Ustar header
UstarHeader ustar;
UstarHeader ustar = {};
memcpy(ustar.mode, "0000664", 8);
if (data.size() >= 0100'000'000'000)
unreachable();
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
ustar.finalize();
finalize(ustar);
fwrite(&ustar, sizeof(ustar), 1, out);
// Write file contents
fwrite(data.data(), data.size(), 1, out);
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
// A tar file must ends with two empty blocks, so write such
// terminator and seek back.
u8 terminator[BLOCK_SIZE * 2] = {};
fwrite(&terminator, BLOCK_SIZE * 2, 1, out);
fseek(out, -BLOCK_SIZE * 2, SEEK_END);
assert(ftell(out) % BLOCK_SIZE == 0);
// A tar file must ends with two empty blocks
(void)!ftruncate(fileno(out), ftell(out) + BLOCK_SIZE * 2);
}
} // namespace mold
@@ -17,11 +17,11 @@ if(EXISTS "${SOURCE_DIR}/.git/HEAD")
endif()
# Create new file contents and update a given file if necessary.
set(NEW_CONTENTS "#include <string>
namespace mold {
std::string mold_git_hash = \"${HASH}\";
}
")
if("${HASH}" STREQUAL "")
set(NEW_CONTENTS "")
else()
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
endif()
if(EXISTS "${OUTPUT_FILE}")
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
-156
View File
@@ -1,156 +0,0 @@
#include "mold.h"
#include "config.h"
#include <cstring>
#include <filesystem>
#include <signal.h>
#include <tbb/global_control.h>
#ifdef USE_SYSTEM_MIMALLOC
#include <mimalloc-new-delete.h>
#endif
#ifdef __FreeBSD__
# include <sys/sysctl.h>
# include <unistd.h>
#endif
namespace mold {
std::string mold_version_string = MOLD_VERSION;
namespace elf {
int main(int argc, char **argv);
}
namespace macho {
int main(int argc, char **argv);
}
static std::string get_mold_version() {
std::string name = MOLD_IS_SOLD ? "mold (sold) " : "mold ";
if (mold_git_hash.empty())
return name + MOLD_VERSION + " (compatible with GNU ld)";
return name + MOLD_VERSION + " (" + mold_git_hash + "; compatible with GNU ld)";
}
void cleanup() {
if (output_tmpfile)
unlink(output_tmpfile);
}
std::string errno_string() {
// strerror is not thread-safe, so guard it with a lock.
static std::mutex mu;
std::scoped_lock lock(mu);
return strerror(errno);
}
// Returns the path of the mold executable itself
std::string get_self_path() {
#ifdef __FreeBSD__
// /proc may not be mounted on FreeBSD. The proper way to get the
// current executable's path is to use sysctl(2).
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t size;
sysctl(mib, 4, NULL, &size, NULL, 0);
std::string path;
path.resize(size);
sysctl(mib, 4, path.data(), &size, NULL, 0);
return path;
#else
return std::filesystem::read_symlink("/proc/self/exe").string();
#endif
}
// mold mmap's an output file, and the mmap succeeds even if there's
// no enough space left on the filesystem. The actual disk blocks are
// not allocated on the mmap call but when the program writes to it
// for the first time.
//
// If a disk becomes full as a result of a write to an mmap'ed memory
// region, the failure of the write is reported as a SIGBUS or structured
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
// signal handler catches that signal and prints out a user-friendly
// error message. Without this, it is very hard to realize that the
// disk might be full.
#ifdef _WIN32
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
static std::mutex mu;
std::scoped_lock lock{mu};
PEXCEPTION_RECORD exception_record = exception_info->ExceptionRecord;
ULONG_PTR *exception_information = exception_record->ExceptionInformation;
if (exception_record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
(ULONG_PTR)output_buffer_start <= exception_information[1] &&
exception_information[1] < (ULONG_PTR)output_buffer_end) {
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
(void)!write(_fileno(stderr), msg, sizeof(msg) - 1);
}
cleanup();
_exit(1);
}
void install_signal_handler() {
AddVectoredExceptionHandler(0, vectored_handler);
}
#else
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
static std::mutex mu;
std::scoped_lock lock{mu};
if ((signo == SIGSEGV || signo == SIGBUS) &&
output_buffer_start <= info->si_addr &&
info->si_addr < output_buffer_end) {
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
}
cleanup();
_exit(1);
}
void install_signal_handler() {
struct sigaction action;
action.sa_sigaction = sighandler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &action, NULL);
sigaction(SIGTERM, &action, NULL);
sigaction(SIGBUS, &action, NULL);
}
#endif
i64 get_default_thread_count() {
// mold doesn't scale well above 32 threads.
int n = tbb::global_control::active_value(
tbb::global_control::max_allowed_parallelism);
return std::min(n, 32);
}
} // namespace mold
int main(int argc, char **argv) {
mold::mold_version = mold::get_mold_version();
#if MOLD_IS_SOLD
std::string cmd = mold::filepath(argv[0]).filename().string();
if (cmd == "ld64" || cmd == "ld64.mold")
return mold::macho::main(argc, argv);
#endif
return mold::elf::main(argc, argv);
}
-887
View File
@@ -1,887 +0,0 @@
#pragma once
#include "integers.h"
#include <array>
#include <atomic>
#include <bit>
#include <bitset>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <optional>
#include <span>
#include <sstream>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/concurrent_vector.h>
#include <tbb/enumerable_thread_specific.h>
#include <vector>
#ifdef _WIN32
# include <io.h>
#else
# include <sys/mman.h>
# include <unistd.h>
#endif
#define XXH_INLINE_ALL 1
#include "third-party/xxhash/xxhash.h"
#ifdef NDEBUG
# define unreachable() __builtin_unreachable()
#else
# define unreachable() assert(0 && "unreachable")
#endif
inline uint64_t hash_string(std::string_view str) {
return XXH3_64bits(str.data(), str.size());
}
class HashCmp {
public:
static size_t hash(const std::string_view &k) {
return hash_string(k);
}
static bool equal(const std::string_view &k1, const std::string_view &k2) {
return k1 == k2;
}
};
namespace mold {
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
template <typename C> class OutputFile;
inline char *output_tmpfile;
inline thread_local bool opt_demangle;
inline u8 *output_buffer_start = nullptr;
inline u8 *output_buffer_end = nullptr;
inline std::string mold_version;
extern std::string mold_version_string;
extern std::string mold_git_hash;
std::string errno_string();
std::string get_self_path();
void cleanup();
void install_signal_handler();
i64 get_default_thread_count();
static u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
//
// Error output
//
template <typename C>
class SyncOut {
public:
SyncOut(C &ctx, std::ostream &out = std::cout) : out(out) {
opt_demangle = ctx.arg.demangle;
}
~SyncOut() {
std::scoped_lock lock(mu);
out << ss.str() << "\n";
}
template <class T> SyncOut &operator<<(T &&val) {
ss << std::forward<T>(val);
return *this;
}
static inline std::mutex mu;
private:
std::ostream &out;
std::stringstream ss;
};
template <typename C>
static std::string add_color(C &ctx, std::string msg) {
if (ctx.arg.color_diagnostics)
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
return "mold: " + msg + ": ";
}
template <typename C>
class Fatal {
public:
Fatal(C &ctx) : out(ctx, std::cerr) {
out << add_color(ctx, "fatal");
}
[[noreturn]] ~Fatal() {
out.~SyncOut();
cleanup();
_exit(1);
}
template <class T> Fatal &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncOut<C> out;
};
template <typename C>
class Error {
public:
Error(C &ctx) : out(ctx, std::cerr) {
if (ctx.arg.noinhibit_exec) {
out << add_color(ctx, "warning");
} else {
out << add_color(ctx, "error");
ctx.has_error = true;
}
}
template <class T> Error &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncOut<C> out;
};
template <typename C>
class Warn {
public:
Warn(C &ctx) : out(ctx, std::cerr) {
if (ctx.arg.fatal_warnings) {
out << add_color(ctx, "error");
ctx.has_error = true;
} else {
out << add_color(ctx, "warning");
}
}
template <class T> Warn &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncOut<C> out;
};
//
// Utility functions
//
// Some C++ libraries haven't implemented std::has_single_bit yet.
inline bool has_single_bit(u64 val) {
return std::popcount(val) == 1;
}
// Some C++ libraries haven't implemented std::bit_ceil yet.
inline u64 bit_ceil(u64 val) {
if (has_single_bit(val))
return val;
return 1LL << (64 - std::countl_zero(val));
}
inline u64 align_to(u64 val, u64 align) {
if (align == 0)
return val;
assert(has_single_bit(align));
return (val + align - 1) & ~(align - 1);
}
inline u64 align_down(u64 val, u64 align) {
assert(has_single_bit(align));
return val & ~(align - 1);
}
inline u64 bit(u64 val, i64 pos) {
return (val >> pos) & 1;
};
// Returns [hi:lo] bits of val.
inline u64 bits(u64 val, u64 hi, u64 lo) {
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
}
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 = {}) {
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(new_val, old_val) &&
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
template <typename T, typename Compare = std::less<T>>
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(old_val, new_val) &&
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
// An optimized "mark" operation for parallel mark-and-sweep algorithms.
// Returns true if `visited` was false and updated to true.
inline bool fast_mark(std::atomic<bool> &visited) {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return !visited.load(std::memory_order_relaxed) &&
!visited.exchange(true, std::memory_order_relaxed);
}
template <typename T, typename U>
inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
}
template <typename T>
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
std::vector<T> ret;
for (std::vector<T> &v : vec)
append(ret, v);
return ret;
}
template <typename T>
inline void sort(T &vec) {
std::stable_sort(vec.begin(), vec.end());
}
template <typename T, typename U>
inline void sort(T &vec, U less) {
std::stable_sort(vec.begin(), vec.end(), less);
}
template <typename T>
inline void remove_duplicates(std::vector<T> &vec) {
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
}
inline i64 write_string(void *buf, std::string_view str) {
memcpy(buf, str.data(), str.size());
*((u8 *)buf + str.size()) = '\0';
return str.size() + 1;
}
template <typename T>
inline i64 write_vector(void *buf, const std::vector<T> &vec) {
i64 sz = vec.size() * sizeof(T);
memcpy(buf, vec.data(), sz);
return sz;
}
inline void encode_uleb(std::vector<u8> &vec, u64 val) {
do {
u8 byte = val & 0x7f;
val >>= 7;
vec.push_back(val ? (byte | 0x80) : byte);
} while (val);
}
inline void encode_sleb(std::vector<u8> &vec, i64 val) {
for (;;) {
u8 byte = val & 0x7f;
val >>= 7;
bool neg = (byte & 0x40);
if ((val == 0 && !neg) || (val == -1 && neg)) {
vec.push_back(byte);
break;
}
vec.push_back(byte | 0x80);
}
}
inline i64 write_uleb(u8 *buf, u64 val) {
i64 i = 0;
do {
u8 byte = val & 0x7f;
val >>= 7;
buf[i++] = val ? (byte | 0x80) : byte;
} while (val);
return i;
}
inline u64 read_uleb(u8 *&buf) {
u64 val = 0;
u8 shift = 0;
u8 byte;
do {
byte = *buf++;
val |= (byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
return val;
}
inline u64 read_uleb(u8 const*&buf) {
return read_uleb(const_cast<u8 *&>(buf));
}
inline u64 read_uleb(std::string_view &str) {
u8 *start = (u8 *)&str[0];
u8 *ptr = start;
u64 val = read_uleb(ptr);
str = str.substr(ptr - start);
return val;
}
inline i64 uleb_size(u64 val) {
#if __GNUC__
#pragma GCC unroll 8
#endif
for (int i = 1; i < 9; i++)
if (val < (1LL << (7 * i)))
return i;
return 9;
}
template <typename C>
std::string_view save_string(C &ctx, const std::string &str) {
u8 *buf = new u8[str.size() + 1];
memcpy(buf, str.data(), str.size());
buf[str.size()] = '\0';
ctx.string_pool.push_back(std::unique_ptr<u8[]>(buf));
return {(char *)buf, str.size()};
}
inline bool remove_prefix(std::string_view &s, std::string_view prefix) {
if (s.starts_with(prefix)) {
s = s.substr(prefix.size());
return true;
}
return false;
}
//
// Concurrent Map
//
// This is an implementation of a fast concurrent hash map. Unlike
// ordinary hash tables, this impl just aborts if it becomes full.
// So you need to give a correct estimation of the final size before
// using it. We use this hash map to uniquify pieces of data in
// mergeable sections.
template <typename T>
class ConcurrentMap {
public:
ConcurrentMap() {}
ConcurrentMap(i64 nbuckets) {
resize(nbuckets);
}
~ConcurrentMap() {
if (keys) {
free((void *)keys);
free((void *)key_sizes);
free((void *)values);
}
}
void resize(i64 nbuckets) {
this->~ConcurrentMap();
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
this->nbuckets = nbuckets;
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
key_sizes = (u32 *)calloc(nbuckets, sizeof(key_sizes[0]));
values = (T *)calloc(nbuckets, sizeof(values[0]));
}
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();
continue;
}
if (ptr == nullptr) {
if (!keys[idx].compare_exchange_weak(ptr, marker,
std::memory_order_acq_rel))
continue;
new (values + idx) T(val);
key_sizes[idx] = key.size();
keys[idx].store(key.data(), std::memory_order_release);
return {values + idx, true};
}
if (key.size() == key_sizes[idx] &&
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
return {values + idx, false};
u64 mask = nbuckets / NUM_SHARDS - 1;
idx = (idx & ~mask) | ((idx + 1) & mask);
retry++;
}
assert(false && "ConcurrentMap is full");
return {nullptr, false};
}
bool has_key(i64 idx) {
return keys[idx].load(std::memory_order_relaxed);
}
static constexpr i64 MIN_NBUCKETS = 2048;
static constexpr i64 NUM_SHARDS = 16;
static constexpr i64 MAX_RETRY = 128;
i64 nbuckets = 0;
std::atomic<const char *> *keys = nullptr;
u32 *key_sizes = nullptr;
T *values = nullptr;
private:
static void pause() {
#if defined(__x86_64__)
asm volatile("pause");
#elif defined(__aarch64__)
asm volatile("yield");
#endif
}
static constexpr const char *marker = "marker";
};
//
// output-file.h
//
template <typename C>
class OutputFile {
public:
static std::unique_ptr<OutputFile<C>>
open(C &ctx, std::string path, i64 filesize, i64 perm);
virtual void close(C &ctx) = 0;
virtual ~OutputFile() = default;
u8 *buf = nullptr;
std::string path;
i64 filesize;
bool is_mmapped;
bool is_unmapped = false;
protected:
OutputFile(std::string path, i64 filesize, bool is_mmapped)
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
};
//
// hyperloglog.cc
//
class HyperLogLog {
public:
HyperLogLog() : buckets(NBUCKETS) {}
void insert(u32 hash) {
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
}
i64 get_cardinality() const;
void merge(const HyperLogLog &other) {
for (i64 i = 0; i < NBUCKETS; i++)
update_maximum(buckets[i], other.buckets[i]);
}
private:
static constexpr i64 NBUCKETS = 2048;
static constexpr double ALPHA = 0.79402;
std::vector<std::atomic_uint8_t> buckets;
};
//
// glob.cc
//
class Glob {
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
struct Element {
Element(Kind k) : kind(k) {}
Kind kind;
std::string str;
std::bitset<256> bitset;
};
public:
static std::optional<Glob> compile(std::string_view pat);
bool match(std::string_view str);
private:
Glob(std::vector<Element> &&vec) : elements(vec) {}
static bool do_match(std::string_view str, std::span<Element> elements);
std::vector<Element> elements;
};
//
// multi-glob.cc
//
class MultiGlob {
public:
bool add(std::string_view pat, u32 val);
bool empty() const { return strings.empty(); }
std::optional<u32> find(std::string_view str);
private:
struct TrieNode {
u32 value = -1;
TrieNode *suffix_link = nullptr;
std::unique_ptr<TrieNode> children[256];
};
void compile();
void fix_suffix_links(TrieNode &node);
void fix_values();
std::vector<std::string> strings;
std::unique_ptr<TrieNode> root;
std::vector<std::pair<Glob, u32>> globs;
std::once_flag once;
bool is_compiled = false;
};
//
// uuid.cc
//
std::array<u8, 16> get_uuid_v4();
//
// filepath.cc
//
template <typename T>
std::filesystem::path filepath(const T &path) {
return {path, std::filesystem::path::format::generic_format};
}
std::string get_realpath(std::string_view path);
std::string path_clean(std::string_view path);
std::filesystem::path to_abs_path(std::filesystem::path path);
//
// demangle.cc
//
std::string_view demangle(std::string_view name);
std::optional<std::string_view> cpp_demangle(std::string_view name);
//
// compress.cc
//
class Compressor {
public:
virtual void write_to(u8 *buf) = 0;
virtual ~Compressor() {}
i64 compressed_size = 0;
};
class ZlibCompressor : public Compressor {
public:
ZlibCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
u64 checksum = 0;
};
class ZstdCompressor : public Compressor {
public:
ZstdCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
};
//
// 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)
values.local()++;
return *this;
}
Counter &operator+=(int delta) {
if (enabled)
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 C>
class Timer {
public:
Timer(C &ctx, std::string name, Timer *parent = nullptr) {
record = new TimerRecord(name, parent ? parent->record : nullptr);
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
}
Timer(const Timer &) = delete;
~Timer() {
record->stop();
}
void stop() {
record->stop();
}
private:
TimerRecord *record;
};
//
// tar.cc
//
// TarFile is a class to create a tar file.
//
// If you pass `--repro` to mold, mold collects all input files and
// put them into `<output-file-path>.repro.tar`, so that it is easy to
// run the same command with the same command line arguments.
class TarWriter {
public:
static std::unique_ptr<TarWriter>
open(std::string output_path, std::string basedir);
~TarWriter();
void append(std::string path, std::string_view data);
private:
static constexpr i64 BLOCK_SIZE = 512;
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
FILE *out = nullptr;
std::string basedir;
};
//
// Memory-mapped file
//
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
template <typename C>
class MappedFile {
public:
static MappedFile *open(C &ctx, std::string path);
static MappedFile *must_open(C &ctx, std::string path);
~MappedFile() { unmap(); }
void unmap();
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
std::string_view get_contents() {
return std::string_view((char *)data, size);
}
i64 get_offset() const {
return parent ? (data - parent->data + parent->get_offset()) : 0;
}
// Returns a string that uniquely identify a file that is possibly
// in an archive.
std::string get_identifier() const {
if (parent) {
// We use the file offset within an archive as an identifier
// because archive members may have the same name.
return parent->name + ":" + std::to_string(get_offset());
}
if (thin_parent) {
// If this is a thin archive member, the filename part is
// guaranteed to be unique.
return thin_parent->name + ":" + name;
}
return name;
}
std::string name;
u8 *data = nullptr;
i64 size = 0;
i64 mtime = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
int fd = -1;
#ifdef _WIN32
HANDLE file_handle = INVALID_HANDLE_VALUE;
#endif
};
template <typename C>
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
i64 fd;
#ifdef _WIN32
fd = ::_open(path.c_str(), O_RDONLY);
#else
fd = ::open(path.c_str(), O_RDONLY);
#endif
if (fd == -1)
return nullptr;
struct stat st;
if (fstat(fd, &st) == -1)
Fatal(ctx) << path << ": fstat failed: " << errno_string();
MappedFile *mf = new MappedFile;
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
mf->name = path;
mf->size = st.st_size;
#ifdef _WIN32
mf->mtime = st.st_mtime;
#elif defined(__APPLE__)
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
#else
mf->mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
#endif
if (st.st_size > 0) {
#ifdef _WIN32
HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
nullptr, PAGE_READWRITE, 0,
st.st_size, nullptr);
if (!handle)
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
mf->file_handle = handle;
mf->data = (u8 *)MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, st.st_size);
if (!mf->data)
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
#else
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
if (mf->data == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
#endif
}
close(fd);
return mf;
}
template <typename C>
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
if (MappedFile *mf = MappedFile::open(ctx, path))
return mf;
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
}
template <typename C>
MappedFile<C> *
MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile<C>;
mf->name = name;
mf->data = data + start;
mf->size = size;
mf->parent = this;
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
return mf;
}
template <typename C>
void MappedFile<C>::unmap() {
if (size == 0 || parent || !data)
return;
#ifdef _WIN32
UnmapViewOfFile(data);
if (file_handle != INVALID_HANDLE_VALUE)
CloseHandle(file_handle);
#else
munmap(data, size);
#endif
data = nullptr;
}
} // namespace mold
-166
View File
@@ -1,166 +0,0 @@
// This file implements the Aho-Corasick algorithm to match multiple
// glob patterns to symbol strings as quickly as possible.
//
// Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// `*` is a wildcard that matches any substring. We sometimes have
// hundreds of glob patterns and have to match them against millions
// of symbol strings.
//
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
// We handle such patterns with the Glob class. Glob is relatively
// slow, but complex patterns are rare in practice, so it should be
// OK.
#include "mold.h"
#include <queue>
#include <regex>
namespace mold {
std::optional<u32> MultiGlob::find(std::string_view str) {
std::call_once(once, [&] { compile(); });
u32 val = UINT32_MAX;
if (root) {
// Match against simple glob patterns
TrieNode *node = root.get();
auto walk = [&](u8 c) {
for (;;) {
if (node->children[c]) {
node = node->children[c].get();
val = std::min(val, node->value);
return;
}
if (!node->suffix_link)
return;
node = node->suffix_link;
}
};
walk('\0');
for (u8 c : str)
walk(c);
walk('\0');
}
// Match against complex glob patterns
for (std::pair<Glob, u32> &glob : globs)
if (glob.first.match(str))
val = std::min(val, glob.second);
if (val == UINT32_MAX)
return {};
return val;
}
static bool is_simple_pattern(std::string_view pat) {
static std::regex re(R"(\*?[^*[?]+\*?)", std::regex_constants::optimize);
return std::regex_match(pat.begin(), pat.end(), re);
}
static std::string handle_stars(std::string_view pat) {
std::string str(pat);
// Convert "foo" -> "\0foo\0", "*foo" -> "foo\0", "foo*" -> "\0foo"
// and "*foo*" -> "foo". Aho-Corasick can do only substring matching,
// so we use \0 as beginning/end-of-string markers.
if (str.starts_with('*') && str.ends_with('*'))
return str.substr(1, str.size() - 2);
if (str.starts_with('*'))
return str.substr(1) + "\0"s;
if (str.ends_with('*'))
return "\0"s + str.substr(0, str.size() - 1);
return "\0"s + str + "\0"s;
}
bool MultiGlob::add(std::string_view pat, u32 val) {
assert(!is_compiled);
assert(!pat.empty());
strings.push_back(std::string(pat));
// Complex glob pattern
if (!is_simple_pattern(pat)) {
if (std::optional<Glob> glob = Glob::compile(pat)) {
globs.push_back({std::move(*glob), val});
return true;
}
return false;
}
// Simple glob pattern
if (!root)
root.reset(new TrieNode);
TrieNode *node = root.get();
for (u8 c : handle_stars(pat)) {
if (!node->children[c])
node->children[c].reset(new TrieNode);
node = node->children[c].get();
}
node->value = std::min(node->value, val);
return true;
}
void MultiGlob::compile() {
is_compiled = true;
if (root) {
fix_suffix_links(*root);
fix_values();
}
}
void MultiGlob::fix_suffix_links(TrieNode &node) {
for (i64 i = 0; i < 256; i++) {
if (!node.children[i])
continue;
TrieNode &child = *node.children[i];
TrieNode *cur = node.suffix_link;
for (;;) {
if (!cur) {
child.suffix_link = root.get();
break;
}
if (cur->children[i]) {
child.suffix_link = cur->children[i].get();
break;
}
cur = cur->suffix_link;
}
fix_suffix_links(child);
}
}
void MultiGlob::fix_values() {
std::queue<TrieNode *> queue;
queue.push(root.get());
do {
TrieNode *node = queue.front();
queue.pop();
for (std::unique_ptr<TrieNode> &child : node->children) {
if (!child)
continue;
child->value = std::min(child->value, child->suffix_link->value);
queue.push(child.get());
}
} while (!queue.empty());
}
} // namespace mold
-169
View File
@@ -1,169 +0,0 @@
#include "mold.h"
#include <fcntl.h>
#include <filesystem>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
namespace mold {
inline u32 get_umask() {
u32 orig_umask = umask(0);
umask(orig_umask);
return orig_umask;
}
template <typename C>
static std::pair<i64, char *>
open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
char *path2 = (char *)save_string(ctx, tmpl).data();
i64 fd = mkstemp(path2);
if (fd == -1)
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
// Reuse an existing file if exists and writable because on Linux,
// writing to an existing file is much faster than creating a fresh
// file and writing to it.
if (ctx.overwrite_output_file && rename(path.c_str(), path2) == 0) {
::close(fd);
fd = ::open(path2, O_RDWR | O_CREAT, perm);
if (fd != -1 && !ftruncate(fd, filesize) && !fchmod(fd, perm & ~get_umask()))
return {fd, path2};
unlink(path2);
fd = ::open(path2, O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
}
if (ftruncate(fd, filesize))
Fatal(ctx) << "ftruncate failed: " << errno_string();
if (fchmod(fd, (perm & ~get_umask())) == -1)
Fatal(ctx) << "fchmod failed: " << errno_string();
return {fd, path2};
}
template <typename C>
class MemoryMappedOutputFile : public OutputFile<C> {
public:
MemoryMappedOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, true) {
i64 fd;
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, 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;
}
~MemoryMappedOutputFile() {
if (fd2 != -1)
::close(fd2);
}
void close(C &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
// 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.
fd2 = ::open(this->path.c_str(), O_RDONLY);
if (fd2 != -1)
unlink(this->path.c_str());
if (rename(output_tmpfile, this->path.c_str()) == -1)
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
output_tmpfile = nullptr;
}
private:
int fd2 = -1;
};
template <typename C>
class MallocOutputFile : public OutputFile<C> {
public:
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, false), perm(perm) {
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << "mmap failed: " << errno_string();
}
void close(C &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 C>
std::unique_ptr<OutputFile<C>>
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
bool is_special = false;
if (path == "-") {
is_special = true;
} else {
struct stat st;
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
is_special = true;
}
OutputFile<C> *file;
if (is_special)
file = new MallocOutputFile<C>(ctx, path, filesize, perm);
else
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
#ifdef MADV_HUGEPAGE
// Enable transparent huge page for an output memory-mapped file.
// On Linux, it has an effect only on tmpfs mounted with `huge=advise`,
// but it can make the linker ~10% faster. You can try it by creating
// a tmpfs with the following commands
//
// $ mkdir tmp
// $ sudo mount -t tmpfs -o size=2G,huge=advise none tmp
//
// and then specifying a path under the directory as an output file.
madvise(file->buf, filesize, MADV_HUGEPAGE);
#endif
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile<C>>(file);
}
} // namespace mold
-57
View File
@@ -1,57 +0,0 @@
#include "mold.h"
#include <fcntl.h>
#include <filesystem>
#include <windows.h>
namespace mold {
template <typename C>
class MallocOutputFile : public OutputFile<C> {
public:
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<C>(path, filesize, false), perm(perm) {
this->buf = (u8 *)malloc(filesize);
if (!this->buf)
Fatal(ctx) << "malloc failed";
}
void close(C &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 C>
std::unique_ptr<OutputFile<C>>
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
OutputFile<C> *file = new MallocOutputFile<C>(ctx, path, filesize, perm);
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile<C>>(file);
}
} // namespace mold
-5
View File
@@ -1,5 +0,0 @@
#if _WIN32
# include "output-file-win32.h"
#else
# include "output-file-unix.h"
#endif
-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
+916
View File
@@ -0,0 +1,916 @@
// ARM32 is a bit special from the linker's viewpoint because ARM
// processors support two different instruction encodings: Thumb and
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
// Thumb is a subset of ARM, so not all ARM instructions are
// representable in Thumb.
//
// ARM processors originally supported only ARM instructions. Thumb
// instructions were later added to increase code density.
//
// ARM processors runs in either ARM mode or Thumb mode. The mode can
// be switched using BX (branch and mode exchange)-family instructions.
// We need to use that instructions to, for example, call a function
// encoded in Thumb from a function encoded in ARM. Sometimes, the
// linker even has to emit interworking thunk code to switch mode.
//
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
// byte boundaries. So the least significant bit of a function address
// is always 0.
//
// To distinguish Thumb functions from ARM fucntions, the LSB of a
// function address is repurposed as a boolean flag. If the LSB is 0,
// the function referred to by the address is encoded in ARM;
// otherwise, Thumb.
//
// For example, if a symbol `foo` is of type STT_FUNC and has value
// 0x2001, `foo` is a function using Thumb instructions whose address
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
// aligned). Likewise, if a function pointer has value 0x2001, it
// refers a Thumb function at 0x2000.
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
#if MOLD_ARM32LE || MOLD_ARM32BE
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold {
using E = MOLD_TARGET;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
U32<E> *arm = (U32<E> *)loc;
U16<E> *thm = (U16<E> *)loc;
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
return (I32<E>)*arm;
case R_ARM_THM_JUMP8:
return sign_extend(thm[0], 8) << 1;
case R_ARM_THM_JUMP11:
return sign_extend(thm[0], 11) << 1;
case R_ARM_THM_JUMP19: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/B--Branch-
u32 S = bit(thm[0], 10);
u32 J2 = bit(thm[1], 11);
u32 J1 = bit(thm[1], 13);
u32 imm6 = bits(thm[0], 5, 0);
u32 imm11 = bits(thm[1], 10, 0);
u32 val = (S << 20) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1);
return sign_extend(val, 21);
}
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/BL--BLX--immediate---Branch-with-Link-and-optional-Exchange--immediate--
u32 S = bit(thm[0], 10);
u32 J1 = bit(thm[1], 13);
u32 J2 = bit(thm[1], 11);
u32 I1 = !(J1 ^ S);
u32 I2 = !(J2 ^ S);
u32 imm10 = bits(thm[0], 9, 0);
u32 imm11 = bits(thm[1], 10, 0);
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
return sign_extend(val, 25);
}
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_TLS_CALL:
return sign_extend(*arm, 24) << 2;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOV--MOVS--immediate---Move--immediate--
u32 imm4 = bits(*arm, 19, 16);
u32 imm12 = bits(*arm, 11, 0);
u32 val = (imm4 << 12) | imm12;
return sign_extend(val, 16);
}
case R_ARM_PREL31:
return sign_extend(*arm, 31);
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOVT--Move-Top-
u32 imm4 = bits(thm[0], 3, 0);
u32 i = bit(thm[0], 10);
u32 imm3 = bits(thm[1], 14, 12);
u32 imm8 = bits(thm[1], 7, 0);
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
return sign_extend(val, 16);
}
default:
return 0;
}
}
static void write_arm_mov(u8 *loc, u32 val) {
u32 imm12 = bits(val, 11, 0);
u32 imm4 = bits(val, 15, 12);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
}
static void write_thm_b21(u8 *loc, u32 val) {
u32 S = bit(val, 20);
u32 J2 = bit(val, 19);
u32 J1 = bit(val, 18);
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
static void write_thm_b25(u8 *loc, u32 val) {
u32 S = bit(val, 24);
u32 I1 = bit(val, 23);
u32 I2 = bit(val, 22);
u32 J1 = !I1 ^ S;
u32 J2 = !I2 ^ S;
u32 imm10 = bits(val, 21, 12);
u32 imm11 = bits(val, 11, 1);
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
static void write_thm_mov(u8 *loc, u32 val) {
u32 imm4 = bits(val, 15, 12);
u32 i = bit(val, 11);
u32 imm3 = bits(val, 10, 8);
u32 imm8 = bits(val, 7, 0);
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
}
template <>
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_ARM_NONE:
break;
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
*(U32<E> *)loc = val;
break;
case R_ARM_THM_JUMP8:
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
break;
case R_ARM_THM_JUMP11:
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
break;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL:
write_thm_b25(loc, val);
break;
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS:
write_arm_mov(loc, val);
break;
case R_ARM_PREL31:
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS:
write_thm_mov(loc, val);
break;
default:
unreachable();
}
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
constexpr ul32 insn[] = {
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe08f'e00e, // 1: add lr, pc, lr
0xe5be'f008, // ldr pc, [lr, #8]!
0x0000'0000, // 2: .word .got.plt - 1b - 8
0x0000'0000, // (padding)
0x0000'0000, // (padding)
0x0000'0000, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
static constexpr ul32 plt_entry[] = {
0xe59f'c004, // 1: ldr ip, 2f
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0x0000'0000, // 2: .word sym@GOT - 1b
};
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_ARM_ABS32:
*(U32<E> *)loc = val;
break;
case R_ARM_REL32:
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = get_addend(*this, rel);
u64 P = get_addr() + rel.r_offset;
u64 T = S & 1;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto get_thumb_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
auto get_tlsdesc_trampoline_addr = [&] {
auto it = ranges::upper_bound(output_section->thunks, P, {},
[](std::unique_ptr<Thunk<E>> &thunk) {
return thunk->get_addr();
});
return (*it)->get_addr();
};
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_TARGET1:
break;
case R_ARM_REL32:
*(U32<E> *)loc = S + A - P;
break;
case R_ARM_THM_CALL: {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
break;
}
// THM_CALL relocation refers to either BL or BLX instruction.
// They are different in only one bit. We need to use BL if
// the jump target is Thumb. Otherwise, use BLX.
i64 val1 = S + A - P;
i64 val2 = align_to(S + A - P, 4);
if (T && is_int(val1, 25)) {
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, val1);
} else if (!T && is_int(val2, 25)) {
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
write_thm_b25(loc, val2);
} else {
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
}
break;
}
case R_ARM_BASE_PREL:
*(U32<E> *)loc = GOT + A - P;
break;
case R_ARM_GOTOFF32:
*(U32<E> *)loc = ((S + A) | T) - GOT;
break;
case R_ARM_GOT_PREL:
case R_ARM_TARGET2:
*(U32<E> *)loc = GOT + G + A - P;
break;
case R_ARM_GOT_BREL:
*(U32<E> *)loc = G + A;
break;
case R_ARM_CALL: {
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
break;
}
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
i64 val = S + A - P;
if (is_int(val, 26)) {
if (T) {
*(U32<E> *)loc = 0xfa00'0000; // BLX
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
} else {
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(val, 25, 2);
}
} else {
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
}
break;
}
case R_ARM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
break;
}
// These relocs refers to a B (unconditional branch) instruction.
// Unlike BL or BLX, we can't rewrite B to BX in place when the
// processor mode switch is required because BX doesn't takes an
// immediate; it takes only a register. So if mode switch is
// required, we jump to a linker-synthesized thunk which does the
// job with a longer code sequence.
i64 val = S + A - P;
if (T || !is_int(val, 26))
val = get_arm_thunk_addr() + A - P;
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
}
case R_ARM_PLT32:
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
} else {
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP8:
check(S + A - P, -(1 << 8), 1 << 8);
*(U16<E> *)loc &= 0xff00;
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
break;
case R_ARM_THM_JUMP11:
check(S + A - P, -(1 << 11), 1 << 11);
*(U16<E> *)loc &= 0xf800;
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
break;
case R_ARM_THM_JUMP19:
check(S + A - P, -(1 << 20), 1 << 20);
write_thm_b21(loc, S + A - P);
break;
case R_ARM_THM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0x8000'f3af; // NOP
break;
}
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
// switch processor mode.
i64 val = S + A - P;
if (!T || !is_int(val, 25))
val = get_thumb_thunk_addr() + A - P;
write_thm_b25(loc, val);
break;
}
case R_ARM_MOVW_PREL_NC:
write_arm_mov(loc, ((S + A) | T) - P);
break;
case R_ARM_MOVW_ABS_NC:
write_arm_mov(loc, (S + A) | T);
break;
case R_ARM_THM_MOVW_PREL_NC:
write_thm_mov(loc, ((S + A) | T) - P);
break;
case R_ARM_PREL31:
check(S + A - P, -(1LL << 30), 1LL << 30);
*(U32<E> *)loc &= 0x8000'0000;
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
break;
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov(loc, (S + A) | T);
break;
case R_ARM_MOVT_PREL:
write_arm_mov(loc, (S + A - P) >> 16);
break;
case R_ARM_THM_MOVT_PREL:
write_thm_mov(loc, (S + A - P) >> 16);
break;
case R_ARM_MOVT_ABS:
write_arm_mov(loc, (S + A) >> 16);
break;
case R_ARM_THM_MOVT_ABS:
write_thm_mov(loc, (S + A) >> 16);
break;
case R_ARM_TLS_GD32:
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDM32:
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDO32:
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_ARM_TLS_IE32:
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_ARM_TLS_LE32:
*(U32<E> *)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 if its TP-relative
// address is known at link-time
//
// ldr r0, .L2
// .L1: nop
// ...
// .L2: .word foo(tpoff)
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// ldr r0, .L2
// .L1: ldr r0, [pc, r0]
// ...
// .L2: .word foo(gottpoff) + . - .L1
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
} else if (sym.has_gottp(ctx)) {
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
} else {
*(U32<E> *)loc = S - ctx.tp_addr;
}
break;
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
*(U32<E> *)loc = 0xeb00'0000; // bl 0
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
} else if (sym.has_gottp(ctx)) {
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
*(U32<E> *)loc = 0xe320'f000; // nop
}
break;
case R_ARM_THM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
write_thm_b25(loc, val);
*(U16<E> *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else if (sym.has_gottp(ctx)) {
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
// we use two instructions instead.
*(U16<E> *)loc = 0x4478; // add r0, pc
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
} else {
*(U32<E> *)loc = 0x8000'f3af; // nop.w
}
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || 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(*this, rel);
switch (rel.r_type) {
case R_ARM_ABS32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U32<E> *)loc = *val;
else
*(U32<E> *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U32<E> *)loc = *val;
else
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_ARM_MOVW_ABS_NC:
case R_ARM_THM_MOVW_ABS_NC:
scan_absrel(ctx, sym, rel);
break;
case R_ARM_THM_CALL:
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_THM_JUMP24:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TARGET2:
sym.flags |= NEEDS_GOT;
break;
case R_ARM_MOVT_PREL:
case R_ARM_THM_MOVT_PREL:
case R_ARM_PREL31:
scan_pcrel(ctx, sym, rel);
break;
case R_ARM_TLS_GD32:
sym.flags |= NEEDS_TLSGD;
break;
case R_ARM_TLS_LDM32:
ctx.needs_tlsld = true;
break;
case R_ARM_TLS_IE32:
sym.flags |= NEEDS_GOTTP;
break;
case R_ARM_TLS_CALL:
case R_ARM_THM_TLS_CALL:
scan_tlsdesc(ctx, sym);
break;
case R_ARM_TLS_LE32:
check_tlsle(ctx, sym, rel);
break;
case R_ARM_ABS32:
case R_ARM_TARGET1:
case R_ARM_MOVT_ABS:
case R_ARM_THM_MOVT_ABS:
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_THM_JUMP8:
case R_ARM_THM_JUMP11:
case R_ARM_THM_JUMP19:
case R_ARM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_TLS_LDO32:
case R_ARM_V4BX:
case R_ARM_TLS_GOTDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
// 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.
constexpr ul32 hdr[] = {
0xe08e'0000, // add r0, lr, r0
0xe590'1004, // ldr r1, [r0, #4]
0xe12f'ff11, // bx r1
0xe320'f000, // nop
};
// This is a range extension and mode switch thunk.
// It has two entry points: +0 for Thumb and +4 for ARM.
constexpr u8 entry[] = {
// .thumb
0x78, 0x47, // bx pc # jumps to 1f
0xc0, 0x46, // nop
// .arm
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 *base = ctx.buf + output_section.shdr.sh_offset + offset;
memcpy(base, hdr, sizeof(hdr));
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
memcpy(buf, entry, sizeof(entry));
*(U32<E> *)(buf + 12) = S - P - 16;
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
if constexpr (E::is_le)
return EF_ARM_EABI_VER5;
else
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
}
template <>
void create_arm_exidx_section<E>(Context<E> &ctx) {
for (i64 i = 0; i < ctx.chunks.size(); i++) {
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
if (osec && osec->shdr.sh_type == SHT_ARM_EXIDX) {
auto *sec = new Arm32ExidxSection(*osec);
ctx.extra.exidx = sec;
ctx.chunks[i] = sec;
ctx.chunk_pool.emplace_back(sec);
for (InputSection<E> *isec : osec->members)
isec->is_alive = false;
break;
}
}
}
template <>
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
output_section.compute_section_size(ctx);
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
}
template <>
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
// .ARM.exidx's sh_link should be set to the .text section index.
// Runtime doesn't care about it, but the binutils's strip command does.
if (Chunk<E> *chunk = find_chunk(ctx, ".text"))
this->shdr.sh_link = chunk->shndx;
}
// Returns the end of the text segment
static u64 get_text_end(Context<E> &ctx) {
u64 ret = 0;
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->shdr.sh_flags & SHF_EXECINSTR)
ret = std::max<u64>(ret, chunk->shdr.sh_addr + chunk->shdr.sh_size);
return ret;
}
// 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.
//
// Other target uses .eh_frame_hdr instead for the same purpose.
// I don't know why only ARM uses the different mechanism, but it's
// likely that it's due to some historical reason.
//
// This function returns contents of .ARM.exidx.
template <>
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
// .ARM.exidx records consists of a signed 31-bit relative address
// and a 32-bit value. The relative address indicates the start
// address of a function that the record covers. The value is one of
// the followings:
//
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
// 2. a compact unwinding record encoded into a 32-bit value, or
// 3. a 31-bit relative address which points to a larger record in
// the .ARM.extab section.
//
// CANTUNWIND is value 1. The most significant bit is set in (2) but
// not in (3). So we can distinguished them just by looking at a value.
const u32 CANTUNWIND = 1;
struct Entry {
U32<E> addr;
U32<E> val;
};
// We reserve one extra slot for the sentinel
i64 num_entries = output_section.shdr.sh_size / sizeof(Entry) + 1;
std::vector<u8> buf(num_entries * sizeof(Entry));
Entry *ent = (Entry *)buf.data();
// Write section contents to the buffer
output_section.shdr.sh_addr = this->shdr.sh_addr;
output_section.write_to(ctx, buf.data());
// Fill in sentinel fields
u64 sentinel_addr = this->shdr.sh_addr + sizeof(Entry) * (num_entries - 1);
ent[num_entries - 1].addr = get_text_end(ctx) - sentinel_addr;
ent[num_entries - 1].val = CANTUNWIND;
// Entry's addresses are relative to themselves. In order to sort
// records by address, we first translate them so that the addresses
// are relative to the beginning of the section.
auto is_relative = [](u32 val) {
return val != CANTUNWIND && !(val & 0x8000'0000);
};
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = sign_extend(ent[i].addr, 31) + offset;
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
// Remove duplicate adjacent entries. That is, if two adjacent functions
// have the same compact unwind info or are both CANTUNWIND, we can
// merge them into a single address range.
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
num_entries -= tail.size();
buf.resize(num_entries * sizeof(Entry));
// Make addresses relative to themselves.
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
});
return buf;
}
template <>
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
this->shdr.sh_size = get_contents(ctx).size();
}
template <>
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
std::vector<u8> contents = get_contents(ctx);
assert(this->shdr.sh_size == contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
// Even though using ARM32 in big-endian mode is very rare, the processor
// technically supports both little- and big-endian modes. There are two
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
// data are encoded in big-endian. In BE8, instructions are encoded in
// little-endian, and only data is in big-endian. BE8 is the de facto
// standard for ARMv6 or later. We support only BE8.
//
// A tricky thing is that instructions in an object file are always
// big-endian if the file is compiled for big-endian mode. In other words,
// the compiler always emit code in BE32 if -mbig-endian is specified. It
// is the linker's responsibility to rewrite instructions from big-endian
// to little-endian for an BE8 output. This function does that.
//
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
// Thumb instructions, and data. We need to distinguish them to swap 4
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
// and $d, respectively. We use mapping symbols to determine what to do
// with the text section.
//
// This function is called after we copy the input section contents to the
// output file. We rewrite instructions in the output buffer in place.
#if MOLD_ARM32BE
void arm32be_swap_bytes(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
// Collect mapping symbols
std::vector<Symbol<E> *> syms;
for (Symbol<E> *sym : file->get_local_syms())
if (InputSection<E> *isec = sym->get_input_section())
if (isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
if (std::string_view x = sym->name();
x == "$a" || x.starts_with("$a.") ||
x == "$t" || x.starts_with("$t.") ||
x == "$d" || x.starts_with("$d."))
syms.push_back(sym);
// Group mapping symbols by input section and sort by address
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
});
// Swap bytes
for (i64 i = 0; i < syms.size(); i++) {
Symbol<E> &sym = *syms[i];
if (sym.name().starts_with("$d"))
continue;
InputSection<E> &isec = *sym.get_input_section();
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
u8 *start = base + sym.value;
u8 *end;
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
end = base + syms[i + 1]->value;
else
end = base + isec.sh_size;
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
for (u8 *p = start; p < end; p += sz)
std::reverse(p, p + sz);
}
});
}
#endif
} // namespace mold
#endif
+329 -155
View File
@@ -17,11 +17,13 @@
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
#if MOLD_ARM64LE || MOLD_ARM64BE
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = ARM64;
using E = MOLD_TARGET;
static void write_adrp(u8 *buf, u64 val) {
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
@@ -46,16 +48,16 @@ static u64 page(u64 val) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
0x9000'0010, // adrp x16, .got.plt[2]
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
0x9100'0210, // add x16, x16, .got.plt[2]
0xd61f'0220, // br x17
0xd503'201f, // nop
0xd503'201f, // nop
0xd503'201f, // nop
};
0xd420'7d00, // brk
0xd420'7d00, // brk
0xd420'7d00, // brk
};
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
u64 plt = ctx.plt->shdr.sh_addr;
@@ -68,7 +70,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, .got.plt[n]
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
0x9100'0210, // add x16, x16, .got.plt[n]
@@ -86,14 +88,14 @@ 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) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, GOT[n]
0xf940'0211, // ldr x17, [x16, GOT[n]]
0xd61f'0220, // br x17
0xd503'201f, // nop
0xd420'7d00, // brk
};
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));
@@ -102,36 +104,49 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_AARCH64_ABS64:
*(ul64 *)loc = val;
*(U64<E> *)loc = val;
break;
case R_AARCH64_PREL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
case R_AARCH64_PREL64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static bool is_adrp(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
}
static bool is_ldr(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
}
static bool is_add(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -140,24 +155,21 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_AARCH64_ABS64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_ADD_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
break;
case R_AARCH64_LDST16_ABS_LO12_NC:
@@ -172,57 +184,110 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_AARCH64_LDST128_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
break;
case R_AARCH64_ADD_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
case R_AARCH64_MOVW_UABS_G0:
check(S + A, 0, 1 << 16);
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
break;
case R_AARCH64_MOVW_UABS_G0: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul32 *)loc |= bits(val, 15, 0) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G0_NC:
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
break;
case R_AARCH64_MOVW_UABS_G1: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc |= bits(val, 31, 16) << 5;
case R_AARCH64_MOVW_UABS_G1:
check(S + A, 0, 1LL << 32);
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G1_NC:
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
break;
case R_AARCH64_MOVW_UABS_G2: {
i64 val = S + A;
check(val, 0, 1LL << 48);
*(ul32 *)loc |= bits(val, 47, 32) << 5;
case R_AARCH64_MOVW_UABS_G2:
check(S + A, 0, 1LL << 48);
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G2_NC:
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
break;
case R_AARCH64_MOVW_UABS_G3:
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
break;
case R_AARCH64_ADR_GOT_PAGE: {
i64 val = page(G + GOT + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
case R_AARCH64_MOVW_PREL_G0:
check(S + A - P, -(1 << 15), 1 << 15);
write_movn_movz(loc, S + A - P);
break;
}
case R_AARCH64_ADR_PREL_PG_HI21: {
case R_AARCH64_MOVW_PREL_G0_NC:
*(ul32 *)loc |= bits(S + A - P, 15, 0) << 5;
break;
case R_AARCH64_MOVW_PREL_G1:
check(S + A - P, -(1LL << 31), 1LL << 31);
write_movn_movz(loc, (i64)(S + A - P) >> 16);
break;
case R_AARCH64_MOVW_PREL_G1_NC:
*(ul32 *)loc |= bits(S + A - P, 31, 16) << 5;
break;
case R_AARCH64_MOVW_PREL_G2:
check(S + A - P, -(1LL << 47), 1LL << 47);
write_movn_movz(loc, (i64)(S + A - P) >> 32);
break;
case R_AARCH64_MOVW_PREL_G2_NC:
*(ul32 *)loc |= bits(S + A - P, 47, 32) << 5;
break;
case R_AARCH64_MOVW_PREL_G3:
*(ul32 *)loc |= bits(S + A - P, 63, 48) << 5;
break;
case R_AARCH64_ADR_GOT_PAGE:
if (sym.has_got(ctx)) {
i64 val = page(G + GOT + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
i64 val = page(S + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
u32 reg = bits(*(ul32 *)loc, 4, 0);
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
i++;
}
break;
case R_AARCH64_ADR_PREL_PG_HI21:
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
// :lo12: foo` instruction pair to materialize a PC-relative address
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
// if foo is in PC ± 1 MiB.
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
i + 1 < rels.size()) {
i64 val = S + A - P - 4;
const ElfRel<E> &rel2 = rels[i + 1];
if (is_int(val, 21) &&
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
rel2.r_sym == rel.r_sym &&
rel2.r_offset == rel.r_offset + 4 &&
rel2.r_addend == rel.r_addend &&
is_adrp(loc) &&
is_add(loc + 4)) {
u32 reg1 = bits(*(ul32 *)loc, 4, 0);
u32 reg2 = bits(*(ul32 *)(loc + 4), 4, 0);
if (reg1 == reg2) {
*(ul32 *)loc = 0xd503'201f; // nop
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
write_adr(loc + 4, val);
i++;
break;
}
}
}
i64 val = page(S + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
break;
}
case R_AARCH64_ADR_PREL_LO21: {
i64 val = S + A - P;
check(val, -(1LL << 20), 1LL << 20);
write_adr(loc, val);
case R_AARCH64_ADR_PREL_LO21:
check(S + A - P, -(1LL << 20), 1LL << 20);
write_adr(loc, S + A - P);
break;
}
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26: {
if (sym.is_remaining_undef_weak()) {
@@ -233,32 +298,34 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
i64 val = S + A - P;
if (val < -(1 << 27) || (1 << 27) <= val)
val = get_thunk_addr(i) + A - P;
if (!is_int(val, 28))
val = sym.get_thunk_addr(ctx, P) + A - P;
*(ul32 *)loc |= bits(val, 27, 2);
break;
}
case R_AARCH64_PLT32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc = S + A - P;
break;
case R_AARCH64_GOTPCREL32:
check(G + GOT + A - P, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc = G + GOT + A - P;
break;
case R_AARCH64_CONDBR19:
case R_AARCH64_LD_PREL_LO19: {
i64 val = S + A - P;
check(val, -(1LL << 20), 1LL << 20);
*(ul32 *)loc |= bits(val, 20, 2) << 5;
case R_AARCH64_LD_PREL_LO19:
check(S + A - P, -(1LL << 20), 1LL << 20);
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
break;
}
case R_AARCH64_PREL16: {
i64 val = S + A - P;
check(val, -(1LL << 15), 1LL << 15);
*(ul16 *)loc = val;
case R_AARCH64_PREL16:
check(S + A - P, -(1LL << 15), 1LL << 16);
*(U16<E> *)loc = S + A - P;
break;
}
case R_AARCH64_PREL32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 32);
*(ul32 *)loc = val;
case R_AARCH64_PREL32:
check(S + A - P, -(1LL << 31), 1LL << 32);
*(U32<E> *)loc = S + A - P;
break;
}
case R_AARCH64_PREL64:
*(ul64 *)loc = S + A - P;
*(U64<E> *)loc = S + A - P;
break;
case R_AARCH64_LD64_GOT_LO12_NC:
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
@@ -309,6 +376,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
check(S + A - ctx.tp_addr, 0, 1 << 12);
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
break;
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
break;
@@ -322,38 +392,67 @@ 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 if its TP-relative
// address is known at link-time
//
// nop
// nop
// movz x0, :tls_offset_hi:foo, lsl #16
// movk x0, :tls_offset_lo:foo
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// nop
// nop
// adrp x0, :gottprel:foo
// ldr x0, [x0, :gottprel_lo12:foo]
if (sym.has_tlsdesc(ctx)) {
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
i64 val = (S + A - ctx.tp_addr);
check(val, -(1LL << 32), 1LL << 32);
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
*(ul32 *)loc = 0xd503'201f; // nop
}
break;
case R_AARCH64_TLSDESC_LD64_LO12:
if (sym.has_tlsdesc(ctx)) {
if (sym.has_tlsdesc(ctx))
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
} else {
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
}
else
*(ul32 *)loc = 0xd503'201f; // nop
break;
case R_AARCH64_TLSDESC_ADD_LO12:
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
} else {
// add x0, x0, #0 -> nop
*(ul32 *)loc = 0xd503'201f;
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
}
break;
case R_AARCH64_TLSDESC_CALL:
if (!sym.has_tlsdesc(ctx)) {
// blr x2 -> nop
*(ul32 *)loc = 0xd503'201f;
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
} else {
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
}
break;
default:
@@ -368,24 +467,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -393,19 +480,21 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_AARCH64_ABS64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
*(U64<E> *)loc = *val;
else
*(ul64 *)loc = S + A;
*(U64<E> *)loc = S + A;
break;
case R_AARCH64_ABS32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
case R_AARCH64_ABS32:
check(S + A, 0, 1LL << 32);
*(U32<E> *)loc = S + A;
break;
}
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
@@ -417,56 +506,82 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_AARCH64_ABS64:
scan_dyn_absrel(ctx, sym, rel);
case R_AARCH64_MOVW_UABS_G3:
scan_absrel(ctx, sym, rel);
break;
case R_AARCH64_ADR_GOT_PAGE:
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
// symbol's address from GOT. If the GOT value is a link-time
// constant, we may be able to rewrite the ADRP+LDR instruction pair
// with an ADRP+ADD, eliminating a GOT memory load.
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
i + 1 < rels.size()) {
// ADRP+LDR must be consecutive and use the same register to relax.
const ElfRel<E> &rel2 = rels[i + 1];
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
rel2.r_offset == rel.r_offset + 4 &&
rel2.r_sym == rel.r_sym &&
rel.r_addend == 0 &&
rel2.r_addend == 0 &&
is_adrp(loc) &&
is_ldr(loc + 4)) {
u32 rd = bits(*(ul32 *)loc, 4, 0);
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
if (rd == rn && rn == rt) {
i++;
break;
}
}
}
sym.flags |= NEEDS_GOT;
break;
case R_AARCH64_LD64_GOT_LO12_NC:
case R_AARCH64_LD64_GOTPAGE_LO15:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
case R_AARCH64_GOTPCREL32:
sym.flags |= NEEDS_GOT;
break;
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
case R_AARCH64_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_AARCH64_ADR_PREL_PG_HI21:
case R_AARCH64_ADR_PREL_PG_HI21_NC:
scan_pcrel(ctx, sym, rel);
break;
case R_AARCH64_TLSGD_ADR_PAGE21:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
case R_AARCH64_TLSDESC_CALL:
scan_tlsdesc(ctx, sym);
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
check_tlsle(ctx, sym, rel);
break;
case R_AARCH64_ABS64:
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_ADR_PREL_LO21:
case R_AARCH64_CONDBR19:
@@ -482,20 +597,25 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_AARCH64_MOVW_UABS_G1_NC:
case R_AARCH64_MOVW_UABS_G2:
case R_AARCH64_MOVW_UABS_G2_NC:
case R_AARCH64_MOVW_UABS_G3:
case R_AARCH64_MOVW_PREL_G0:
case R_AARCH64_MOVW_PREL_G0_NC:
case R_AARCH64_MOVW_PREL_G1:
case R_AARCH64_MOVW_PREL_G1_NC:
case R_AARCH64_MOVW_PREL_G2:
case R_AARCH64_MOVW_PREL_G2_NC:
case R_AARCH64_MOVW_PREL_G3:
case R_AARCH64_PREL16:
case R_AARCH64_PREL32:
case R_AARCH64_PREL64:
case R_AARCH64_TLSGD_ADD_LO12_NC:
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
case R_AARCH64_TLSGD_ADD_LO12_NC:
case R_AARCH64_TLSDESC_CALL:
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -503,27 +623,81 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
// The size of a thunk entry varies on ARM64 depending on the distance to
// the branch target. This function computes the size of each thunk entry.
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
void Thunk<E>::shrink_size(Context<E> &ctx) {
offsets.clear();
offsets.push_back(0);
i64 off = 0;
static const ul32 data[] = {
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
// The distance between S and P is only reduced by shrink_size(), but
// page(S) – page(P) may still increase by one page due to address
// changes, so we add a safety margin.
//
// For example, page(0x1200) – page(0x1000) is 0, whereas
// page(0x1100) – page(0xfff) is 0x1000, even though the latter
// distance is shorter than the former.
auto is_small = [](i64 prel) {
return is_int(prel + 0x1000, 33) && is_int(prel - 0x1000, 33);
};
static_assert(E::thunk_size == sizeof(data));
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
u8 *loc = buf + i * E::thunk_size;
memcpy(loc , 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);
u64 P = get_addr() + off;
i64 prel = page(S) - page(P);
off += is_small(prel) ? 16 : 32;
offsets.push_back(off);
}
}
} // namespace mold::elf
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
// Short thunk with a 33 bit displacement
constexpr ul32 insn1[] = {
0x9000'0010, // adrp x16, 0
0x9100'0210, // add x16, x16
0xd61f'0200, // br x16
0xd420'7d00, // brk
};
// Long thunk with a 64 bit displacement
constexpr ul32 insn2[] = {
0x1000'0010, // adr x16, 0
0xd2a0'0011, // movz x17, 0, lsl #16
0xf2c0'0011, // movk x17, 0, lsl #32
0xf2e0'0011, // movk x17, 0, lsl #48
0x8b11'0210, // add x16, x16, x17
0xd61f'0200, // br x16
0xd420'7d00, // brk
0xd420'7d00, // brk
};
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
if (offsets[i + 1] - offsets[i] == 16) {
i64 prel = page(S) - page(P);
assert(is_int(prel, 33));
memcpy(buf, insn1, sizeof(insn1));
write_adrp(buf, prel);
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
} else {
memcpy(buf, insn2, sizeof(insn2));
write_adr(buf, bits(S - P, 15, 0));
*(ul32 *)(buf + 4) |= bits(S - P, 31, 16) << 5;
*(ul32 *)(buf + 8) |= bits(S - P, 47, 32) << 5;
*(ul32 *)(buf + 12) |= bits(S - P, 63, 48) << 5;
}
}
}
template class Thunk<E>;
} // namespace mold
#endif
+236 -216
View File
@@ -31,25 +31,25 @@
// assume that %ebx is set to .got. For PDE, we need to create position-
// dependent PLT entries which don't use %ebx.
//
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
// https://github.com/rui314/psabi/blob/main/i386.pdf
#if MOLD_I386
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = I386;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_386_NONE:
return 0;
case R_386_8:
case R_386_PC8:
return *loc;
return *(i8 *)loc;
case R_386_16:
case R_386_PC16:
return *(ul16 *)loc;
return *(il16 *)loc;
case R_386_32:
case R_386_PC32:
case R_386_GOT32:
@@ -65,9 +65,10 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_GOTDESC:
return *(ul32 *)loc;
return *(il32 *)loc;
default:
return 0;
}
unreachable();
}
template <>
@@ -109,25 +110,24 @@ template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0x51, // push %ecx
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 7) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
*(ul32 *)(buf + 3) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
} else {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0x51, // push %ecx
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, // (padding)
0x51, // push %ecx
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr + 4;
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
}
}
@@ -135,51 +135,47 @@ template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, // (padding)
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, // (padding)
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx);
}
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx);
}
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -203,15 +199,89 @@ static u32 relax_got32x(u8 *loc) {
return 0;
}
// Relax GD to LE
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
};
switch (rel.r_type) {
case R_386_PLT32:
case R_386_PC32:
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
case R_386_GOT32:
case R_386_GOT32X:
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = val;
break;
default:
unreachable();
}
}
// Relax LD to LE
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
switch (rel.r_type) {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 5) = tls_size;
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = tls_size;
break;
}
default:
unreachable();
}
}
static u32 relax_tlsdesc_to_ie(u8 *loc) {
switch ((loc[0] << 8) | loc[1]) {
case 0x8d83: return 0x8b83; // lea 0(%ebx), %eax -> mov 0(%ebx), %eax
case 0x8d9b: return 0x8b9b; // lea 0(%ebx), %ebx -> mov 0(%ebx), %ebx
case 0x8d8b: return 0x8b8b; // lea 0(%ebx), %ecx -> mov 0(%ebx), %ecx
case 0x8d93: return 0x8b93; // lea 0(%ebx), %edx -> mov 0(%ebx), %edx
case 0x8db3: return 0x8bb3; // lea 0(%ebx), %esi -> mov 0(%ebx), %esi
case 0x8dbb: return 0x8bbb; // lea 0(%ebx), %edi -> mov 0(%ebx), %edi
case 0x8da3: return 0x8ba3; // lea 0(%ebx), %esp -> mov 0(%ebx), %esp
case 0x8dab: return 0x8bab; // lea 0(%ebx), %ebp -> mov 0(%ebx), %ebp
}
return 0;
}
static u32 relax_tlsdesc_to_le(u8 *loc) {
switch ((loc[0] << 8) | loc[1]) {
case 0x8d83: return 0x90b8; // lea 0(%ebx), %eax -> mov $0, %eax
case 0x8d9b: return 0x90bb; // lea 0(%ebx), %ebx -> mov $0, %ebx
case 0x8d8b: return 0x90b9; // lea 0(%ebx), %ecx -> mov $0, %ecx
case 0x8d93: return 0x90ba; // lea 0(%ebx), %edx -> mov $0, %edx
case 0x8db3: return 0x90be; // lea 0(%ebx), %esi -> mov $0, %esi
case 0x8dbb: return 0x90bf; // lea 0(%ebx), %edi -> mov $0, %edi
case 0x8da3: return 0x90bc; // lea 0(%ebx), %esp -> mov $0, %esp
case 0x8dab: return 0x90bd; // lea 0(%ebx), %ebp -> mov $0, %ebp
}
return 0;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -220,47 +290,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = get_addend(*this, rel);
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_386_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_386_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_386_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
}
case R_386_32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_386_PC8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*loc = val;
case R_386_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
break;
}
case R_386_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
case R_386_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A - P;
break;
}
case R_386_PC32:
case R_386_PLT32:
*(ul32 *)loc = S + A - P;
@@ -295,72 +353,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
break;
case R_386_TLS_GD:
if (sym.has_tlsgd(ctx)) {
if (sym.has_tlsgd(ctx))
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
} else {
// Relax GD to LE
switch (rels[i + 1].r_type) {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = ctx.tp_addr - S - A;
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
break;
}
default:
unreachable();
}
i++;
}
else
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
break;
case R_386_TLS_LDM:
if (ctx.got->has_tlsld(ctx)) {
if (ctx.got->has_tlsld(ctx))
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
} else {
// Relax LD to LE
switch (rels[i + 1].r_type) {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
0x90, // nop
};
memcpy(loc - 2, insn, sizeof(insn));
break;
}
default:
unreachable();
}
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
i++;
}
else
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
break;
case R_386_TLS_LDO_32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
@@ -369,13 +371,53 @@ 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 if its TP-relative
// address is known at link-time
//
// mov $foo@TPOFF, %eax
// nop
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// mov foo@GOTTPOFF(%ebx), %eax
// nop
//
// We allow the following alternative code sequence too because
// LLVM emits such code.
//
// lea 0(%ebx), %reg
// R_386_TLS_GOTDESC foo
// mov %reg, %eax
// call *(%eax)
// R_386_TLS_DESC_CALL foo
//
// Note that the compiler always uses the local-exec TLS model
// for -fno-pic, so TLSDESC code is always PIC (i.e. uses %ebx to
// store the address of GOT.)
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
} else if (sym.has_gottp(ctx)) {
u32 insn = relax_tlsdesc_to_ie(loc - 2);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
loc[-2] = insn >> 8;
loc[-1] = insn;
*(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));
u32 insn = relax_tlsdesc_to_le(loc - 2);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = S + A - ctx.tp_addr;
}
break;
@@ -398,24 +440,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -424,37 +454,33 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : get_addend(*this, rel);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_386_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_386_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_386_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
}
case R_386_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
case R_386_PC8: {
i64 val = S + A;
check(val, -(1 << 7), 1 << 7);
*loc = val;
case R_386_PC8:
check(S + A, -(1 << 7), 1 << 7);
*loc = S + A;
break;
}
case R_386_PC16: {
i64 val = S + A;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
case R_386_PC16:
check(S + A, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A;
break;
}
case R_386_PC32:
*(ul32 *)loc = S + A;
break;
@@ -482,35 +508,35 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
}
switch (rel.r_type) {
case R_386_8:
case R_386_16:
scan_absrel(ctx, sym, rel);
break;
case R_386_32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_386_PC8:
case R_386_PC16:
case R_386_PC32:
@@ -518,56 +544,48 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_386_GOT32:
case R_386_GOTPC:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_386_GOT32X: {
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_got32x(loc - 2);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
case R_386_GOT32X:
// We always want to relax GOT32X even if --no-relax is given
// because static PIE doesn't work without it.
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
// Do nothing
} else {
sym.flags |= NEEDS_GOT;
}
break;
}
case R_386_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_386_TLS_GD:
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
if (relax_tlsgd(ctx, sym))
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx)))
i++;
else
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_386_TLS_LDM:
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
if (relax_tlsld(ctx))
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
i++;
else
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_386_TLS_GOTDESC:
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
scan_tlsdesc(ctx, sym);
break;
case R_386_TLS_LE:
check_tlsle(ctx, sym, rel);
break;
case R_386_32:
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
@@ -579,4 +597,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
#endif
File diff suppressed because it is too large. Load diff
+35 -52
View File
@@ -1,10 +1,10 @@
// This file contains code for the Motorola 68000 series microprocessors,
// which is often abbreviated as m68k. Running a Unix-like system on a
// m68k-based machine today is probably a retro-computing hobby activity,
// but the processor was a popular choice to build Unix computers during
// '80s. Early Sun workstations for example used m68k. Macintosh until
// 1994 were based on m68k as well until they switched to PowerPC (and
// then to x86 and to ARM.)
// m68k-based machine today is a retro-computing hobby activity, but the
// processor was a popular choice to build Unix computers during '80s.
// Early Sun workstations for example used m68k. Macintosh until 1994 were
// based on m68k as well until they switched to PowerPC (and then to x86
// and to ARM.)
//
// From the linker's point of view, it is not hard to support m68k. It's
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
@@ -12,11 +12,13 @@
// access instructions and therefore can support position-independent
// code without too much hassle.
//
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
// https://github.com/rui314/psabi/blob/main/m68k.pdf
#if MOLD_M68K
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = M68K;
@@ -52,12 +54,12 @@ 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 <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -78,11 +80,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -91,11 +88,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
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 << ")";
check_range(ctx, i, val, lo, hi);
};
auto write16 = [&](u64 val) {
@@ -118,15 +118,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*loc = val;
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_68K_32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_68K_16:
write16(S + A);
@@ -221,17 +214,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -256,29 +244,19 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
switch (rel.r_type) {
case R_68K_32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_68K_16:
case R_68K_8:
scan_absrel(ctx, sym, rel);
@@ -294,35 +272,38 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_68K_GOTOFF32:
case R_68K_GOTOFF16:
case R_68K_GOTOFF8:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_68K_PLT32:
case R_68K_PLT16:
case R_68K_PLT8:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_68K_TLS_GD32:
case R_68K_TLS_GD16:
case R_68K_TLS_GD8:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_68K_TLS_LDM32:
case R_68K_TLS_LDM16:
case R_68K_TLS_LDM8:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_68K_TLS_IE32:
case R_68K_TLS_IE16:
case R_68K_TLS_IE8:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_68K_TLS_LDO32:
case R_68K_TLS_LDO16:
case R_68K_TLS_LDO8:
case R_68K_TLS_LE32:
case R_68K_TLS_LE16:
case R_68K_TLS_LE8:
check_tlsle(ctx, sym, rel);
break;
case R_68K_32:
case R_68K_TLS_LDO32:
case R_68K_TLS_LDO16:
case R_68K_TLS_LDO8:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -330,4 +311,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
#endif
+64 -78
View File
@@ -30,7 +30,7 @@
// 0x10000 (or 65536) bytes.
//
// Since each object file has its own .got2, %r30 refers to different
// places in a merged .got2 for two functions that are came from different
// places in a merged .got2 for two functions that came from different
// input files. Therefore, %r30 makes sense only within a single function.
//
// Technically, we can reuse a %r30 value in our PLT if we create a PLT
@@ -38,27 +38,25 @@
// doesn't seems to be worth its complexity. Our PLT simply doesn't rely
// on a %r30 value.
//
// https://github.com/rui314/mold/wiki/ppc32-psabi.pdf
// https://github.com/rui314/psabi/blob/main/ppc32.pdf
#if MOLD_PPC32
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = PPC32;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ub32 insn[] = {
constexpr ub32 insn[] = {
// Get the address of this PLT section
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
@@ -92,7 +90,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
loc[5] |= lo(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
}
static const ub32 plt_entry[] = {
static constexpr ub32 plt_entry[] = {
// Get the address of this PLT entry
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
@@ -124,14 +122,14 @@ 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);
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -152,14 +150,7 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
u64 got2 = 0;
if (file.ppc32_got2)
got2 = file.ppc32_got2->output_section->shdr.sh_addr + file.ppc32_got2->offset;
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
@@ -176,10 +167,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_PPC_ADDR32:
case R_PPC_UADDR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_PPC_ADDR14:
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
break;
@@ -201,16 +188,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc |= bits(S + A, 31, 2) << 2;
break;
case R_PPC_PLT16_LO:
*(ub16 *)loc = lo(G + GOT - got2 - A);
*(ub16 *)loc = lo(G + GOT - A - GOT2);
break;
case R_PPC_PLT16_HI:
*(ub16 *)loc = hi(G + GOT - got2 - A);
*(ub16 *)loc = hi(G + GOT - A - GOT2);
break;
case R_PPC_PLT16_HA:
*(ub16 *)loc = ha(G + GOT - got2 - A);
*(ub16 *)loc = ha(G + GOT - A - GOT2);
break;
case R_PPC_PLT32:
*(ub32 *)loc = G + GOT - got2 - A;
*(ub32 *)loc = G + GOT - A - GOT2;
break;
case R_PPC_REL14:
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
@@ -228,15 +215,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC_REL24:
case R_PPC_LOCAL24PC: {
i64 val = S + A - P;
if (sign_extend(val, 25) != val)
val = get_thunk_addr(i) - P;
if (!is_int(val, 26))
val = sym.get_thunk_addr(ctx, P) - P;
*(ub32 *)loc |= bits(val, 25, 2) << 2;
break;
}
case R_PPC_PLTREL24: {
i64 val = S - P;
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
val = get_thunk_addr(i) - P;
if (sym.has_plt(ctx) || !is_int(val, 26))
val = sym.get_thunk_addr(ctx, P) - P;
*(ub32 *)loc |= bits(val, 25, 2) << 2;
break;
}
@@ -281,6 +268,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC_GOT_TPREL16:
*(ub16 *)loc = sym.get_gottp_addr(ctx) - GOT;
break;
case R_PPC_ADDR32:
case R_PPC_UADDR32:
case R_PPC_TLS:
case R_PPC_TLSGD:
case R_PPC_TLSLD:
@@ -299,17 +288,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -324,6 +308,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(ub32 *)loc = S + A;
break;
case R_PPC_DTPREL32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub32 *)loc = *val;
else
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
@@ -334,31 +324,20 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_PPC_ADDR32:
case R_PPC_UADDR32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PPC_ADDR14:
case R_PPC_ADDR16:
case R_PPC_UADDR16:
@@ -385,30 +364,34 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC_PLT16_HI:
case R_PPC_PLT16_HA:
case R_PPC_PLT32:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_PPC_REL24:
case R_PPC_PLTREL24:
case R_PPC_PLTREL32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_PPC_GOT_TLSGD16:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_PPC_GOT_TLSLD16:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_PPC_GOT_TPREL16:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_PPC_TPREL16_LO:
case R_PPC_TPREL16_HI:
case R_PPC_TPREL16_HA:
check_tlsle(ctx, sym, rel);
break;
case R_PPC_ADDR32:
case R_PPC_UADDR32:
case R_PPC_LOCAL24PC:
case R_PPC_TLS:
case R_PPC_TLSGD:
case R_PPC_TLSLD:
case R_PPC_TPREL16_LO:
case R_PPC_TPREL16_HI:
case R_PPC_TPREL16_HA:
case R_PPC_DTPREL16_LO:
case R_PPC_DTPREL16_HI:
case R_PPC_DTPREL16_HA:
@@ -422,10 +405,8 @@ 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[] = {
void Thunk<E>::copy_buf(Context<E> &ctx) {
constexpr ub32 local_thunk[] = {
// Get this thunk's address
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
@@ -443,23 +424,28 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_entry));
static_assert(E::thunk_size == sizeof(local_thunk));
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
for (i64 i = 0; i < symbols.size(); i++) {
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
Symbol<E> &sym = *symbols[i];
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
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);
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);
}
}
}
} // namespace mold::elf
} // namespace mold
#endif
+179 -159
View File
@@ -5,14 +5,15 @@
//
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
// endianness. The most notable difference is, in ELFv1, a function
// pointer doesn't directly refer the entry point of a function but
// instead refers a data structure so-called "function descriptor".
// pointer doesn't directly refer to the entry point of a function but
// instead refers to a data structure so-called "function descriptor".
//
// The function descriptor is essentially a pair of a function entry point
// address and a value that should be set to %r2 before calling that
// function. There is actually a third member, but we don't need to know
// what that is. In total, the function descriptor is 24 bytes long. Here
// is why we need it.
// function. There is also a third member for "the environment pointer for
// languages such as Pascal and PL/1" according to the psABI, but it looks
// like no one acutally uses it. In total, the function descriptor is 24
// bytes long. Here is why we need it.
//
// PPC generally lacks PC-relative data access instructions. Position-
// independent code sets GOT + 0x8000 to %r2 and access global variables
@@ -25,8 +26,8 @@
//
// In this way, you can't call a function just by knowing the function's
// entry point address. You also need to know a proper %r2 value for the
// function. This is why a function pointer refers a tuple of an address
// and a %r2 value.
// function. This is why a function pointer refers to a tuple of an
// address and a %r2 value.
//
// If a function call is made through PLT, PLT takes care of restoring %r2.
// Therefore, the caller has to restore %r2 only for function calls
@@ -39,27 +40,27 @@
// few different addresses for different purposes. It may not only have an
// entry point address but may also have PLT and/or GOT addresses.
// In PPCV1, it may have an OPD address in addition to these. OPD address
// is used for relocations that refers the address of a function as a
// is used for relocations that refers to the address of a function as a
// function pointer.
//
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
#if MOLD_PPC64V1
#include "mold.h"
#include <algorithm>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
namespace mold {
using E = PPC64V1;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
@@ -69,33 +70,59 @@ static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
// resolved addresses.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ub32 insn[] = {
constexpr ub32 insn[] = {
0x7d88'02a6, // mflr r12
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
0xe84b'0024, // ld r2,36(r11)
0x7d88'03a6, // mtlr r12
0x7d62'5a14, // add r11,r2,r11
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
0xe98b'0000, // ld r12,0(r11)
0xe84b'0008, // ld r2,8(r11)
0x7d89'03a6, // mtctr r12
0xe96b'0010, // ld r11,16(r11)
0x4e80'0420, // bctr
// .quad .got.plt - .plt - 8
0x0000'0000,
0x0000'0000,
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4;
*(ub32 *)(buf + 0) = 0x3800'0000 | sym.get_plt_idx(ctx); // li %r0, PLT_INDEX
*(ub32 *)(buf + 4) = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
ub32 *loc = (ub32 *)buf;
i64 idx = sym.get_plt_idx(ctx);
// The PPC64 ELFv1 ABI requires PLT entries to be vary in size depending
// on their indices. Unlike other targets, .got.plt is filled not by us
// but by the loader, so we don't have a control over where the initial
// call to the PLT entry jumps to. So we need to strictly follow the PLT
// section layout as the loader expect it to be.
if (idx < 0x8000) {
constexpr ub32 insn[] = {
0x3800'0000, // li r0, PLT_INDEX
0x4b00'0000, // b plt0
};
memcpy(loc, insn, sizeof(insn));
loc[0] |= idx;
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
} else {
constexpr ub32 insn[] = {
0x3c00'0000, // lis r0, PLT_INDEX@high
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
0x4b00'0000, // b plt0
};
memcpy(loc, insn, sizeof(insn));
loc[0] |= high(idx);
loc[1] |= lo(idx);
loc[2] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 8) & 0x00ff'ffff;
}
}
// .plt.got is not necessary on PPC64 because range extension thunks
@@ -104,8 +131,8 @@ template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -129,11 +156,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -142,13 +164,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
@@ -156,12 +171,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
u64 TOC = ctx.extra.TOC->value;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_PPC64_ADDR64:
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_PPC64_TOC:
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, dynrel);
break;
case R_PPC64_TOC16_HA:
*(ub16 *)loc = ha(S + A - TOC);
@@ -169,19 +184,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_TOC16_LO:
*(ub16 *)loc = lo(S + A - TOC);
break;
case R_PPC64_TOC16_DS: {
i64 val = S + A - TOC;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc |= val & 0xfffc;
case R_PPC64_TOC16_DS:
check(S + A - TOC, -(1 << 15), 1 << 15);
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
break;
}
case R_PPC64_TOC16_LO_DS:
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
break;
case R_PPC64_REL24: {
i64 val = sym.get_addr(ctx, NO_OPD) + A - P;
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
val = get_thunk_addr(i) + A - P;
if (sym.has_plt(ctx) || !is_int(val, 26))
val = sym.get_thunk_addr(ctx, P) + A - P;
check(val, -(1 << 25), 1 << 25);
*(ub32 *)loc |= bits(val, 25, 2) << 2;
@@ -194,6 +207,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
break;
}
case R_PPC64_REL32:
*(ub32 *)loc = S + A - P;
break;
case R_PPC64_REL64:
*(ub64 *)loc = S + A - P;
break;
@@ -203,6 +219,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_REL16_LO:
*(ub16 *)loc = lo(S + A - P);
break;
case R_PPC64_GOT16:
*(ub16 *)loc = G - TOC;
break;
case R_PPC64_PLT16_HA:
*(ub16 *)loc = ha(G + GOT - TOC);
break;
@@ -233,18 +252,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_DTPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_DTPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_DTPREL16_LO_DS:
*(ub16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
break;
case R_PPC64_TPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC64_TPREL16_LO_DS:
*(ub16 *)loc |= (S + A - ctx.tp_addr) & 0xfffc;
break;
case R_PPC64_GOT_TPREL16_LO_DS:
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_ADDR64:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TLS:
@@ -263,24 +289,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -288,6 +302,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (std::optional<u64> val = get_tombstone(sym, frag))
@@ -295,12 +313,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(ub64 *)loc = S + A;
break;
case R_PPC64_ADDR32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_PPC64_ADDR32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_PPC64_DTPREL64:
*(ub64 *)loc = S + A - ctx.dtp_addr;
break;
@@ -314,55 +330,50 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD,
std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
// Any relocation except R_PPC64_REL24 is considered as an
// address-taking relocation.
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
sym.flags.fetch_or(NEEDS_PPC_OPD, std::memory_order_relaxed);
sym.flags |= NEEDS_PPC_OPD;
switch (rel.r_type) {
case R_PPC64_ADDR64:
scan_toc_rel(ctx, sym, rel);
break;
case R_PPC64_TOC:
scan_toc_rel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_PPC64_GOT16:
case R_PPC64_PLT16_HA:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_PPC64_GOT_TLSGD16_HA:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_PPC64_GOT_TLSLD16_HA:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL16_LO_DS:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_ADDR64:
case R_PPC64_TOC:
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO:
@@ -375,8 +386,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_PLT16_LO_DS:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
@@ -385,6 +394,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_TLSLD:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_LO:
case R_PPC64_DTPREL16_LO_DS:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -393,13 +403,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;
void Thunk<E>::copy_buf(Context<E> &ctx) {
// 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.
static const ub32 pltgot_thunk[] = {
constexpr ub32 pltgot_thunk[] = {
// Store the caller's %r2
0xf841'0028, // std %r2, 40(%r1)
@@ -417,7 +425,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
};
// If the destination is .plt, read a function descriptor from .got.plt.
static const ub32 plt_thunk[] = {
constexpr ub32 plt_thunk[] = {
// Store the caller's %r2
0xf841'0028, // std %r2, 40(%r1)
@@ -436,7 +444,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
// If the destination is a non-imported function, we directly jump
// to the function entry address.
static const ub32 local_thunk[] = {
constexpr ub32 local_thunk[] = {
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
@@ -450,65 +458,61 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(local_thunk));
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
u64 TOC = ctx.extra.TOC->value;
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
u8 *buf = base + offsets[i];
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);
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
i64 val = sym.get_got_addr(ctx) - TOC;
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else if(sym.has_plt(ctx)) {
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);
memcpy(buf, plt_thunk, sizeof(plt_thunk));
i64 val = sym.get_gotplt_addr(ctx) - TOC;
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else {
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);
memcpy(buf, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx, NO_OPD) - TOC;
*(ub32 *)buf |= higha(val);
*(ub32 *)(buf + 4) |= lo(val);
}
}
}
static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
if (isec && isec->name() == ".opd")
if (isec && isec->name == ".opd")
return isec.get();
return nullptr;
}
static ElfRel<E> *
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
[](const ElfRel<E> &r, i64 offset) {
return r.r_offset < offset;
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
return (u64)r.r_offset;
});
if (it == rels.end())
return nullptr;
if (it->r_offset != offset)
if (it == rels.end() || it->r_offset != offset)
return nullptr;
return &*it;
}
namespace {
struct OpdSymbol {
bool operator<(const OpdSymbol &x) const { return r_offset < x.r_offset; }
u64 r_offset = 0;
Symbol<E> *sym = nullptr;
};
}
static Symbol<E> *
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
if (it == syms.end())
return nullptr;
if (it->r_offset != offset)
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
if (it == syms.end() || it->r_offset != offset)
return nullptr;
return it->sym;
}
@@ -521,10 +525,10 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
// However, in reality, .opd isn't a normal input section. It needs many
// special treatments as follows:
//
// 1. A function symbol refers not a .text but an .opd. Its address works
// fine for address-taking relocations such as R_PPC64_ADDR64. However,
// R_PPC64_REL24 (which is used for branch instruction) needs a
// function's real address instead of the function's .opd address.
// 1. A function symbol refers to not a .text but an .opd. Its address
// works fine for address-taking relocations such as R_PPC64_ADDR64.
// However, R_PPC64_REL24 (which is used for branch instruction) needs
// a function's real address instead of the function's .opd address.
// We need to read .opd contents to find out a function entry point
// address to apply R_PPC64_REL24.
//
@@ -532,9 +536,9 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
// are taken. Just copying input .opd sections to an output would
// produces lots of dead .opd entries.
//
// 3. In this design, all function symbols refer an .opd section, and that
// doesn't work well with graph traversal optimizations such as garbage
// collection or identical comdat folding. For example, garbage
// 3. In this design, all function symbols refer to an .opd section, and
// that doesn't work well with graph traversal optimizations such as
// garbage collection or identical comdat folding. For example, garbage
// collector would mark an .opd alive which in turn mark all functions
// thatare referenced by .opd as alive, effectively keeping all
// functions as alive.
@@ -547,9 +551,9 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
//
// So, in this function, we undo what the compiler did to .opd. We remove
// function symbols from .opd and reattach them to their function entry
// points. We also rewrite relocations that directly refer an input .opd
// section so that they refer function symbols instead. We then mark input
// .opd sections as dead.
// points. We also rewrite relocations that directly refer to an input
// .opd section so that they refer to function symbols instead. We then
// mark input .opd sections as dead.
//
// After this function, we mark symbols with the NEEDS_PPC_OPD flag if the
// symbol needs an .opd entry. We then create an output .opd just like we
@@ -578,7 +582,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
Symbol<E> *sym2 = file->symbols[rel->r_sym];
if (sym2->get_type() != STT_SECTION)
Fatal(ctx) << *file << ": bad relocation in .opd referring " << *sym2;
Fatal(ctx) << *file << ": bad relocation in .opd referring to " << *sym2;
opd_syms.push_back({sym->value, sym});
@@ -587,7 +591,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
}
// Sort symbols so that get_opd_sym_at() can do binary search.
sort(opd_syms);
ranges::stable_sort(opd_syms, {}, &OpdSymbol::r_offset);
// Rewrite relocations so that they directly refer to .opd.
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
@@ -599,7 +603,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;
@@ -612,8 +616,8 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
}
// When a function is exported, the dynamic symbol for the function should
// refers the function's .opd entry. This function marks such symbols with
// NEEDS_PPC_OPD.
// refers to the function's .opd entry. This function marks such symbols
// with NEEDS_PPC_OPD.
void ppc64v1_scan_symbols(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
@@ -623,15 +627,14 @@ void ppc64v1_scan_symbols(Context<E> &ctx) {
});
// Functions referenced by the ELF header also have to have .opd entries.
auto mark = [&](std::string_view name) {
if (!name.empty())
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
sym.flags |= NEEDS_PPC_OPD;
};
if (!ctx.arg.entry->is_imported)
ctx.arg.entry->flags |= NEEDS_PPC_OPD;
mark(ctx.arg.entry);
mark(ctx.arg.init);
mark(ctx.arg.fini);
if (!ctx.arg.init->is_imported)
ctx.arg.init->flags |= NEEDS_PPC_OPD;
if (!ctx.arg.fini->is_imported)
ctx.arg.fini->flags |= NEEDS_PPC_OPD;
}
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
@@ -640,14 +643,31 @@ void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
this->shdr.sh_size += ENTRY_SIZE;
}
std::vector<ElfRel<E>> PPC64OpdSection::collect_dynrels(Context<E> &ctx) const {
if (!ctx.arg.pic)
return {};
std::vector<ElfRel<E>> rels;
for (Symbol<E> *sym : symbols) {
u64 loc = sym->get_opd_addr(ctx);
rels.emplace_back(loc, E::R_RELATIVE, 0, sym->get_addr(ctx, NO_PLT | NO_OPD));
rels.emplace_back(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
}
return rels;
}
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
for (Symbol<E> *sym : symbols) {
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
*buf++ = addr;
*buf++ = ctx.extra.TOC->value;
*buf++ = 0;
}
}
} // namespace mold::elf
} // namespace mold
#endif
+682
View File
@@ -0,0 +1,682 @@
// This file implements the PowerPC ELFv2 ABI which was standardized in
// 2014. Modern little-endian PowerPC systems are based on this ABI.
// The ABI is often referred to as "ppc64le". This shouldn't be confused
// with "ppc64" which refers to the original, big-endian PowerPC systems.
//
// PPC64 is a bit tricky to support because PC-relative load/store
// instructions hadn't been available until Power10 which debuted in 2021.
// Prior to Power10, it wasn't trivial for position-independent code (PIC)
// to load a value from, for example, .got, as we can't do that with [PC +
// the offset to the .got entry].
//
// In the following, I'll explain how PIC is supported on pre-Power10
// systems first and then explain what has changed with Power10.
//
//
// Position-independent code on Power9 or earlier:
//
// We can get the program counter on older PPC64 systems with the
// following four instructions
//
// mflr r1 // save the current link register to r1
// bl .+4 // branch to the next instruction as if it were a function
// mflr r0 // copy the return address to r0
// mtlr r1 // restore the original link register value
//
// , but it's too expensive to do if we do this for each load/store.
//
// As a workaround, most functions are compiled in such a way that r2 is
// assumed to always contain the address of .got + 0x8000. With this, we
// can for example load the first entry of .got with a single instruction
// `lw r0, -0x8000(r2)`. r2 is called the TOC pointer.
//
// There's only one .got for each ELF module. Therefore, if a callee is in
// the same ELF module, r2 doesn't have to be recomputed. Most function
// calls are usually within the same ELF module, so this mechanism is
// efficient.
//
// A function compiled for pre-Power10 usually has two entry points,
// global and local. The global entry point usually 8 bytes precedes
// the local entry point. In between is the following instructions:
//
// addis r2, r12, .TOC.@ha
// addi r2, r2, .TOC.@lo + 4;
//
// The global entry point assumes that the address of itself is in r12,
// and it computes its own TOC pointer from r12. It's easy to do so for
// the callee because the offset between its .got + 0x8000 and the
// function is known at link-time. The above code sequence then falls
// through to the local entry point that assumes r2 is .got + 0x8000.
//
// So, if a callee's TOC pointer is different from the current one
// (e.g. calling a function in another .so), we first load the callee's
// address to r12 (e.g. from .got.plt with a r2-relative load) and branch
// to that address. Then the callee computes its own TOC pointer using
// r12.
//
//
// Position-independent code on Power10:
//
// Power10 added 8-bytes-long instructions to the ISA. Some of them are
// PC-relative load/store instructions that take 34 bits offsets.
// Functions compiled with `-mcpu=power10` use these instructions for PIC.
// r2 does not have a special meaning in such fucntions.
//
// When a fucntion compiled for Power10 calls a function that uses the TOC
// pointer, we need to compute a correct value for TOC and set it to r2
// before transferring the control to the callee. Thunks are responsible
// for doing it.
//
// `_NOTOC` relocations such as `R_PPC64_REL24_NOTOC` indicate that the
// callee does not use TOC (i.e. compiled with `-mcpu=power10`). If a
// function using TOC is referenced via a `_NOTOC` relocation, that call
// is made through a range extension thunk.
//
//
// Note on section names: the PPC64 psABI uses a weird naming convention
// which calls .got.plt .plt. We ignored that part because it's just
// confusing. Since the runtime only cares about segments, we should be
// able to name sections whatever we want.
//
// https://github.com/rui314/psabi/blob/main/ppc64v2.pdf
#if MOLD_PPC64V2
#include "mold.h"
namespace mold {
using E = PPC64V2;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static 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
// calls are made via range extension thunks even if they are within
// reach. Thunks read addresses from .got.plt and jump there.
// Therefore, once PLT symbols are resolved and final addresses are
// written to .got.plt, thunks just skip .plt and directly jump to the
// resolved addresses.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
constexpr ul32 insn[] = {
// Get PC
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
0x7c08'03a6, // mtlr r0
// Compute the PLT entry index
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
};
memcpy(buf, insn, sizeof(insn));
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
*(ul32 *)(buf + 28) |= higha(val);
*(ul32 *)(buf + 32) |= lo(val);
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
// When the control is transferred to a PLT entry, the PLT entry's
// address is already set to %r12 by the caller.
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx);
*(ul32 *)buf = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
}
// .plt.got is not necessary on PPC64 because range extension thunks
// directly read GOT entries and jump there.
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_PPC64_ADDR64:
*(ul64 *)loc = val;
break;
case R_PPC64_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_PPC64_REL64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
i64 val = sym.esym().ppc64_local_entry;
assert(val <= 7);
if (val == 7)
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
if (val == 0 || val == 1)
return 0;
return 1 << val;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
u64 TOC = ctx.extra.TOC->value;
auto r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
auto no_r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 8; };
switch (rel.r_type) {
case R_PPC64_TOC16_HA:
*(ul16 *)loc = ha(S + A - TOC);
break;
case R_PPC64_TOC16_LO:
*(ul16 *)loc = lo(S + A - TOC);
break;
case R_PPC64_TOC16_DS:
case R_PPC64_TOC16_LO_DS:
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
break;
case R_PPC64_REL24:
if (sym.has_plt(ctx) || !sym.esym().ppc64_preserves_r2()) {
i64 val = r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
// The thunk saves %r2 to the caller's r2 save slot. We need to
// restore it after function return. To do so, there's usually a
// NOP as a placeholder after a BL. 0x6000'0000 is a NOP.
if (*(ul32 *)(loc + 4) == 0x6000'0000)
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
} else {
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
if (!is_int(val, 26))
val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
}
break;
case R_PPC64_REL24_NOTOC:
if (sym.has_plt(ctx) || sym.esym().ppc64_uses_toc()) {
i64 val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
} else {
i64 val = S + A - P;
if (!is_int(val, 26))
val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
}
break;
case R_PPC64_REL14:
*(ul32 *)loc |= bits(S + A - P, 15, 2) << 2;
break;
case R_PPC64_REL32:
*(ul32 *)loc = S + A - P;
break;
case R_PPC64_REL64:
*(ul64 *)loc = S + A - P;
break;
case R_PPC64_REL16_HA:
*(ul16 *)loc = ha(S + A - P);
break;
case R_PPC64_REL16_LO:
*(ul16 *)loc = lo(S + A - P);
break;
case R_PPC64_GOT16:
*(ul16 *)loc = G - TOC;
break;
case R_PPC64_GOT16_LO:
*(ul16 *)loc = lo(G - TOC);
break;
case R_PPC64_GOT16_HI:
*(ul16 *)loc = hi(G - TOC);
break;
case R_PPC64_GOT16_HA:
*(ul16 *)loc = ha(G - TOC);
break;
case R_PPC64_PLT16_HA:
*(ul16 *)loc = ha(G + GOT - TOC);
break;
case R_PPC64_PLT16_HI:
*(ul16 *)loc = hi(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO:
*(ul16 *)loc = lo(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO_DS:
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
break;
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
case R_PPC64_GOT_PCREL34:
write34(loc, G + GOT - P);
break;
case R_PPC64_PCREL34:
write34(loc, S + A - P);
break;
case R_PPC64_GOT_TPREL16_HA:
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_GOT_TPREL_PCREL34:
write34(loc, sym.get_gottp_addr(ctx) - P);
break;
case R_PPC64_GOT_TLSGD16_HA:
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_LO:
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD_PCREL34:
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);
break;
case R_PPC64_GOT_TLSLD16_LO:
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD_PCREL34:
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
break;
case R_PPC64_DTPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC64_DTPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_DTPREL16_LO_DS:
*(ul16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
break;
case R_PPC64_DTPREL34:
write34(loc, S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC64_TPREL16_LO_DS:
*(ul16 *)loc |= (S + A - ctx.tp_addr) & 0xfffc;
break;
case R_PPC64_TPREL34:
write34(loc, S + A - ctx.tp_addr);
break;
case R_PPC64_ADDR64:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTSEQ_NOTOC:
case R_PPC64_PLTCALL:
case R_PPC64_PLTCALL_NOTOC:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
case R_PPC64_ENTRY:
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || 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 : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
case R_PPC64_ADDR32:
check(S + A, 0, 1LL << 32);
*(ul32 *)loc = S + A;
break;
case R_PPC64_DTPREL64:
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_PPC64_GOT_TPREL16_HA:
case R_PPC64_GOT_TPREL_PCREL34:
sym.flags |= NEEDS_GOTTP;
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_PPC64_REL24_NOTOC:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
ctx.extra.is_power10 = true;
break;
case R_PPC64_GOT16:
case R_PPC64_GOT16_LO:
case R_PPC64_GOT16_HI:
case R_PPC64_GOT16_HA:
case R_PPC64_PLT16_HA:
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
case R_PPC64_GOT_PCREL34:
sym.flags |= NEEDS_GOT;
break;
case R_PPC64_GOT_TLSGD16_HA:
case R_PPC64_GOT_TLSGD_PCREL34:
sym.flags |= NEEDS_TLSGD;
break;
case R_PPC64_GOT_TLSLD16_HA:
case R_PPC64_GOT_TLSLD_PCREL34:
ctx.needs_tlsld = true;
break;
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL34:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_ADDR64:
case R_PPC64_REL14:
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO:
case R_PPC64_TOC16_LO_DS:
case R_PPC64_TOC16_DS:
case R_PPC64_REL16_HA:
case R_PPC64_REL16_LO:
case R_PPC64_PLT16_HI:
case R_PPC64_PLT16_LO:
case R_PPC64_PLT16_LO_DS:
case R_PPC64_PCREL34:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTSEQ_NOTOC:
case R_PPC64_PLTCALL:
case R_PPC64_PLTCALL_NOTOC:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL16_LO_DS:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_LO:
case R_PPC64_DTPREL16_LO_DS:
case R_PPC64_DTPREL34:
case R_PPC64_ENTRY:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
// If the destination is PLT, we read an address from .got.plt or .got
// and jump there.
constexpr 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
0x4e80'0420, // bctr
};
constexpr 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
0x4e80'0420, // bctr
};
// If the destination is a non-imported function, we directly jump
// to its local entry point.
constexpr 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
0x4e80'0420, // bctr
};
constexpr 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
0x4e80'0420, // bctr
};
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(plt_thunk_power10));
static_assert(E::thunk_size == sizeof(local_thunk));
static_assert(E::thunk_size == sizeof(local_thunk_power10));
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
u64 TOC = ctx.extra.TOC->value;
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
if (sym.has_plt(ctx)) {
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
if (ctx.extra.is_power10) {
memcpy(buf, plt_thunk_power10, E::thunk_size);
write34(buf + 8, got - P - 8);
} else {
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(buf, local_thunk_power10, E::thunk_size);
write34(buf + 8, S - P - 8);
} else {
memcpy(buf, local_thunk, E::thunk_size);
*(ul32 *)(buf + 8) |= higha(S - TOC);
*(ul32 *)(buf + 12) |= lo(S - TOC);
}
}
}
}
// GCC may emit references to the following functions in function prologue
// and epiilogue if -Os is specified. For some reason, these functions are
// not in libgcc.a and expected to be synthesized by the linker.
const std::vector<std::pair<std::string_view, u32>>
ppc64_save_restore_insns = {
{ "_savegpr0_14", 0xf9c1ff70 }, // std r14,-144(r1)
{ "_savegpr0_15", 0xf9e1ff78 }, // std r15,-136(r1)
{ "_savegpr0_16", 0xfa01ff80 }, // std r16,-128(r1)
{ "_savegpr0_17", 0xfa21ff88 }, // std r17,-120(r1)
{ "_savegpr0_18", 0xfa41ff90 }, // std r18,-112(r1)
{ "_savegpr0_19", 0xfa61ff98 }, // std r19,-104(r1)
{ "_savegpr0_20", 0xfa81ffa0 }, // std r20,-96(r1)
{ "_savegpr0_21", 0xfaa1ffa8 }, // std r21,-88(r1)
{ "_savegpr0_22", 0xfac1ffb0 }, // std r22,-80(r1)
{ "_savegpr0_23", 0xfae1ffb8 }, // std r23,-72(r1)
{ "_savegpr0_24", 0xfb01ffc0 }, // std r24,-64(r1)
{ "_savegpr0_25", 0xfb21ffc8 }, // std r25,-56(r1)
{ "_savegpr0_26", 0xfb41ffd0 }, // std r26,-48(r1)
{ "_savegpr0_27", 0xfb61ffd8 }, // std r27,-40(r1)
{ "_savegpr0_28", 0xfb81ffe0 }, // std r28,-32(r1)
{ "_savegpr0_29", 0xfba1ffe8 }, // std r29,-24(r1)
{ "_savegpr0_30", 0xfbc1fff0 }, // std r30,-16(r1)
{ "_savegpr0_31", 0xfbe1fff8 }, // std r31,-8(r1)
{ "", 0xf8010010 }, // std r0,16(r1)
{ "", 0x4e800020 }, // blr
{ "_restgpr0_14", 0xe9c1ff70 }, // ld r14,-144(r1)
{ "_restgpr0_15", 0xe9e1ff78 }, // ld r15,-136(r1)
{ "_restgpr0_16", 0xea01ff80 }, // ld r16,-128(r1)
{ "_restgpr0_17", 0xea21ff88 }, // ld r17,-120(r1)
{ "_restgpr0_18", 0xea41ff90 }, // ld r18,-112(r1)
{ "_restgpr0_19", 0xea61ff98 }, // ld r19,-104(r1)
{ "_restgpr0_20", 0xea81ffa0 }, // ld r20,-96(r1)
{ "_restgpr0_21", 0xeaa1ffa8 }, // ld r21,-88(r1)
{ "_restgpr0_22", 0xeac1ffb0 }, // ld r22,-80(r1)
{ "_restgpr0_23", 0xeae1ffb8 }, // ld r23,-72(r1)
{ "_restgpr0_24", 0xeb01ffc0 }, // ld r24,-64(r1)
{ "_restgpr0_25", 0xeb21ffc8 }, // ld r25,-56(r1)
{ "_restgpr0_26", 0xeb41ffd0 }, // ld r26,-48(r1)
{ "_restgpr0_27", 0xeb61ffd8 }, // ld r27,-40(r1)
{ "_restgpr0_28", 0xeb81ffe0 }, // ld r28,-32(r1)
{ "_restgpr0_29", 0xe8010010 }, // ld r0,16(r1)
{ "", 0xeba1ffe8 }, // ld r29,-24(r1)
{ "", 0x7c0803a6 }, // mtlr r0
{ "", 0xebc1fff0 }, // ld r30,-16(r1)
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
{ "", 0x4e800020 }, // blr
{ "_restgpr0_30", 0xebc1fff0 }, // ld r30,-16(r1)
{ "_restgpr0_31", 0xe8010010 }, // ld r0,16(r1)
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
{ "", 0x7c0803a6 }, // mtlr r0
{ "", 0x4e800020 }, // blr
{ "_savegpr1_14", 0xf9ccff70 }, // std r14,-144(r12)
{ "_savegpr1_15", 0xf9ecff78 }, // std r15,-136(r12)
{ "_savegpr1_16", 0xfa0cff80 }, // std r16,-128(r12)
{ "_savegpr1_17", 0xfa2cff88 }, // std r17,-120(r12)
{ "_savegpr1_18", 0xfa4cff90 }, // std r18,-112(r12)
{ "_savegpr1_19", 0xfa6cff98 }, // std r19,-104(r12)
{ "_savegpr1_20", 0xfa8cffa0 }, // std r20,-96(r12)
{ "_savegpr1_21", 0xfaacffa8 }, // std r21,-88(r12)
{ "_savegpr1_22", 0xfaccffb0 }, // std r22,-80(r12)
{ "_savegpr1_23", 0xfaecffb8 }, // std r23,-72(r12)
{ "_savegpr1_24", 0xfb0cffc0 }, // std r24,-64(r12)
{ "_savegpr1_25", 0xfb2cffc8 }, // std r25,-56(r12)
{ "_savegpr1_26", 0xfb4cffd0 }, // std r26,-48(r12)
{ "_savegpr1_27", 0xfb6cffd8 }, // std r27,-40(r12)
{ "_savegpr1_28", 0xfb8cffe0 }, // std r28,-32(r12)
{ "_savegpr1_29", 0xfbacffe8 }, // std r29,-24(r12)
{ "_savegpr1_30", 0xfbccfff0 }, // std r30,-16(r12)
{ "_savegpr1_31", 0xfbecfff8 }, // std r31,-8(r12)
{ "", 0x4e800020 }, // blr
{ "_restgpr1_14", 0xe9ccff70 }, // ld r14,-144(r12)
{ "_restgpr1_15", 0xe9ecff78 }, // ld r15,-136(r12)
{ "_restgpr1_16", 0xea0cff80 }, // ld r16,-128(r12)
{ "_restgpr1_17", 0xea2cff88 }, // ld r17,-120(r12)
{ "_restgpr1_18", 0xea4cff90 }, // ld r18,-112(r12)
{ "_restgpr1_19", 0xea6cff98 }, // ld r19,-104(r12)
{ "_restgpr1_20", 0xea8cffa0 }, // ld r20,-96(r12)
{ "_restgpr1_21", 0xeaacffa8 }, // ld r21,-88(r12)
{ "_restgpr1_22", 0xeaccffb0 }, // ld r22,-80(r12)
{ "_restgpr1_23", 0xeaecffb8 }, // ld r23,-72(r12)
{ "_restgpr1_24", 0xeb0cffc0 }, // ld r24,-64(r12)
{ "_restgpr1_25", 0xeb2cffc8 }, // ld r25,-56(r12)
{ "_restgpr1_26", 0xeb4cffd0 }, // ld r26,-48(r12)
{ "_restgpr1_27", 0xeb6cffd8 }, // ld r27,-40(r12)
{ "_restgpr1_28", 0xeb8cffe0 }, // ld r28,-32(r12)
{ "_restgpr1_29", 0xebacffe8 }, // ld r29,-24(r12)
{ "_restgpr1_30", 0xebccfff0 }, // ld r30,-16(r12)
{ "_restgpr1_31", 0xebecfff8 }, // ld r31,-8(r12)
{ "", 0x4e800020 }, // blr
};
void PPC64SaveRestoreSection::copy_buf(Context<E> &ctx) {
ul32 *buf = (ul32 *)(ctx.buf + this->shdr.sh_offset);
for (auto [label, insn] : ppc64_save_restore_insns)
*buf++ = insn;
}
template <>
u64 get_eflags(Context<E> &ctx) {
return 2;
}
} // namespace mold
#endif
+1277
View File
File diff suppressed because it is too large. Load diff
+169 -215
View File
@@ -33,11 +33,13 @@
// we need to add TP to a return value before use. I don't know why it is
// different, but that is the way it is.
//
// https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
// https://github.com/IBM/s390x-abi/releases/download/v1.6.1/lzsabi_s390x.pdf
#if MOLD_S390X
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = S390X;
@@ -48,16 +50,29 @@ static void write_mid20(u8 *loc, u64 val) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static u8 insn[] = {
// Compute the offset into .rela.plt. This is equivalent to
// (%r0 - %r1 - 48 - 14) * 3/2 where %r0 is the PLT entry address
// plus 14, %r1 is the start address of .plt, and 48 is the size
// of this PLT header. We multiply by 3/2 because each PLT entry
// is 16 bytes, whereas each .rela.plt entry is 24 bytes.
0xb9, 0x09, 0x00, 0x01, // sgr %r0, %r1
0xa7, 0x0b, 0xff, 0xc2, // aghi %r0, -62
0xeb, 0x10, 0x00, 0x01, 0x00, 0x0c, // srlg %r1, %r0, 1
0xb9, 0x08, 0x00, 0x01, // agr %r0, %r1
// Store the computed value to 56(%r15) and .got.plt[1] to 48(%15)
// where %r15 is the stack pointer.
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
// Branch to _dl_runtime_resolve
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x00, 0x00, 0x00, 0x00, // (filler)
};
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 +80,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
0x00, 0x00, // (filler)
};
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 <>
@@ -82,16 +94,16 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, // nopr
0x00, 0x00, // (filler)
};
memcpy(buf, insn, sizeof(insn));
*(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 <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -112,11 +124,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -125,160 +132,114 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
check(val, lo, hi);
// R_390_*DBL relocs should never refer a symbol at an odd address
if (val & 1)
Error(ctx) << *this << ": misaligned symbol " << sym
<< " for relocation " << rel;
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
// R_390_*DBL relocs should never refer to a symbol at an odd address
check(val, lo, hi);
if (val & 1)
Error(ctx) << *this << ": misaligned symbol " << sym
<< " for relocation " << rel;
};
switch (rel.r_type) {
case R_390_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_390_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
case R_390_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_390_12: {
i64 val = S + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
case R_390_12:
check(S + A, 0, 1 << 12);
*(ul16 *)loc |= bits(S + A, 11, 0);
break;
}
case R_390_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
case R_390_16:
check(S + A, 0, 1 << 16);
*(ub16 *)loc = S + A;
break;
}
case R_390_20: {
i64 val = S + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
case R_390_20:
check(S + A, 0, 1 << 20);
write_mid20(loc, S + A);
break;
}
case R_390_32:
case R_390_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_390_PLT64:
*(ub64 *)loc = S + A;
case R_390_PLT32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
case R_390_PC12DBL:
case R_390_PLT12DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 12), 1 << 12);
*(ul16 *)loc = (val >> 1) & 0x0fff;
case R_390_PLT12DBL:
check_dbl(S + A - P, -(1 << 12), 1 << 12);
*(ul16 *)loc |= bits(S + A - P, 12, 1);
break;
}
case R_390_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
case R_390_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - P;
break;
}
case R_390_PC32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
case R_390_PC32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - P;
break;
}
case R_390_PC64:
case R_390_PLT64:
*(ub64 *)loc = S + A - P;
break;
case R_390_PC16DBL:
case R_390_PLT16DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 16), 1 << 16);
*(ub16 *)loc = val >> 1;
case R_390_PLT16DBL:
check_dbl(S + A - P, -(1 << 16), 1 << 16);
*(ub16 *)loc = (S + A - P) >> 1;
break;
}
case R_390_PC24DBL:
case R_390_PLT24DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 24), 1 << 24);
*(ub32 *)loc |= bits(val, 24, 1);
case R_390_PLT24DBL:
check_dbl(S + A - P, -(1 << 24), 1 << 24);
*(ub32 *)loc |= bits(S + A - P, 24, 1);
break;
}
case R_390_PC32DBL:
case R_390_PLT32DBL:
if (ctx.is_static && &sym == ctx.tls_get_offset) {
// __tls_get_offset() in libc.a is stub code that calls abort().
// So we provide a replacement function.
*(ub32 *)loc = (ctx.extra.tls_get_offset->shdr.sh_addr - P) >> 1;
} else {
i64 val = S + A - P;
check_dbl(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
}
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (S + A - P) >> 1;
break;
case R_390_GOT12:
case R_390_GOTPLT12: {
i64 val = G + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
case R_390_GOTPLT12:
check(G + A, 0, 1 << 12);
*(ul16 *)loc |= bits(G + A, 11, 0);
break;
}
case R_390_GOT16:
case R_390_GOTPLT16: {
i64 val = G + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
case R_390_GOTPLT16:
check(G + A, 0, 1 << 16);
*(ub16 *)loc = G + A;
break;
}
case R_390_GOT20:
case R_390_GOTPLT20: {
i64 val = G + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
case R_390_GOTPLT20:
check(G + A, 0, 1 << 20);
write_mid20(loc, G + A);
break;
}
case R_390_GOT32:
case R_390_GOTPLT32: {
i64 val = G + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_390_GOTPLT32:
check(G + A, 0, 1LL << 32);
*(ub32 *)loc = G + A;
break;
}
case R_390_GOT64:
case R_390_GOTPLT64:
*(ub64 *)loc = G + A;
break;
case R_390_GOTOFF16:
case R_390_PLTOFF16: {
i64 val = S + A - GOT;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
case R_390_PLTOFF16:
check(S + A - GOT, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - GOT;
break;
}
case R_390_GOTOFF32:
case R_390_PLTOFF32: {
i64 val = S + A - GOT;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
case R_390_PLTOFF32:
check(S + A - GOT, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - GOT;
break;
}
case R_390_GOTOFF64:
case R_390_PLTOFF64:
*(ub64 *)loc = S + A - GOT;
@@ -286,18 +247,27 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_390_GOTPC:
*(ub64 *)loc = GOT + A - P;
break;
case R_390_GOTPCDBL: {
i64 val = GOT + A - P;
check_dbl(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
case R_390_GOTPCDBL:
check_dbl(GOT + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (GOT + A - P) >> 1;
break;
}
case R_390_GOTENT: {
i64 val = GOT + G + A - P;
check(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
case R_390_GOTENT:
// If we can relax a GOT-loading LGRL to an address-materializing
// LARL, do that. The format of LGRL is 0xc 0x4 <reg> 0x8 followed
// by a 32-bit offset. LARL is 0xc 0x0 <reg> 0x0.
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx)) {
u64 op = *(ub16 *)(loc - 2);
u64 val = S + A - P;
if ((op & 0xff0f) == 0xc408 && A == 2 && (val & 1) == 0 &&
is_int(val, 33)) {
*(ub16 *)(loc - 2) = 0xc000 | (op & 0x00f0);
*(ub32 *)loc = val >> 1;
break;
}
}
check_dbl(GOT + G + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (GOT + G + A - P) >> 1;
break;
}
case R_390_TLS_LE32:
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
@@ -313,47 +283,57 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_390_TLS_GD32:
if (sym.has_tlsgd(ctx))
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
else if (sym.has_gottp(ctx))
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
else
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_GD64:
if (sym.has_tlsgd(ctx))
*(ub64 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
else if (sym.has_gottp(ctx))
*(ub64 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
else
*(ub64 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_GDCALL:
if (!sym.has_tlsgd(ctx)) {
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
if (sym.has_tlsgd(ctx)) {
// do nothing
} else if (sym.has_gottp(ctx)) {
// lg %r2, 0(%r2, %r12)
static u8 insn[] = { 0xe3, 0x22, 0xc0, 0x00, 0x00, 0x04 };
memcpy(loc, insn, sizeof(insn));
} else {
// nop
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, insn, sizeof(insn));
}
break;
case R_390_TLS_LDM32:
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
else
*(ub32 *)loc = ctx.dtp_addr - ctx.tp_addr;
break;
case R_390_TLS_LDM64:
if (ctx.got->has_tlsld(ctx))
*(ub64 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
break;
case R_390_TLS_LDO32:
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc = S + A - ctx.dtp_addr;
else
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_LDO64:
if (ctx.got->has_tlsld(ctx))
*(ub64 *)loc = S + A - ctx.dtp_addr;
else
*(ub64 *)loc = S + A - ctx.tp_addr;
*(ub64 *)loc = ctx.dtp_addr - ctx.tp_addr;
break;
case R_390_TLS_LDCALL:
if (!ctx.got->has_tlsld(ctx)) {
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
// nop
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, insn, sizeof(insn));
}
break;
case R_390_TLS_LDO32:
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
case R_390_TLS_LDO64:
*(ub64 *)loc = S + A - ctx.dtp_addr;
break;
default:
unreachable();
}
@@ -366,24 +346,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -391,13 +359,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
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;
@@ -419,30 +389,20 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_390_64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_390_8:
case R_390_12:
case R_390_16:
@@ -450,8 +410,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_32:
scan_absrel(ctx, sym, rel);
break;
case R_390_PC12DBL:
case R_390_PC16:
case R_390_PC16DBL:
case R_390_PC24DBL:
case R_390_PC32:
case R_390_PC32DBL:
case R_390_PC64:
@@ -473,7 +435,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_GOTPC:
case R_390_GOTPCDBL:
case R_390_GOTENT:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_390_PLT12DBL:
case R_390_PLT16DBL:
@@ -485,57 +447,49 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_PLTOFF32:
case R_390_PLTOFF64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_390_TLS_GOTIE20:
case R_390_TLS_IEENT:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_390_TLS_GD32:
case R_390_TLS_GD64:
if (!relax_tlsgd(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
// We always want to relax calls to __tls_get_offset() in statically-
// linked executables because __tls_get_offset() in libc.a just calls
// abort().
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// Do nothing
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
} else {
sym.flags |= NEEDS_TLSGD;
}
break;
case R_390_TLS_LDM32:
case R_390_TLS_LDM64:
if (!relax_tlsld(ctx))
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
// Do nothing
} else {
ctx.needs_tlsld = true;
}
break;
case R_390_TLS_LE32:
case R_390_TLS_LE64:
check_tlsle(ctx, sym, rel);
break;
case R_390_64:
case R_390_TLS_LDO32:
case R_390_TLS_LDO64:
case R_390_TLS_GDCALL:
case R_390_TLS_LDCALL:
break;
default:
Fatal(ctx) << *this << ": scan_relocations: " << rel;
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
// __tls_get_offset() in libc.a just calls abort(), assuming that the
// linker always relaxes TLS calls for statically-linkd executables.
// We don't always do that because we believe --relax and --static
// should be orthogonal.
//
// This section provides a replacement for __tls_get_offset() in libc.a.
void S390XTlsGetOffsetSection::copy_buf(Context<E> &ctx) {
static const u8 insn[] = {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT
0xb9, 0x08, 0x00, 0x21, // agr %r2, %r1
0xe3, 0x20, 0x20, 0x08, 0x00, 0x04, // lg %r2, 8(%r2)
0xc0, 0x11, 0, 0, 0, 0, // lgfi %r1, TLS_BLOCK_SIZE
0xb9, 0x09, 0x00, 0x21, // sgr %r2, %r1
0x07, 0xfe, // br %r14
};
} // namespace mold
assert(this->shdr.sh_size == sizeof(insn));
u8 *loc = ctx.buf + this->shdr.sh_offset;
memcpy(loc, insn, sizeof(insn));
*(ub32 *)(loc + 2) = (ctx.got->shdr.sh_addr - this->shdr.sh_addr) >> 1;
*(ub32 *)(loc + 18) = ctx.tp_addr - ctx.tls_begin;
}
} // namespace mold::elf
#endif
+106 -98
View File
@@ -58,11 +58,13 @@
// output from the linker contains lots of text relocations. That's not
// a problem with embedded programming, I guess.
#if MOLD_SH4LE || MOLD_SH4BE
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = SH4;
using E = MOLD_TARGET;
// Even though SH-4 uses RELA-type relocations, addends are stored to
// relocated places for some reason.
@@ -84,120 +86,142 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_SH_GOTOFF:
case R_SH_GOTPC:
case R_SH_GOTPLT32:
return *(ul32 *)loc;
return *(U32<E> *)loc;
default:
return 0;
}
}
template <>
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_SH_DIR32:
case R_SH_REL32:
case R_SH_TLS_GD_32:
case R_SH_TLS_LD_32:
case R_SH_TLS_LDO_32:
case R_SH_TLS_IE_32:
case R_SH_TLS_LE_32:
case R_SH_TLS_DTPMOD32:
case R_SH_TLS_DTPOFF32:
case R_SH_TLS_TPOFF32:
case R_SH_GOT32:
case R_SH_PLT32:
case R_SH_GOTOFF:
case R_SH_GOTPC:
case R_SH_GOTPLT32:
*(U32<E> *)loc = val;
}
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0x02, 0xd2, // mov.l 1f, r2
0xcc, 0x32, // add r12, r2
0x22, 0x50, // mov.l @(8, r2), r0
0x21, 0x52, // mov.l @(4, r2), r2
0x2b, 0x40, // jmp @r0
0x00, 0xe0, // mov #0, r0
0, 0, 0, 0, // 1: .long GOTPLT
constexpr U16<E> insn[] = {
0xd202, // mov.l 1f, r2
0x32cc, // add r12, r2
0x5022, // mov.l @(8, r2), r0
0x5221, // mov.l @(4, r2), r2
0x402b, // jmp @r0
0xe000, // mov #0, r0
0, 0, // 1: .long GOTPLT
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr;
*(U32<E> *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0x02, 0xd2, // mov.l 1f, r2
0x22, 0x50, // mov.l @(8, r2), r0
0x21, 0x52, // mov.l @(4, r2), r2
0x2b, 0x40, // jmp @r0
0x00, 0xe0, // mov #0, r0
0x09, 0x00, // nop
0, 0, 0, 0, // 1: .long GOTPLT
constexpr U16<E> insn[] = {
0xd202, // mov.l 1f, r2
0x5022, // mov.l @(8, r2), r0
0x5221, // mov.l @(4, r2), r2
0x402b, // jmp @r0
0xe000, // mov #0, r0
0xfffd, // (illegal)
0, 0, // 1: .long GOTPLT
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr;
*(U32<E> *)(buf + 12) = ctx.gotplt->shdr.sh_addr;
}
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0xce, 0x00, // mov.l @(r0, r12), r0
0x2b, 0x40, // jmp @r0
0x01, 0xd1, // mov.l 2f, r1
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x00ce, // mov.l @(r0, r12), r0
0x402b, // jmp @r0
0xd101, // mov.l 2f, r1
0, 0, // 1: .long GOTPLT_ENTRY
0, 0, // 2: .long INDEX_IN_RELPLT
};
static_assert(sizeof(insn) == E::plt_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
} else {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0x02, 0x60, // mov.l @r0, r0
0x2b, 0x40, // jmp @r0
0x01, 0xd1, // mov.l 2f, r1
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x6002, // mov.l @r0, r0
0x402b, // jmp @r0
0xd101, // mov.l 2f, r1
0, 0, // 1: .long GOTPLT_ENTRY
0, 0, // 2: .long INDEX_IN_RELPLT
};
static_assert(sizeof(insn) == E::plt_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx);
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx);
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0xce, 0x00, // mov.l @(r0, r12), r0
0x2b, 0x40, // jmp @r0
0x09, 0x00, // nop
0, 0, 0, 0, // 1: .long GOT_ENTRY
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x00ce, // mov.l @(r0, r12), r0
0x402b, // jmp @r0
0x0009, // nop
0, 0, // 1: .long GOT_ENTRY
};
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;
*(U32<E> *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0x02, 0x60, // mov.l @r0, r0
0x2b, 0x40, // jmp @r0
0x09, 0x00, // nop
0, 0, 0, 0, // 1: .long GOT_ENTRY
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x6002, // mov.l @r0, r0
0x402b, // jmp @r0
0x0009, // nop
0, 0, // 1: .long GOT_ENTRY
};
static_assert(sizeof(insn) == E::pltgot_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx);
*(U32<E> *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
}
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_SH_DIR32:
*(ul32 *)loc = val;
*(U32<E> *)loc = val;
break;
case R_SH_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -208,11 +232,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -229,35 +248,34 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_SH_DIR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_SH_REL32:
case R_SH_PLT32:
*(ul32 *)loc = S + A - P;
*(U32<E> *)loc = S + A - P;
break;
case R_SH_GOT32:
*(ul32 *)loc = G;
*(U32<E> *)loc = G;
break;
case R_SH_GOTPC:
*(ul32 *)loc = GOT + A - P;
*(U32<E> *)loc = GOT + A - P;
break;
case R_SH_GOTOFF:
*(ul32 *)loc = S + A - GOT;
*(U32<E> *)loc = S + A - GOT;
break;
case R_SH_TLS_GD_32:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
break;
case R_SH_TLS_LD_32:
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
break;
case R_SH_TLS_LDO_32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_SH_TLS_IE_32:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - GOT;
break;
case R_SH_TLS_LE_32:
*(ul32 *)loc = S + A - ctx.tp_addr;
*(U32<E> *)loc = S + A - ctx.tp_addr;
break;
default:
unreachable();
@@ -271,17 +289,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -292,9 +305,9 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_SH_DIR32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
*(U32<E> *)loc = *val;
else
*(ul32 *)loc = S + A;
*(U32<E> *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
@@ -306,52 +319,45 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
switch (rel.r_type) {
case R_SH_DIR32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_SH_REL32:
scan_pcrel(ctx, sym, rel);
break;
case R_SH_GOT32:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_SH_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_SH_TLS_GD_32:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
sym.flags |= NEEDS_TLSGD;
break;
case R_SH_TLS_LD_32:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
ctx.needs_tlsld = true;
break;
case R_SH_TLS_IE_32:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_SH_TLS_LE_32:
check_tlsle(ctx, sym, rel);
break;
case R_SH_DIR32:
case R_SH_GOTPC:
case R_SH_GOTOFF:
case R_SH_TLS_LDO_32:
case R_SH_TLS_LE_32:
break;
default:
Fatal(ctx) << *this << ": unknown relocation: " << rel;
@@ -359,4 +365,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
#endif
+274 -231
View File
@@ -51,15 +51,13 @@
// address. That also means each function needs to execute the above piece
// of code to become position-independent.
//
// This scheme is very similar to i386. That may not be a coincidence
// because the i386 ELF psABI is created by Sun Microsystems too.
//
// https://docs.oracle.com/cd/E36784_01/html/E36857/chapter6-62988.html
// https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-40/index.html
// https://github.com/rui314/psabi/blob/main/sparc.pdf
#if MOLD_SPARC64
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = SPARC64;
@@ -80,26 +78,77 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
memset(buf, 0, E::plt_hdr_size);
}
// Returns the byte offset within .plt of the data pointer for a large SPARC
// PLT entry. See write_plt_entry below for the block layout this assumes.
i64 sparc_plt_ptr_offset(Context<E> &ctx, i64 pltidx) {
i64 i = pltidx - sparc_num_small_plt;
i64 block = i / 160;
i64 nlarge = (i64)ctx.plt->symbols.size() - sparc_num_small_plt;
i64 nstub = std::min<i64>(160, nlarge - block * 160);
return 0x100000 + block * 5120 + nstub * 24 + (i % 160) * 8;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static ub32 insn[] = {
0x0300'0000, // sethi (. - .PLT0), %g1
0x3068'0000, // ba,a %xcc, .PLT1
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
};
// SPARC uses two PLT entry formats. A "small" entry branches directly to
// the resolver stub (.PLT1) with a BPcc instruction whose reach is only
// ±1 MiB. Once the PLT grows past that (0x100000 bytes), we switch to a
// "large" format: rather than branching to the resolver, a large entry
// loads a 64-bit value from a nearby data pointer and jumps to (that value
// + its own address).
//
// Large entries are grouped into blocks of 160: each block is 160 code
// stubs followed by 160 8-byte data pointers, one per stub. The stubs must
// sit on a fixed grid, 24 bytes apart, because that is how the loader finds
// them: on a lazy bind the stub jumps to the resolver (.PLT0) with only its
// own address, and the loader derives which symbol to resolve from that
// address. Nothing may sit between two stubs, so each stub's pointer lives
// in the block's pointer region, which it reaches with a signed 13-bit ldx
// offset (see to_plt_offset). This layout is dictated by the loader; we
// cannot rearrange or simplify it.
i64 idx = sym.get_plt_idx(ctx);
u64 plt0 = ctx.plt->shdr.sh_addr;
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
u64 entry = sym.get_plt_addr(ctx);
if (idx < sparc_num_small_plt) {
static ub32 insn[] = {
0x0300'0000, // sethi (. - .PLT0), %g1
0x3068'0000, // ba,a %xcc, .PLT1
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
u64 plt0 = ctx.plt->shdr.sh_addr;
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
u64 entry = sym.get_plt_addr(ctx);
memcpy(buf, insn, sizeof(insn));
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
} else {
static ub32 insn[] = {
0x8a10'000f, // mov %o7, %g5
0x4000'0002, // call . + 8
0x0100'0000, // nop
0xc25b'e000, // ldx [ %o7 + .ptr ], %g1
0x83c3'c001, // jmpl %o7 + %g1, %g1
0x9e10'0005, // mov %g5, %o7
};
u64 plt = ctx.plt->shdr.sh_addr;
u64 call = sym.get_plt_addr(ctx) + 4;
i64 ptroff = sparc_plt_ptr_offset(ctx, idx);
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 12) |= bits(plt + ptroff - call, 12, 0);
// The data pointer initially holds (.PLT0 - call) so that the first call
// jumps to .PLT0, where the loader's lazy resolver lives. The resolver
// later overwrites it with (target - call).
*(ub64 *)(ctx.buf + ctx.plt->shdr.sh_offset + ptroff) = plt - call;
}
}
template <>
@@ -116,12 +165,12 @@ 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 <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -143,12 +192,9 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
// We iterate over relocations in reverse order so that it is easy
// to swap instructions for R_SPARC_TLS_GD_CALL.
for (i64 i = (i64)rels.size() - 1; i >= 0; i--) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
@@ -156,114 +202,85 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = (get_addr() + rel.r_offset);
u64 G = (sym.get_got_idx(ctx) * sizeof(Word<E>));
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto rs1 = [&] { return *(ub32 *)loc & (0b11111 << 14); };
auto rs2 = [&] { return *(ub32 *)loc & 0b11111; };
auto rd = [&] { return *(ub32 *)loc & (0b11111 << 25); };
switch (rel.r_type) {
case R_SPARC_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
case R_SPARC_5:
check(S + A, 0, 1 << 5);
*(ub32 *)loc |= bits(S + A, 4, 0);
break;
case R_SPARC_5: {
i64 val = S + A;
check(val, 0, 1 << 5);
*(ub32 *)loc |= bits(val, 4, 0);
case R_SPARC_6:
check(S + A, 0, 1 << 6);
*(ub32 *)loc |= bits(S + A, 5, 0);
break;
}
case R_SPARC_6: {
i64 val = S + A;
check(val, 0, 1 << 6);
*(ub32 *)loc |= bits(val, 5, 0);
case R_SPARC_7:
check(S + A, 0, 1 << 7);
*(ub32 *)loc |= bits(S + A, 6, 0);
break;
}
case R_SPARC_7: {
i64 val = S + A;
check(val, 0, 1 << 7);
*(ub32 *)loc |= bits(val, 6, 0);
case R_SPARC_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
}
case R_SPARC_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*(u8 *)loc = val;
case R_SPARC_10:
check(S + A, 0, 1 << 10);
*(ub32 *)loc |= bits(S + A, 9, 0);
break;
}
case R_SPARC_10: {
i64 val = S + A;
check(val, 0, 1 << 10);
*(ub32 *)loc |= bits(val, 9, 0);
break;
}
case R_SPARC_LO10:
case R_SPARC_LOPLT10:
*(ub32 *)loc |= bits(S + A, 9, 0);
break;
case R_SPARC_11: {
i64 val = S + A;
check(val, 0, 1 << 11);
*(ub32 *)loc |= bits(val, 10, 0);
case R_SPARC_11:
check(S + A, 0, 1 << 11);
*(ub32 *)loc |= bits(S + A, 10, 0);
break;
}
case R_SPARC_13: {
i64 val = S + A;
check(val, 0, 1 << 13);
*(ub32 *)loc |= bits(val, 12, 0);
case R_SPARC_13:
check(S + A, 0, 1 << 13);
*(ub32 *)loc |= bits(S + A, 12, 0);
break;
}
case R_SPARC_16:
case R_SPARC_UA16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
case R_SPARC_UA16:
check(S + A, 0, 1 << 16);
*(ub16 *)loc = S + A;
break;
}
case R_SPARC_22: {
i64 val = S + A;
check(val, 0, 1 << 22);
*(ub32 *)loc |= bits(val, 21, 0);
case R_SPARC_22:
check(S + A, 0, 1 << 22);
*(ub32 *)loc |= bits(S + A, 21, 0);
break;
}
case R_SPARC_32:
case R_SPARC_UA32:
case R_SPARC_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_SPARC_PLT32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_SPARC_PLT64:
case R_SPARC_UA64:
case R_SPARC_REGISTER:
*(ub64 *)loc = S + A;
break;
case R_SPARC_DISP8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*(u8 *)loc = val;
case R_SPARC_DISP8:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
break;
}
case R_SPARC_DISP16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
case R_SPARC_DISP16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ub16 *)loc = S + A - P;
break;
}
case R_SPARC_DISP32:
case R_SPARC_PCPLT32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
case R_SPARC_PCPLT32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = S + A - P;
break;
}
case R_SPARC_DISP64:
*(ub64 *)loc = S + A - P;
break;
@@ -273,25 +290,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub16 *)loc |= (bit(val, 16) << 21) | bits(val, 15, 2);
break;
}
case R_SPARC_WDISP19: {
i64 val = S + A - P;
check(val, -(1 << 20), 1 << 20);
*(ub32 *)loc |= bits(val, 20, 2);
case R_SPARC_WDISP19:
check(S + A - P, -(1 << 20), 1 << 20);
*(ub32 *)loc |= bits(S + A - P, 20, 2);
break;
}
case R_SPARC_WDISP22: {
i64 val = S + A - P;
check(val, -(1 << 23), 1 << 23);
*(ub32 *)loc |= bits(val, 23, 2);
case R_SPARC_WDISP22:
check(S + A - P, -(1 << 23), 1 << 23);
*(ub32 *)loc |= bits(S + A - P, 23, 2);
break;
}
case R_SPARC_WDISP30:
case R_SPARC_WPLT30: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(val, 31, 2);
case R_SPARC_WPLT30:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(S + A - P, 31, 2);
break;
}
case R_SPARC_HI22:
case R_SPARC_HIPLT22:
case R_SPARC_LM22:
@@ -321,39 +332,34 @@ 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()) {
*(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()) {
*(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;
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 {
// 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;
*(ub32 *)loc |= bits(G, 31, 10);
}
break;
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);
}
break;
case R_SPARC_GOTDATA_OP:
if (sym.is_absolute()) {
// ldx [ %rs1 + %rs2 ], %rd → mov %rs2, %rd
*(ub32 *)loc = 0x8010'0000 | rs2() | rd();
} else if (sym.is_pcrel_linktime_const(ctx)) {
// ldx [ %rs1 + %rs2 ], %rd → add %rs1, %rs2, %rd
*(ub32 *)loc = 0x8000'0000 | rs1() | rs2() | rd();
}
break;
case R_SPARC_PC10:
@@ -396,23 +402,79 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc |= bits(S + A, 11, 0);
break;
case R_SPARC_TLS_GD_HI22:
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
if (sym.has_tlsgd(ctx)) {
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
} else if (sym.has_gottp(ctx)) {
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 31, 10);
} else {
*(ub32 *)loc |= bits(~(S + A - ctx.tp_addr), 31, 10);
}
break;
case R_SPARC_TLS_GD_LO10:
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
if (sym.has_tlsgd(ctx)) {
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
} else if (sym.has_gottp(ctx)) {
// add %rs1, %rs2, %rd → or %rs1, $imm, %rd
*(ub32 *)loc = 0x8010'2000 | rs1() | rd();
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 9, 0);
} else {
// add %rs1, %rs2, %rd → xor %rs1, $imm, %rd
*(ub32 *)loc = 0x8018'2000 | rs1() | rd();
*(ub32 *)loc |= bits(S + A - ctx.tp_addr, 9, 0) | 0b1'1100'0000'0000;
}
break;
case R_SPARC_TLS_GD_ADD:
if (sym.has_tlsgd(ctx)) {
// do nothing
} else if (sym.has_gottp(ctx)) {
// add %rs1, %rs2, %rd → ldx [ %rs1 + %rs2 ], %rd
*(ub32 *)loc = 0xc058'0000 | rs1() | rs2() | rd();
} else {
// add %rs1, %rs2, %rd → add %g7, %rs2, %rd
*(ub32 *)loc = 0x8001'c000 | rs2() | rd();
}
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (ctx.arg.is_static)
*(ub32 *)loc |= bits(ctx.extra.tls_get_addr->shdr.sh_addr + A - P, 31, 2);
else
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + A - P, 31, 2);
if (sym.has_tlsgd(ctx)) {
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
} else if (sym.has_gottp(ctx)) {
// When we rewrite a branch instruction with a non-branch one,
// we need to swap the instruction and the following one so that
// the original execution order, which is inverted due to the
// branch delay slot, is preserved.
//
// Since we apply relocations from the end to the beginning,
// the instruction at loc + 4 is already complete.
memcpy(loc, loc + 4, 4);
*(ub32 *)(loc + 4) = 0x9001'c008; // add %g7, %o0, %o0
} else {
*(ub32 *)loc = 0x0100'0000; // call → nop
}
break;
case R_SPARC_TLS_LDM_HI22:
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
else
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
break;
case R_SPARC_TLS_LDM_LO10:
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
else
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
break;
case R_SPARC_TLS_LDM_ADD:
if (!ctx.got->has_tlsld(ctx))
*(ub32 *)loc = 0x8021'c000 | rs2() | rd(); // sub %g7, %rs2, %rd
break;
case R_SPARC_TLS_LDM_CALL:
if (ctx.got->has_tlsld(ctx)) {
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
} else {
*(ub32 *)loc = 0x0100'0000; // nop
}
break;
case R_SPARC_TLS_LDO_HIX22:
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 31, 10);
@@ -435,8 +497,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_SPARC_SIZE32:
*(ub32 *)loc = sym.esym().st_size + A;
break;
case R_SPARC_TLS_GD_ADD:
case R_SPARC_TLS_LDM_ADD:
case R_SPARC_64:
case R_SPARC_UA64:
case R_SPARC_TLS_LDO_ADD:
case R_SPARC_TLS_IE_LD:
case R_SPARC_TLS_IE_LDX:
@@ -454,24 +516,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -479,6 +529,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_SPARC_64:
case R_SPARC_UA64:
@@ -488,12 +542,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
*(ub64 *)loc = S + A;
break;
case R_SPARC_32:
case R_SPARC_UA32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_SPARC_UA32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_SPARC_TLS_DTPOFF32:
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
@@ -509,30 +561,21 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
bool needs_tlsgd = false;
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_SPARC_64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_SPARC_8:
case R_SPARC_5:
case R_SPARC_6:
@@ -546,7 +589,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SPARC_REGISTER:
case R_SPARC_UA16:
case R_SPARC_UA32:
case R_SPARC_UA64:
case R_SPARC_PC_HM10:
case R_SPARC_OLO10:
case R_SPARC_LOX10:
@@ -571,17 +613,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SPARC_PCPLT10:
case R_SPARC_PLT64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
sym.flags |= NEEDS_PLT;
break;
case R_SPARC_GOT13:
case R_SPARC_GOT10:
case R_SPARC_GOT22:
case R_SPARC_GOTDATA_HIX22:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_SPARC_GOTDATA_OP_HIX22:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
sym.flags |= NEEDS_GOT;
break;
case R_SPARC_DISP16:
case R_SPARC_DISP32:
@@ -597,26 +639,42 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_SPARC_TLS_GD_HI22:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
} else {
sym.flags |= NEEDS_TLSGD;
needs_tlsgd = true;
}
break;
case R_SPARC_TLS_LDM_HI22:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
} else {
ctx.needs_tlsld = true;
}
break;
case R_SPARC_TLS_IE_HI22:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
sym.flags |= NEEDS_GOTTP;
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
ctx.tls_get_addr->flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
check_tlsle(ctx, sym, rel);
break;
case R_SPARC_64:
case R_SPARC_UA64:
case R_SPARC_GOTDATA_OP_LOX10:
case R_SPARC_GOTDATA_OP:
case R_SPARC_GOTDATA_LOX10:
case R_SPARC_TLS_GD_LO10:
case R_SPARC_TLS_GD_ADD:
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_LO10:
case R_SPARC_TLS_LDM_ADD:
case R_SPARC_TLS_LDM_CALL:
case R_SPARC_TLS_LDO_HIX22:
case R_SPARC_TLS_LDO_LOX10:
case R_SPARC_TLS_LDO_ADD:
@@ -624,35 +682,20 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SPARC_TLS_IE_LD:
case R_SPARC_TLS_IE_LDX:
case R_SPARC_TLS_IE_LO10:
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
case R_SPARC_SIZE32:
break;
default:
Fatal(ctx) << *this << ": scan_relocations: " << rel;
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
// TLS_GD_CALL and TLS_LDM_CALL relocations implicitly refer to
// __tls_get_addr, which may be dynamically linked from libc.so.
Symbol<E> &sym = *ctx.extra.tls_get_addr;
if (sym.is_imported && (needs_tlsgd || ctx.needs_tlsld))
sym.flags |= NEEDS_PLT;
}
// __tls_get_addr is not defined by libc.a, so we can't use that function
// in statically-linked executables. This section provides a replacement.
void SparcTlsGetAddrSection::copy_buf(Context<E> &ctx) {
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
} // namespace mold
static const ub32 insn[] = {
0x0300'0000, // sethi %hi(TP_SIZE), %g1
0x8210'6000, // or %g1, %lo(TP_SIZE), %g1
0x8221'c001, // sub %g7, %g1, %g1
0xd05a'2008, // ldx [ %o0 + 8 ], %o0
0x81c3'e008, // retl
0x9000'4008, // add %g1, %o0, %o0
};
assert(this->shdr.sh_size == sizeof(insn));
memcpy(buf, insn, sizeof(insn));
buf[0] |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
buf[1] |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
}
} // namespace mold::elf
#endif
+955
View File
@@ -0,0 +1,955 @@
// Supporting x86-64 is straightforward. Unlike its predecessor, i386,
// x86-64 supports PC-relative addressing for position-independent code.
// Being CISC, its instructions are variable in size. Branch instructions
// take 4 bytes offsets, so we don't need range extension thunks.
//
// The psABI specifies %r11 as neither caller- nor callee-saved. It's
// intentionally left out so that we can use it as a scratch register in
// PLT.
//
// Thread Pointer (TP) is stored not to a general-purpose register but to
// FS segment register. Segment register is a 64-bits register which can
// be used as a base address for memory access. Each thread has a unique
// FS value, and they access their thread-local variables relative to FS
// as %fs:offset_from_tp.
//
// The value of a segment register itself is not generally readable from
// the user space. As a workaround, libc initializes %fs:0 (the first word
// referenced by FS) to the value of %fs itself. So we can obtain TP just
// by `mov %fs:0, %rax` if we need it.
//
// For historical reasons, TP points past the end of the TLS block on x86.
// This is contrary to other psABIs which usually use the beginning of the
// TLS block as TP (with some addend). As a result, offsets from TP to
// thread-local variables (TLVs) in the main executable are all negative.
//
// https://gitlab.com/x86-psABIs/x86-64-ABI
#if MOLD_X86_64
#include "mold.h"
#include <tbb/parallel_for_each.h>
namespace mold {
using E = X86_64;
// This is a security-enhanced version of the regular PLT. The PLT
// header and each PLT entry starts with endbr64 for the Intel's
// control-flow enforcement security mechanism.
//
// Note that our IBT-enabled PLT instruction sequence is different
// from the one used in GNU ld. GNU's IBTPLT implementation uses two
// separate sections (.plt and .plt.sec) in which one PLT entry takes
// 32 bytes in total. Our IBTPLT consists of just .plt and each entry
// is 16 bytes long.
//
// Our PLT entry clobbers %r11, but that's fine because the resolver
// function (_dl_runtime_resolve) clobbers %r11 anyway.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0x41, 0x53, // push %r11
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
// 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;
} 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[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 6;
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_X86_64_32:
*(ul32 *)loc = val;
break;
case R_X86_64_64:
*(ul64 *)loc = val;
break;
case R_X86_64_PC32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_X86_64_PC64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static u32 relax_gotpcrelx(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTPCRELX) {
switch ((loc[-2] << 8) | loc[-1]) {
case 0xff15: return 0x40e8; // call *0(%rip) -> call 0
case 0xff25: return 0x40e9; // jmp *0(%rip) -> jmp 0
}
} else {
assert(rel.r_type == R_X86_64_REX_GOTPCRELX ||
rel.r_type == R_X86_64_CODE_4_GOTPCRELX);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488b05: return 0x8d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
case 0x488b0d: return 0x8d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
case 0x488b15: return 0x8d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
case 0x488b1d: return 0x8d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
case 0x488b25: return 0x8d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
case 0x488b2d: return 0x8d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
case 0x488b35: return 0x8d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
case 0x488b3d: return 0x8d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
case 0x4c8b05: return 0x8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
case 0x4c8b0d: return 0x8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
case 0x4c8b15: return 0x8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
case 0x4c8b1d: return 0x8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
case 0x4c8b25: return 0x8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
case 0x4c8b2d: return 0x8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
case 0x4c8b35: return 0x8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
case 0x4c8b3d: return 0x8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
}
}
return 0;
}
static u32 relax_gottpoff(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTTPOFF) {
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
}
} else {
assert(rel.r_type == R_X86_64_CODE_4_GOTTPOFF);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488b05: return 0x18c7c0; // mov 0(%rip), %r16 -> mov $0, %r16
case 0x488b0d: return 0x18c7c1; // mov 0(%rip), %r17 -> mov $0, %r17
case 0x488b15: return 0x18c7c2; // mov 0(%rip), %r18 -> mov $0, %r18
case 0x488b1d: return 0x18c7c3; // mov 0(%rip), %r19 -> mov $0, %r19
case 0x488b25: return 0x18c7c4; // mov 0(%rip), %r20 -> mov $0, %r20
case 0x488b2d: return 0x18c7c5; // mov 0(%rip), %r21 -> mov $0, %r21
case 0x488b35: return 0x18c7c6; // mov 0(%rip), %r22 -> mov $0, %r22
case 0x488b3d: return 0x18c7c7; // mov 0(%rip), %r23 -> mov $0, %r23
case 0x4c8b05: return 0x19c7c0; // mov 0(%rip), %r24 -> mov $0, %r24
case 0x4c8b0d: return 0x19c7c1; // mov 0(%rip), %r25 -> mov $0, %r25
case 0x4c8b15: return 0x19c7c2; // mov 0(%rip), %r26 -> mov $0, %r26
case 0x4c8b1d: return 0x19c7c3; // mov 0(%rip), %r27 -> mov $0, %r27
case 0x4c8b25: return 0x19c7c4; // mov 0(%rip), %r28 -> mov $0, %r28
case 0x4c8b2d: return 0x19c7c5; // mov 0(%rip), %r29 -> mov $0, %r29
case 0x4c8b35: return 0x19c7c6; // mov 0(%rip), %r30 -> mov $0, %r30
case 0x4c8b3d: return 0x19c7c7; // mov 0(%rip), %r31 -> mov $0, %r31
}
}
return 0;
}
static u32 relax_tlsdesc_to_ie(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTPC32_TLSDESC) {
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488d05: return 0x488b05; // lea 0(%rip), %rax -> mov 0(%rip), %rax
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %rcx -> mov 0(%rip), %rcx
case 0x488d15: return 0x488b15; // lea 0(%rip), %rdx -> mov 0(%rip), %rdx
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %rbx -> mov 0(%rip), %rbx
case 0x488d25: return 0x488b25; // lea 0(%rip), %rsp -> mov 0(%rip), %rsp
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %rbp -> mov 0(%rip), %rbp
case 0x488d35: return 0x488b35; // lea 0(%rip), %rsi -> mov 0(%rip), %rsi
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %rdi -> mov 0(%rip), %rdi
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r8 -> mov 0(%rip), %r8
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r9 -> mov 0(%rip), %r9
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r10 -> mov 0(%rip), %r10
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r11 -> mov 0(%rip), %r11
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r12 -> mov 0(%rip), %r12
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r13 -> mov 0(%rip), %r13
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r14 -> mov 0(%rip), %r14
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r15 -> mov 0(%rip), %r15
}
} else {
assert(rel.r_type == R_X86_64_CODE_4_GOTPC32_TLSDESC);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488d05: return 0x488b05; // lea 0(%rip), %r16 -> mov 0(%rip), %r16
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %r17 -> mov 0(%rip), %r17
case 0x488d15: return 0x488b15; // lea 0(%rip), %r18 -> mov 0(%rip), %r18
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %r19 -> mov 0(%rip), %r19
case 0x488d25: return 0x488b25; // lea 0(%rip), %r20 -> mov 0(%rip), %r20
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %r21 -> mov 0(%rip), %r21
case 0x488d35: return 0x488b35; // lea 0(%rip), %r22 -> mov 0(%rip), %r22
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %r23 -> mov 0(%rip), %r23
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r24 -> mov 0(%rip), %r24
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r25 -> mov 0(%rip), %r25
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r26 -> mov 0(%rip), %r26
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r27 -> mov 0(%rip), %r27
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r28 -> mov 0(%rip), %r28
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r29 -> mov 0(%rip), %r29
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r30 -> mov 0(%rip), %r30
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r31 -> mov 0(%rip), %r31
}
}
return 0;
}
static u32 relax_tlsdesc_to_le(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTPC32_TLSDESC) {
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
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
}
} else {
assert(rel.r_type == R_X86_64_CODE_4_GOTPC32_TLSDESC);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488d05: return 0x18c7c0; // lea 0(%rip), %r16 -> mov $0, %r16
case 0x488d0d: return 0x18c7c1; // lea 0(%rip), %r17 -> mov $0, %r17
case 0x488d15: return 0x18c7c2; // lea 0(%rip), %r18 -> mov $0, %r18
case 0x488d1d: return 0x18c7c3; // lea 0(%rip), %r19 -> mov $0, %r19
case 0x488d25: return 0x18c7c4; // lea 0(%rip), %r20 -> mov $0, %r20
case 0x488d2d: return 0x18c7c5; // lea 0(%rip), %r21 -> mov $0, %r21
case 0x488d35: return 0x18c7c6; // lea 0(%rip), %r22 -> mov $0, %r22
case 0x488d3d: return 0x18c7c7; // lea 0(%rip), %r23 -> mov $0, %r23
case 0x4c8d05: return 0x19c7c0; // lea 0(%rip), %r24 -> mov $0, %r24
case 0x4c8d0d: return 0x19c7c1; // lea 0(%rip), %r25 -> mov $0, %r25
case 0x4c8d15: return 0x19c7c2; // lea 0(%rip), %r26 -> mov $0, %r26
case 0x4c8d1d: return 0x19c7c3; // lea 0(%rip), %r27 -> mov $0, %r27
case 0x4c8d25: return 0x19c7c4; // lea 0(%rip), %r28 -> mov $0, %r28
case 0x4c8d2d: return 0x19c7c5; // lea 0(%rip), %r29 -> mov $0, %r29
case 0x4c8d35: return 0x19c7c6; // lea 0(%rip), %r30 -> mov $0, %r30
case 0x4c8d3d: return 0x19c7c7; // lea 0(%rip), %r31 -> mov $0, %r31
}
}
return 0;
}
// Rewrite a function call to __tls_get_addr to a cheaper instruction
// sequence. We can do this when we know the thread-local variable's TP-
// relative address at link-time.
static void relax_gd_to_le(u8 *loc, const ElfRel<E> &rel, u64 val) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
//
// or
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
};
memcpy(loc - 4, insn, sizeof(insn));
*(ul32 *)(loc + 8) = val;
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 9) = val;
break;
}
default:
unreachable();
}
}
static void relax_gd_to_ie(u8 *loc, const ElfRel<E> &rel, u64 val) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
};
memcpy(loc - 4, insn, sizeof(insn));
*(ul32 *)(loc + 8) = val - 12;
break;
}
case R_X86_64_PLTOFF64: {
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 9) = val - 13;
break;
}
default:
unreachable();
}
}
// Rewrite a function call to __tls_get_addr to a cheaper instruction
// sequence. The difference from relax_gd_to_le is that we are materializing
// the address of the beginning of TLS block instead of an address of a
// particular thread-local variable.
static void relax_ld_to_le(u8 *loc, const ElfRel<E> &rel, i64 tls_size) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// e8 00 00 00 00 call __tls_get_addr
//
// Because the original instruction sequence is so short that we need a
// little bit of code golfing here. "mov %fs:0, %rax" is 9 byte long, so
// xor + mov is shorter. Note that `xor %eax, %eax` zero-clears %eax.
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = tls_size;
break;
}
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x48, 0x31, 0xc0, // xor %rax, %rax
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 + 6) = tls_size;
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 8) = tls_size;
break;
}
default:
unreachable();
}
}
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
// mapped to memory at runtime) based on the result of
// scan_relocations().
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
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 GOT = ctx.gotplt->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto write32 = [&](u64 val) {
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
};
auto write32s = [&](u64 val) {
check(val, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val;
};
switch (rel.r_type) {
case R_X86_64_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
case R_X86_64_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
case R_X86_64_32:
write32(S + A);
break;
case R_X86_64_32S:
write32s(S + A);
break;
case R_X86_64_64:
break;
case R_X86_64_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
break;
case R_X86_64_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A - P;
break;
case R_X86_64_PC32:
case R_X86_64_PLT32:
write32s(S + A - P);
break;
case R_X86_64_PC64:
*(ul64 *)loc = S + A - P;
break;
case R_X86_64_GOT32:
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 - GOT;
break;
case R_X86_64_GOTPC32:
write32s(GOT + A - P);
break;
case R_X86_64_GOTPC64:
*(ul64 *)loc = GOT + A - P;
break;
case R_X86_64_GOTPCREL:
write32s(G + GOT + A - P);
break;
case R_X86_64_GOTPCREL64:
*(ul64 *)loc = G + GOT + A - P;
break;
case R_X86_64_GOTPCRELX:
case R_X86_64_REX_GOTPCRELX:
case R_X86_64_CODE_4_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_pcrel_linktime_const(ctx) && is_int(S + A - P, 32)) {
if (u32 insn = relax_gotpcrelx(loc, rel)) {
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = S + A - P;
break;
}
}
write32s(G + GOT + A - P);
break;
case R_X86_64_TLSGD:
if (sym.has_tlsgd(ctx))
write32s(sym.get_tlsgd_addr(ctx) + A - P);
else if (sym.has_gottp(ctx))
relax_gd_to_ie(loc, rels[++i], 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))
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
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);
break;
case R_X86_64_DTPOFF64:
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_X86_64_TPOFF32:
write32s(S + A - ctx.tp_addr);
break;
case R_X86_64_TPOFF64:
*(ul64 *)loc = S + A - ctx.tp_addr;
break;
case R_X86_64_GOTTPOFF:
case R_X86_64_CODE_4_GOTTPOFF:
if (sym.has_gottp(ctx)) {
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_gottpoff(loc, rel);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S - ctx.tp_addr);
}
break;
case R_X86_64_CODE_6_GOTTPOFF:
write32s(sym.get_gottp_addr(ctx) + A - P);
break;
case R_X86_64_GOTPC32_TLSDESC:
case R_X86_64_CODE_4_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 if its TP-relative
// address is known at link-time
//
// mov $foo@TPOFF, %rax
// nop
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// mov foo@GOTTPOFF(%rip), %rax
// nop
//
// We allow the following alternative code sequence too because
// LLVM emits such code.
//
// lea 0(%rip), %reg
// R_X86_64_GOTPC32_TLSDESC foo
// mov %reg, %rax
// call *(%rax)
// R_X86_64_TLSDESC_CALL foo
if (sym.has_tlsdesc(ctx)) {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
} else if (sym.has_gottp(ctx)) {
u32 insn = relax_tlsdesc_to_ie(loc, rel);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for " << rel;
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_tlsdesc_to_le(loc, rel);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for " << rel;
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S - ctx.tp_addr);
}
break;
case R_X86_64_TLSDESC_CALL:
if (!sym.has_tlsdesc(ctx)) {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
break;
case R_X86_64_SIZE64:
*(ul64 *)loc = sym.esym().st_size + A;
break;
default:
unreachable();
}
}
}
// This function is responsible for applying relocations against
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
// at runtime).
//
// Relocations against non-SHF_ALLOC sections are much easier to
// handle than that against SHF_ALLOC sections. It is because, since
// they are not mapped to memory, they don't contain any variable or
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
// mostly debug info sections.
//
// Relocations against non-SHF_ALLOC sections are not scanned by
// scan_relocations.
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || 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 : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto write32 = [&](u64 val) {
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
};
auto write32s = [&](u64 val) {
check(val, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val;
};
switch (rel.r_type) {
case R_X86_64_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
case R_X86_64_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
case R_X86_64_32:
write32(S + A);
break;
case R_X86_64_32S:
write32s(S + A);
break;
case R_X86_64_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
case R_X86_64_DTPOFF32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
write32s(S + A - ctx.dtp_addr);
break;
case R_X86_64_DTPOFF64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_X86_64_GOTOFF64:
*(ul64 *)loc = S + A - ctx.gotplt->shdr.sh_addr;
break;
case R_X86_64_GOTPC64:
// PC-relative relocation doesn't make sense for non-memory-allocated
// section, but GCC 6.3.0 seems to create this reloc for
// _GLOBAL_OFFSET_TABLE_.
*(ul64 *)loc = ctx.gotplt->shdr.sh_addr + A;
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
break;
case R_X86_64_SIZE64:
*(ul64 *)loc = sym.esym().st_size + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
}
// Linker has to create data structures in an output file to apply
// some type of relocations. For example, if a relocation refers a GOT
// or a PLT entry of a symbol, linker has to create an entry in .got
// or in .plt for that symbol. In order to fix the file layout, we
// need to scan relocations.
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
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:
case R_X86_64_32:
case R_X86_64_32S:
scan_absrel(ctx, sym, rel);
break;
case R_X86_64_PC8:
case R_X86_64_PC16:
case R_X86_64_PC32:
case R_X86_64_PC64:
scan_pcrel(ctx, sym, rel);
break;
case R_X86_64_GOT32:
case R_X86_64_GOT64:
case R_X86_64_GOTPC32:
case R_X86_64_GOTPC64:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCREL64:
case R_X86_64_GOTPCRELX:
case R_X86_64_REX_GOTPCRELX:
case R_X86_64_CODE_4_GOTPCRELX:
sym.flags |= NEEDS_GOT;
break;
case R_X86_64_PLT32:
case R_X86_64_PLTOFF64:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_X86_64_TLSGD:
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
i++;
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
i++;
} else {
sym.flags |= NEEDS_TLSGD;
}
break;
case R_X86_64_TLSLD:
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
i++;
else
ctx.needs_tlsld = true;
break;
case R_X86_64_GOTTPOFF:
case R_X86_64_CODE_4_GOTTPOFF:
if (!ctx.arg.relax || !sym.is_tprel_linktime_const(ctx) ||
!relax_gottpoff(loc, rel))
sym.flags |= NEEDS_GOTTP;
break;
case R_X86_64_CODE_6_GOTTPOFF:
sym.flags |= NEEDS_GOTTP;
break;
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);
break;
case R_X86_64_64:
case R_X86_64_GOTOFF64:
case R_X86_64_DTPOFF32:
case R_X86_64_DTPOFF64:
case R_X86_64_SIZE32:
case R_X86_64_SIZE64:
case R_X86_64_GOTPC32_TLSDESC:
case R_X86_64_CODE_4_GOTPC32_TLSDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
// Intel CET is a relatively new CPU feature to enhance security by
// protecting control flow integrity. If the feature is enabled, indirect
// branches (i.e. branch instructions that take a register instead of an
// immediate) must land on a "landing pad" instruction, or a CPU-level fault
// will raise. That prevents an attacker to branch to a middle of a random
// function, making ROP or JOP much harder to conduct.
//
// On x86-64, the landing pad instruction is ENDBR64. That is actually a
// repurposed NOP instruction to provide binary compatibility with older
// hardware that doesn't support CET.
//
// The problem here is that the compiler always emits a landing pad at the
// beginning fo a global function because it doesn't know whether or not the
// function's address is taken in other translation units. As a result, the
// resulting binary contains more landing pads than necessary.
//
// This function rewrites a landing pad with a nop if the function's address
// was not actually taken. We can do what the compiler cannot because we
// know about all translation units.
void rewrite_endbr(Context<E> &ctx) {
Timer t(ctx, "rewrite_endbr");
constexpr u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
constexpr u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
// Rewrite all endbr64 instructions referred to by function symbols with
// NOPs. We handle only global symbols because the compiler doesn't emit
// an endbr64 for a file-scoped function in the first place if its address
// is not taken within the file.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file == file && sym->esym().st_type == STT_FUNC) {
if (InputSection<E> *isec = sym->get_input_section();
isec && (isec->shdr().sh_flags & SHF_EXECINSTR)) {
if (OutputSection<E> *osec = isec->output_section) {
u8 *buf = ctx.buf + osec->shdr.sh_offset + isec->offset + sym->value;
if (memcmp(buf, endbr64, 4) == 0)
memcpy(buf, nop, 4);
}
}
}
}
});
auto write_back = [&](InputSection<E> *isec, i64 offset) {
// If isec has an endbr64 at a given offset, copy that instruction to
// the output buffer, possibly overwriting a nop written in the above
// loop.
if (isec && isec->output_section &&
(isec->shdr().sh_flags & SHF_EXECINSTR) &&
0 <= offset && offset <= isec->contents.size() - 4 &&
memcmp(isec->contents.data() + offset, endbr64, 4) == 0)
memcpy(ctx.buf + isec->output_section->shdr.sh_offset + isec->offset + offset,
endbr64, 4);
};
// Write back endbr64 instructions if they are referred to by address-taking
// relocations.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC)) {
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
if (!is_func_call_rel(rel)) {
Symbol<E> *sym = file->symbols[rel.r_sym];
if (sym->esym().st_type == STT_SECTION)
write_back(sym->get_input_section(), rel.r_addend);
else
write_back(sym->get_input_section(), sym->value);
}
}
}
}
});
// We record addresses of some symbols in the ELF header, .dynamic or in
// .dynsym. We need to retain endbr64s for such symbols.
auto keep = [&](Symbol<E> *sym) {
if (sym)
write_back(sym->get_input_section(), sym->value);
};
keep(ctx.arg.entry);
keep(ctx.arg.init);
keep(ctx.arg.fini);
if (ctx.dynsym)
for (Symbol<E> *sym : ctx.dynsym->symbols)
if (sym && sym->is_exported)
keep(sym);
}
} // namespace mold
#endif
+32 -23
View File
@@ -23,13 +23,11 @@
// see the contents of libc.a by running `ar t
// /usr/lib/x86_64-linux-gnu/libc.a`.
#pragma once
#include "mold.h"
#include "filetype.h"
namespace mold {
namespace {
struct ArHdr {
char ar_name[16];
char ar_date[12];
@@ -75,13 +73,14 @@ struct ArHdr {
return {ar_name, sizeof(ar_name)};
}
};
}
template <typename C>
std::vector<MappedFile<C> *>
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
template <typename E>
std::vector<MappedFile *>
read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile<C> *> vec;
std::vector<MappedFile *> vec;
std::string_view strtab;
while (data < begin + mf->size) {
@@ -116,20 +115,20 @@ read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
continue;
std::string path = name.starts_with('/') ?
name : (filepath(mf->name).parent_path() / name).string();
vec.push_back(MappedFile<C>::must_open(ctx, path));
name : (path_dirname(mf->name) / name).string();
vec.push_back(must_open_file(ctx, path));
vec.back()->thin_parent = mf;
data = body;
}
return vec;
}
template <typename C>
std::vector<MappedFile<C> *>
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
template <typename E>
std::vector<MappedFile *>
read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile<C> *> vec;
std::vector<MappedFile *> vec;
std::string_view strtab;
while (begin + mf->size - data >= 2) {
@@ -163,17 +162,27 @@ read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
return vec;
}
template <typename C>
std::vector<MappedFile<C> *>
read_archive_members(C &ctx, MappedFile<C> *mf) {
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
case FileType::AR:
template <typename E>
std::vector<MappedFile *>
read_archive_members(Context<E> &ctx, MappedFile *mf) {
std::string_view str = mf->get_contents();
if (str.starts_with("!<arch>\n"))
return read_fat_archive_members(ctx, mf);
case FileType::THIN_AR:
return read_thin_archive_members(ctx, mf);
default:
unreachable();
}
assert(str.starts_with("!<thin>\n"));
return read_thin_archive_members(ctx, mf);
}
using E = MOLD_TARGET;
template std::vector<MappedFile *>
read_thin_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_fat_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_archive_members(Context<E> &, MappedFile *);
} // namespace mold
File diff suppressed because it is too large. Load diff
+1048
View File
File diff suppressed because it is too large. Load diff
+531 -271
View File
File diff suppressed because it is too large. Load diff
+32
View File
@@ -0,0 +1,32 @@
#include "config.h"
#include "mold.h"
#include "mold-git-hash.h"
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
#endif
#if MOLD_USE_SYSTEM_MIMALLOC
// Including mimalloc-new-delete.h overrides the new/delete operators.
// We need it only when using mimalloc as a dynamic library.
// This header should be included in only one source file, so we do
// it in this file.
# include <mimalloc-new-delete.h>
#endif
std::string mold::mold_version =
#ifdef MOLD_GIT_HASH
"mold " MOLD_VERSION " (" MOLD_GIT_HASH "; compatible with GNU ld)";
#else
"mold " MOLD_VERSION " (compatible with GNU ld)";
#endif
int main(int argc, char **argv) {
#if MOLD_USE_MIMALLOC
// Silence mimalloc warnings that users can ignore
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
#endif
return mold::mold_main<mold::MOLD_FIRST_TARGET>(argc, argv);
}
+55
View File
@@ -0,0 +1,55 @@
#include "mold.h"
namespace mold {
static std::string_view fatal_mono = "mold: fatal: ";
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
static std::string_view error_mono = "mold: error: ";
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
static std::string_view warning_mono = "mold: warning: ";
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
template <typename E>
Fatal<E>::Fatal(Context<E> &ctx) {
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
}
template <typename E>
[[noreturn]] Fatal<E>::~Fatal() {
out.emit();
cleanup();
_exit(1);
}
template <typename E>
Error<E>::Error(Context<E> &ctx) {
if (ctx.arg.noinhibit_exec) {
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
} else {
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
}
}
template <typename E>
Warn<E>::Warn(Context<E> &ctx) {
if (ctx.arg.suppress_warnings)
return;
out.emplace(std::cerr);
if (ctx.arg.fatal_warnings) {
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
} else {
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
}
}
using E = MOLD_TARGET;
template class Fatal<E>;
template class Error<E>;
template class Warn<E>;
} // namespace mold
+207
View File
@@ -0,0 +1,207 @@
#include "mold.h"
namespace mold {
static bool is_text_file(MappedFile *mf) {
auto istext = [](char c) {
return isprint(c) || c == '\n' || c == '\t';
};
u8 *data = mf->data;
return mf->size >= 4 && istext(data[0]) && istext(data[1]) &&
istext(data[2]) && istext(data[3]);
}
template <typename E>
static bool is_gcc_lto_obj(MappedFile *mf, bool has_gcc_plugin) {
const char *data = mf->get_contents().data();
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(data + ehdr.e_shoff);
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
// e_shstrndx is a 16-bit field. If .shstrtab's section index is
// too large, the actual number is stored to sh_link field.
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
? sh_begin->sh_link : ehdr.e_shstrndx;
for (ElfShdr<E> &sec : shdrs) {
// GCC FAT LTO objects contain both regular ELF sections and GCC-
// specific LTO sections, so that they can be linked as LTO objects if
// the LTO linker plugin is available and falls back as regular
// objects otherwise. GCC FAT LTO object can be identified by the
// presence of `.gcc.lto_.symtab` section.
if (has_gcc_plugin) {
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
if (name.starts_with(".gnu.lto_.symtab."))
return true;
}
if (sec.sh_type != SHT_SYMTAB)
continue;
// GCC non-FAT LTO object contains only sections symbols followed by
// a common symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1`
// for older GCC releases).
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
auto skip = [](u8 type) {
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
};
i64 i = 1;
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
i++;
if (i < elf_syms.size() && elf_syms[i].st_shndx == SHN_COMMON) {
std::string_view name =
data + shdrs[sec.sh_link].sh_offset + elf_syms[i].st_name;
if (name.starts_with("__gnu_lto_"))
return true;
}
break;
}
return false;
}
template <typename E>
FileType get_file_type(Context<E> &ctx, MappedFile *mf) {
std::string_view data = mf->get_contents();
if (data.empty())
return FileType::EMPTY;
// GCC FAT LTO objects can be linked as regular ELF objects. If the active
// plugin is LLVM's, treat them as regular objects so that we can fall back
// to native code instead of routing them through GCC LTO handling.
bool has_gcc_plugin = !ctx.arg.plugin.empty() &&
ctx.arg.plugin.find("LLVMgold.") == std::string_view::npos;
if (data.starts_with("\177ELF")) {
u8 byte_order = ((ElfEhdr<I386> *)data.data())->e_ident[EI_DATA];
if (byte_order == ELFDATA2LSB) {
auto &ehdr = *(ElfEhdr<I386> *)data.data();
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<I386>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<X86_64>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
}
if (ehdr.e_type == ET_DYN)
return FileType::ELF_DSO;
} else {
auto &ehdr = *(ElfEhdr<M68K> *)data.data();
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<M68K>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<SPARC64>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
}
if (ehdr.e_type == ET_DYN)
return FileType::ELF_DSO;
}
return FileType::UNKNOWN;
}
if (data.starts_with("!<arch>\n"))
return FileType::AR;
if (data.starts_with("!<thin>\n"))
return FileType::THIN_AR;
if (is_text_file(mf))
return FileType::TEXT;
if (data.starts_with("\xde\xc0\x17\x0b"))
return FileType::LLVM_BITCODE;
if (data.starts_with("BC\xc0\xde"))
return FileType::LLVM_BITCODE;
return FileType::UNKNOWN;
}
static std::string_view get_elf_type(u8 *buf) {
bool is_le = (buf[EI_DATA] == ELFDATA2LSB);
bool is_64 = (buf[EI_CLASS] == ELFCLASS64);
auto *ehdr_le = (ElfEhdr<I386> *)buf;
auto *ehdr_be = (ElfEhdr<M68K> *)buf;
switch (is_le ? ehdr_le->e_machine : ehdr_be->e_machine) {
case EM_386:
return I386::name;
case EM_X86_64:
return X86_64::name;
case EM_ARM:
return is_le ? ARM32LE::name : ARM32BE::name;
case EM_AARCH64:
return is_le ? ARM64LE::name : ARM64BE::name;
case EM_RISCV:
if (is_le)
return is_64 ? RV64LE::name : RV32LE::name;
return is_64 ? RV64BE::name : RV32BE::name;
case EM_PPC:
return PPC32::name;
case EM_PPC64:
return is_le ? PPC64V2::name : PPC64V1::name;
case EM_S390X:
return S390X::name;
case EM_SPARC64:
return SPARC64::name;
case EM_68K:
return M68K::name;
case EM_SH:
return is_le ? SH4LE::name : SH4BE::name;
case EM_LOONGARCH:
return is_64 ? LOONGARCH64::name : LOONGARCH32::name;
default:
return "";
}
}
// Read the beginning of a given file and returns its machine type
// (e.g. EM_X86_64 or EM_386).
template <typename E>
std::string_view
get_machine_type(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
case FileType::ELF_DSO:
case FileType::GCC_LTO_OBJ:
return get_elf_type(mf->data);
case FileType::AR:
for (MappedFile *child : read_fat_archive_members(ctx, mf))
if (FileType ty = get_file_type(ctx, child);
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
return get_elf_type(child->data);
return "";
case FileType::THIN_AR:
for (MappedFile *child : read_thin_archive_members(ctx, mf))
if (FileType ty = get_file_type(ctx, child);
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
return get_elf_type(child->data);
return "";
case FileType::TEXT:
return Script(ctx, mf, &rctx).get_script_output_type();
default:
return "";
}
}
using E = MOLD_TARGET;
template FileType get_file_type(Context<E> &, MappedFile *);
template std::string_view
get_machine_type(Context<E> &, ReaderContext &, MappedFile *);
} // namespace mold
+249
View File
@@ -0,0 +1,249 @@
// This file implements a mark-sweep garbage collector for -gc-sections.
// In this algorithm, vertices are sections and edges are relocations.
// Any section that is reachable from a root section is considered alive.
#include "mold.h"
#include <fstream>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for_each.h>
namespace mold {
template <typename E>
static bool should_keep(Context<E> &ctx, const InputSection<E> &isec) {
u32 type = isec.shdr().sh_type;
u32 flags = isec.shdr().sh_flags;
if constexpr (is_ppc32<E>)
if (isec.name == ".got2")
return true;
// KEEP'ed by a linker script
if (!isec.file.script_match.empty()) {
i32 rank = isec.file.script_match[isec.shndx];
if (rank != -1 && ctx.script_ranks[rank].keep)
return true;
}
return (flags & SHF_GNU_RETAIN) ||
type == SHT_NOTE ||
type == SHT_INIT_ARRAY ||
type == SHT_FINI_ARRAY ||
type == SHT_PREINIT_ARRAY ||
isec.name.starts_with(".ctors") ||
isec.name.starts_with(".dtors") ||
isec.name.starts_with(".init") ||
isec.name.starts_with(".fini");
}
// Sections whose names are valid C identifiers can be referenced via
// __start_<name>/__stop_<name> symbols, which the linker synthesizes.
// Such sections must be kept alive only if such a marker symbol is
// referenced from a live section. This map lets us find all sections
// of a given name when we encounter such a reference during marking.
template <typename E>
using StartStopMap =
tbb::concurrent_unordered_multimap<std::string_view, InputSection<E> *>;
template <typename E>
static StartStopMap<E> build_start_stop_map(Context<E> &ctx) {
StartStopMap<E> map;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
is_c_identifier(isec->name))
map.insert({isec->name, isec.get()});
});
return map;
}
template <typename E>
static bool mark_section(InputSection<E> *isec) {
return isec && isec->is_alive && !isec->is_visited.test_and_set();
}
template <typename E>
static tbb::concurrent_vector<InputSection<E> *>
collect_root_set(Context<E> &ctx) {
Timer t(ctx, "collect_root_set");
tbb::concurrent_vector<InputSection<E> *> rootset;
auto enqueue_section = [&](InputSection<E> *isec) {
if (mark_section(isec))
rootset.push_back(isec);
};
auto enqueue_symbol = [&](Symbol<E> *sym) {
if (sym) {
if (SectionFragment<E> *frag = sym->get_frag())
frag->is_alive = true;
else
enqueue_section(sym->get_input_section());
}
};
// Add sections that are not subject to garbage collection.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (!isec || !isec->is_alive)
continue;
// --gc-sections discards only SHF_ALLOC sections. If you want to
// reduce the amount of non-memory-mapped segments, you should
// use `strip` command, compile without debug info or use
// --strip-all linker option.
if (!(isec->shdr().sh_flags & SHF_ALLOC)) {
isec->is_visited = true;
continue;
}
if (should_keep(ctx, *isec))
enqueue_section(isec.get());
}
});
// Add sections containing gc root or exported symbols
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
if (sym->file == file && (sym->gc_root || sym->is_exported))
enqueue_symbol(sym);
});
// .eh_frame consists of variable-length records called CIE and FDE
// records, and they are a unit of inclusion or exclusion.
// We just keep all CIEs and everything that are referenced by them.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (CieRecord<E> &cie : file->cies)
for (const ElfRel<E> &rel : cie.get_rels())
enqueue_symbol(file->symbols[rel.r_sym]);
});
return rootset;
}
static std::string_view start_stop_name(std::string_view sym) {
if (sym.starts_with("__start_"))
return sym.substr(8);
if (sym.starts_with("__stop_"))
return sym.substr(7);
return "";
}
template <typename E>
static void visit_section(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth,
const StartStopMap<E> &start_stop_map) {
assert(isec->is_visited);
// Mark a section alive. For better performacne, we don't call
// `feeder.add` too often.
auto mark = [&](InputSection<E> *sec) {
if (mark_section(sec)) {
if (depth < 3)
visit_section(ctx, sec, feeder, depth + 1, start_stop_map);
else
feeder.add(sec);
}
};
// If this is a text section, .eh_frame may contain records
// describing how to handle exceptions for that function.
// We want to keep associated .eh_frame records.
for (FdeRecord<E> &fde : isec->get_fdes())
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
mark(sym->get_input_section());
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
// Symbol can refer to either a section fragment or an input section.
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
if (SectionFragment<E> *frag = sym.get_frag()) {
frag->is_alive = true;
continue;
}
mark(sym.get_input_section());
// A reference to __start_<name> or __stop_<name> keeps every section
// named <name> alive, mirroring how those symbols are defined.
if (std::string_view sec = start_stop_name(sym.name());
!sec.empty()) {
auto [i, end] = start_stop_map.equal_range(sec);
for (; i != end; ++i)
mark(i->second);
}
}
if constexpr (is_arm32<E>)
mark(isec->extra.exidx);
}
// Mark all reachable sections
template <typename E>
static void mark(Context<E> &ctx,
tbb::concurrent_vector<InputSection<E> *> &rootset,
const StartStopMap<E> &start_stop_map) {
Timer t(ctx, "mark");
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder) {
visit_section(ctx, isec, feeder, 0, start_stop_map);
});
}
// Remove unreachable sections
template <typename E>
static void sweep(Context<E> &ctx) {
Timer t(ctx, "sweep");
std::vector<std::vector<InputSection<E> *>> sections(ctx.objs.size());
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
ObjectFile<E> &file = *ctx.objs[i];
for (std::unique_ptr<InputSection<E>> &isec : file.sections) {
if (isec && isec->is_alive && !isec->is_visited) {
isec->kill();
sections[i].push_back(isec.get());
}
}
});
std::string &path = ctx.arg.print_gc_sections;
if (!path.empty()) {
std::ostream *out = &std::cout;
std::ofstream file;
if (path != "-") {
file.open(path);
if (file.fail())
Fatal(ctx) << "--print-gc-sections: cannot open " << path << ": "
<< errno_string();
out = &file;
}
i64 saved_bytes = 0;
for (std::span<InputSection<E> *> vec : sections)
for (InputSection<E> *isec : vec)
*out << "removing unused section " << *isec << '\n';
*out << "GC saved " << saved_bytes << " bytes\n";
}
}
template <typename E>
void gc_sections(Context<E> &ctx) {
Timer t(ctx, "gc");
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
StartStopMap<E> start_stop_map = build_start_stop_map(ctx);
mark(ctx, rootset, start_stop_map);
sweep(ctx);
}
using E = MOLD_TARGET;
template void gc_sections(Context<E> &ctx);
} // namespace mold
+808
View File
@@ -0,0 +1,808 @@
// 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>
namespace mold {
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 {
auto operator<=>(const NameType &) const = default;
u64 hash;
u8 type;
std::string_view name;
};
struct MapValue {
u32 gdb_hash = 0;
Atomic<u32> count;
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;
Offset *offsets = (Offset *)base;
read_rnglist_range<E>(vec, base + offsets[ranges.value], 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(NameType{hash_string(name), type, name});
}
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) {
ranges::stable_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) {
CompressedSection<E> &sec = *(CompressedSection<E> *)chunk;
return {sec.uncompressed_data.get(), (size_t)sec.chdr.ch_size};
}
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
}
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{gdb_hash(nt.name)});
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. We use malloc instead of vector to
// avoid zero-initializing the entire buffer.
ctx.output_file->buf2 = (u8 *)malloc(bufsize);
ctx.output_file->buf2_size = bufsize;
u8 *buf = ctx.output_file->buf2;
// 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
std::vector<i64> range_offsets(cus.size());
for (i64 i = 1; i < cus.size(); i++)
range_offsets[i] = range_offsets[i - 1] + cus[i - 1].ranges.size() * 20;
tbb::parallel_for_each(cus, [&](Compunit &cu) {
i64 i = &cu - cus.data();
u8 *p = buf + hdr.ranges_offset + range_offsets[i];
for (std::pair<u64, u64> range : cu.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);
memset(ht, 0, ht_size * 8);
for (Entry *ent : entries) {
u32 hash = ent->value.gdb_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. Use MapValue::count as an atomic slot counter.
u8 *base = buf + hdr.const_pool_offset;
for (Entry *ent : entries)
ent->value.count = 0;
tbb::parallel_for_each(cus, [&](Compunit &cu) {
i64 i = &cu - cus.data();
for (i64 j = 0; j < cu.nametypes.size(); j++) {
MapValue *ent = cu.entries[j];
ul32 *p = (ul32 *)(base + ent->type_offset);
i64 idx = ++ent->count;
p[idx] = (cu.nametypes[j].type << 24) | i;
}
});
// Write the final counts into the buffer.
tbb::parallel_for_each(entries, [&](Entry *ent) {
ul32 *p = (ul32 *)(base + ent->value.type_offset);
p[0] = ent->value.count;
// Sort entries for deterministic output
std::sort(p + 1, p + 1 + ent->value.count);
});
// Write names
tbb::parallel_for_each(entries, [&](Entry *ent) {
u8 *dst = buf + hdr.const_pool_offset + ent->value.name_offset;
memcpy(dst, ent->key, ent->keylen);
dst[ent->keylen] = '\0';
});
// 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
+105 -79
View File
@@ -65,10 +65,11 @@
// conditions.
#include "mold.h"
#include "../sha.h"
#include "../lib/siphash.h"
#include <array>
#include <cstdio>
#include <fstream>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/enumerable_thread_specific.h>
@@ -78,44 +79,43 @@
static constexpr int64_t HASH_SIZE = 16;
typedef std::array<uint8_t, HASH_SIZE> Digest;
using Digest = std::array<uint8_t, HASH_SIZE>;
namespace std {
template<> struct hash<Digest> {
template <> struct hash<Digest> {
size_t operator()(const Digest &k) const {
return *(int64_t *)&k[0];
static_assert(sizeof(size_t) <= HASH_SIZE);
size_t val;
memcpy(&val, k.data(), sizeof(size_t));
return val;
}
};
}
namespace mold::elf {
namespace mold {
class BitVector {
public:
BitVector(u32 size) : vec((size + 7) / 8) {}
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
private:
std::vector<u8> vec;
};
static u8 hmac_key[16];
template <typename E>
static void uniquify_cies(Context<E> &ctx) {
Timer t(ctx, "uniquify_cies");
std::vector<CieRecord<E> *> cies;
auto find = [&](CieRecord<E> &cie) -> i64 {
for (i64 i = 0; i < cies.size(); i++)
if (cie_equals(cie, *cies[i]))
return i;
return -1;
};
for (ObjectFile<E> *file : ctx.objs) {
for (CieRecord<E> &cie : file->cies) {
for (i64 i = 0; i < cies.size(); i++) {
if (cie.equals(*cies[i])) {
cie.icf_idx = i;
goto found;
}
if (i64 idx = find(cie); idx != -1) {
cie.icf_idx = idx;
} else {
cie.icf_idx = cies.size();
cies.push_back(&cie);
}
cie.icf_idx = cies.size();
cies.push_back(&cie);
found:;
}
}
}
@@ -123,32 +123,26 @@ static void uniquify_cies(Context<E> &ctx) {
template <typename E>
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(isec.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) &&
isec.name != ".init" && isec.name != ".fini";
static Digest digest_final(SHA256Hash &sha) {
u8 buf[SHA256_SIZE];
sha.finish(buf);
// .gcc_except_table contains a compiler-generated table. Pointer
// equality for the section is not significant because only the C++
// exception handling code will use the table at runtime.
if (isec.name == ".gcc_except_table" ||
isec.name.starts_with(".gcc_except_table."))
return true;
Digest digest;
memcpy(digest.data(), buf, HASH_SIZE);
return digest;
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
bool is_relro = isec.name.starts_with(".data.rel.ro");
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
(is_readonly || is_relro);
}
template <typename E>
@@ -205,7 +199,7 @@ static void merge_leaf_nodes(Context<E> &ctx) {
static Counter non_eligible("icf_non_eligibles");
static Counter leaf("icf_leaf_nodes");
tbb::concurrent_unordered_map<InputSection<E> *, InputSection<E> *,
tbb::concurrent_unordered_map<InputSection<E> *, Atomic<InputSection<E> *>,
LeafHasher<E>, LeafEq<E>> map;
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
@@ -222,8 +216,11 @@ static void merge_leaf_nodes(Context<E> &ctx) {
leaf++;
isec->icf_leaf = true;
auto [it, inserted] = map.insert({isec.get(), isec.get()});
if (!inserted && isec->get_priority() < it->second->get_priority())
it->second = isec.get();
if (!inserted) {
InputSection<E> *isec2 = it->second.load();
while (isec->get_priority() < isec2->get_priority() &&
!it->second.compare_exchange_strong(isec2, isec.get()));
}
} else {
eligible++;
isec->icf_eligible = true;
@@ -244,21 +241,21 @@ static void merge_leaf_nodes(Context<E> &ctx) {
template <typename E>
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
SHA256Hash sha;
SipHash13_128 hasher(hmac_key);
auto hash = [&](auto val) {
sha.update((u8 *)&val, sizeof(val));
hasher.update((u8 *)&val, sizeof(val));
};
auto hash_string = [&](std::string_view str) {
hash(str.size());
sha.update((u8 *)str.data(), str.size());
hasher.update((u8 *)str.data(), str.size());
};
auto hash_symbol = [&](Symbol<E> &sym) {
InputSection<E> *isec = sym.get_input_section();
if (!sym.file) {
if (!sym.file || sym.is_imported) {
hash('1');
hash((u64)&sym);
} else if (SectionFragment<E> *frag = sym.get_frag()) {
@@ -308,7 +305,9 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
hash_symbol(*isec.file.symbols[rel.r_sym]);
}
return digest_final(sha);
Digest digest;
hasher.finish(digest.data());
return digest;
}
template <typename E>
@@ -409,7 +408,7 @@ static void gather_edges(Context<E> &ctx,
template <typename E>
static i64 propagate(std::span<std::vector<Digest>> digests,
std::span<u32> edges, std::span<u32> edge_indices,
bool &slot, BitVector &converged,
bool &slot, std::span<u8> converged,
tbb::affinity_partitioner &ap) {
static Counter round("icf_round");
round++;
@@ -418,24 +417,24 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
tbb::enumerable_thread_specific<i64> changed;
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
if (converged.get(i))
if (converged[i])
return;
SHA256Hash sha;
sha.update(digests[2][i].data(), HASH_SIZE);
SipHash13_128 hasher(hmac_key);
hasher.update(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);
hasher.update(digests[slot][j].data(), HASH_SIZE);
digests[!slot][i] = digest_final(sha);
hasher.finish(digests[!slot][i].data());
if (digests[slot][i] == digests[!slot][i]) {
// This node has converged. Skip further iterations as it will
// yield the same hash.
converged.set(i);
converged[i] = true;
} else {
changed.local()++;
}
@@ -476,28 +475,36 @@ static void print_icf_sections(Context<E> &ctx) {
});
tbb::parallel_sort(leaders.begin(), leaders.end(),
[&](InputSection<E> *a, InputSection<E> *b) {
[](InputSection<E> *a, InputSection<E> *b) {
return a->get_priority() < b->get_priority();
});
std::ostream *out = &std::cout;
std::ofstream file;
std::string &path = ctx.arg.print_icf_sections;
if (path != "-") {
file.open(path);
if (file.fail())
Fatal(ctx) << "--print-icf-sections: cannot open " << path << ": "
<< errno_string();
out = &file;
}
i64 saved_bytes = 0;
for (InputSection<E> *leader : leaders) {
auto [begin, end] = map.equal_range(leader);
if (begin == end)
continue;
SyncOut(ctx) << "selected section " << *leader;
i64 n = 0;
for (auto it = begin; it != end; it++) {
SyncOut(ctx) << " removing identical section " << *it->second;
n++;
if (begin != end) {
*out << "selected section " << *leader << '\n';
for (auto it = begin; it != end; it++) {
*out << " removing identical section " << *it->second << '\n';
saved_bytes += leader->contents.size();
}
}
saved_bytes += leader->contents.size() * n;
}
SyncOut(ctx) << "ICF saved " << saved_bytes << " bytes";
*out << "ICF saved " << saved_bytes << " bytes\n";
}
template <typename E>
@@ -506,6 +513,8 @@ void icf_sections(Context<E> &ctx) {
if (ctx.objs.empty())
return;
get_random_bytes(hmac_key, sizeof(hmac_key));
uniquify_cies(ctx);
merge_leaf_nodes(ctx);
@@ -530,7 +539,7 @@ void icf_sections(Context<E> &ctx) {
std::vector<u32> edge_indices;
gather_edges<E>(ctx, sections, edges, edge_indices);
BitVector converged(digests[0].size());
std::vector<u8> converged(digests[0].size());
bool slot = 0;
// Execute the propagation rounds until convergence is obtained.
@@ -573,18 +582,23 @@ void icf_sections(Context<E> &ctx) {
}
}
// Group sections by SHA digest.
// Group sections by hash values.
{
Timer t(ctx, "group");
auto *map = new tbb::concurrent_unordered_map<Digest, InputSection<E> *>;
auto *map =
new tbb::concurrent_unordered_map<Digest, Atomic<InputSection<E> *>>;
std::span<Digest> digest = digests[slot];
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> *isec = sections[i];
auto [it, inserted] = map->insert({digest[i], isec});
if (!inserted && isec->get_priority() < it->second->get_priority())
it->second = isec;
if (!inserted) {
InputSection<E> *isec2 = it->second.load();
while (isec->get_priority() < isec2->get_priority() &&
!it->second.compare_exchange_strong(isec2, isec));
}
});
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
@@ -597,9 +611,21 @@ void icf_sections(Context<E> &ctx) {
ctx.on_exit.push_back([=] { delete map; });
}
if (ctx.arg.print_icf_sections)
if (!ctx.arg.print_icf_sections.empty())
print_icf_sections(ctx);
// Update alignment of leaders.
{
Timer t(ctx, "update_alignment");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && isec->leader && isec->leader != isec.get()) {
update_maximum(isec->leader->p2align, isec->p2align);
}
}
});
}
// Eliminate duplicate sections.
// Symbols pointing to eliminated sections will be redirected on the fly when
// exporting to the symtab.
@@ -608,7 +634,7 @@ void icf_sections(Context<E> &ctx) {
static Counter eliminated("icf_eliminated");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && isec->is_killed_by_icf()) {
if (isec && isec->is_alive && isec->icf_removed()) {
isec->kill();
eliminated++;
}
@@ -621,4 +647,4 @@ using E = MOLD_TARGET;
template void icf_sections(Context<E> &ctx);
} // namespace mold::elf
} // namespace mold
File diff suppressed because it is too large. Load diff
+458
View File
@@ -0,0 +1,458 @@
#include "mold.h"
#include <zlib.h>
#include <zstd.h>
namespace mold {
static i64 to_p2align(u64 alignment) {
if (alignment == 0)
return 0;
return std::countr_zero(alignment);
}
template <typename E>
bool cie_equals(const CieRecord<E> &a, const CieRecord<E> &b) {
if (a.get_contents() != b.get_contents())
return false;
std::span<const ElfRel<E>> x = a.get_rels();
std::span<const ElfRel<E>> y = b.get_rels();
if (x.size() != y.size())
return false;
for (i64 i = 0; i < x.size(); i++)
if (x[i].r_offset - a.input_offset != y[i].r_offset - b.input_offset ||
x[i].r_type != y[i].r_type ||
a.file.symbols[x[i].r_sym] != b.file.symbols[y[i].r_sym] ||
get_addend(a.input_section, x[i]) != get_addend(b.input_section, y[i]))
return false;
return true;
}
template <typename E>
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
: file(file), shndx(shndx) {
if (shndx < file.elf_sections.size()) {
name = file.shstrtab.data() + file.elf_sections[shndx].sh_name;
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
} else {
name = (shdr().sh_flags & SHF_TLS) ? ".tls_common" : ".common";
}
if (shdr().sh_flags & SHF_COMPRESSED) {
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
sh_size = chdr.ch_size;
p2align = to_p2align(chdr.ch_addralign);
} else {
sh_size = shdr().sh_size;
p2align = to_p2align(shdr().sh_addralign);
}
// Sections may have been compressed. We usually uncompress them
// directly into the mmap'ed output file, but we want to uncompress
// early for REL-type ELF types to read relocation addends from
// section contents. For RELA-type, we don't need to do this because
// addends are in relocations.
//
// SH-4 stores addends to sections despite being RELA, which is a
// special (and buggy) case.
if constexpr (!E::is_rela || is_sh4<E>)
uncompress(ctx);
}
template <typename E>
void InputSection<E>::uncompress(Context<E> &ctx) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
return;
u8 *buf = new u8[sh_size];
copy_contents_to(ctx, buf, sh_size);
contents = std::string_view((char *)buf, sh_size);
ctx.string_pool.emplace_back(buf);
uncompressed = true;
}
template <typename E>
void InputSection<E>::copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
memcpy(buf, contents.data(), sz);
return;
}
if (contents.size() < sizeof(ElfChdr<E>))
Fatal(ctx) << *this << ": corrupted compressed section";
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
switch (hdr.ch_type) {
case ELFCOMPRESS_ZLIB: {
z_stream s = {};
inflateInit(&s);
s.next_in = (u8 *)data.data();
s.avail_in = data.size();
s.next_out = buf;
s.avail_out = sz;
int r;
while (s.total_out < sz && (r = inflate(&s, Z_NO_FLUSH)) == Z_OK);
if (s.total_out < sz && r != Z_STREAM_END)
Fatal(ctx) << *this << ": uncompress failed: " << s.msg;
inflateEnd(&s);
msan_unpoison(buf, sz);
break;
}
case ELFCOMPRESS_ZSTD: {
ZSTD_DCtx *dctx = ZSTD_createDCtx();
ZSTD_inBuffer in = { data.data(), data.size() };
ZSTD_outBuffer out = { buf, (size_t)sz };
while (out.pos < out.size) {
size_t r = ZSTD_decompressStream(dctx, &out, &in);
if (ZSTD_isError(r))
Fatal(ctx) << *this << ": uncompress failed: " << ZSTD_getErrorName(r);
if (r == 0 && out.pos < out.size)
Fatal(ctx) << *this << ": uncompress failed: premature end of input";
}
ZSTD_freeDCtx(dctx);
msan_unpoison(buf, sz);
break;
}
default:
Fatal(ctx) << *this << ": unsupported compression type: 0x"
<< std::hex << hdr.ch_type;
}
}
// wingdi.h defines ERROR as a macro, so undefine it before use
#undef ERROR
typedef enum : u8 { NONE, ERROR, COPYREL, PLT, CPLT } Action;
template <typename E>
static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel) {
switch (action) {
case NONE:
break;
case ERROR:
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
<< std::hex << rel.r_offset << " against symbol `"
<< sym << "' can not be used; recompile with -fPIC";
break;
case COPYREL:
sym.flags |= NEEDS_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;
}
}
template <typename E>
static inline i64 get_output_type(Context<E> &ctx) {
if (ctx.arg.shared)
return 0;
if (ctx.arg.pie)
return 1;
return 2;
}
template <typename E>
static inline i64 get_sym_type(Symbol<E> &sym) {
if (sym.is_absolute())
return 0;
if (!sym.is_imported)
return 1;
if (sym.get_type() != STT_FUNC)
return 2;
return 3;
}
template <typename E>
void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
// We cannot promote them to dynamic relocations because the dynamic
// linker generally does not support PC-relative relocations.
static Action table[][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, PLT }, // Shared object
{ ERROR, NONE, COPYREL, CPLT }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
do_action(ctx, action, *this, sym, rel);
}
template <typename E>
void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
// This is a decision table for absolute relocations that is smaller
// than the pointer size (e.g. R_X86_64_32). Since the dynamic linker
// generally does not support dynamic relocations smaller than the
// pointer size, we need to report an error if a relocation cannot be
// resolved at link-time.
static Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, ERROR, ERROR, ERROR }, // Shared object
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
do_action(ctx, action, *this, sym, rel);
}
template <typename E>
void InputSection<E>::scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// Relax TLSDESC to Local Exec. In this case, we directly materialize
// a TP-relative offset, so no dynamic relocation is needed.
//
// TLSDESC relocs must always be relaxed for statically-linked
// executables even if -no-relax is given. It is because a
// statically-linked executable doesn't contain a trampoline
// function needed for TLSDESC.
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
// In this condition, TP-relative offset of a thread-local variable
// is known at process startup time, so we can relax TLSDESC to the
// code that reads the TP-relative offset from GOT and add TP to it.
sym.flags |= NEEDS_GOTTP;
} else {
// If no relaxation is doable, we simply create a TLSDESC dynamic
// relocation.
sym.flags |= NEEDS_TLSDESC;
}
}
template <typename E>
void InputSection<E>::check_tlsle(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
if (ctx.arg.shared)
Error(ctx) << *this << ": relocation " << rel << " against `" << sym
<< "` can not be used when making a shared object;"
<< " recompile with -fPIC";
}
template <typename E>
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
return;
// Copy data. In RISC-V and LoongArch object files, sections are not
// atomic unit of copying because of relaxation. That is, some
// relocations are allowed to remove bytes from the middle of a
// section and shrink the overall size of it.
if constexpr (is_riscv<E> || is_loongarch<E>) {
std::span<RelocDelta> deltas = extra.r_deltas;
if (deltas.empty()) {
// If a section is not relaxed, we can copy it as a one big chunk.
copy_contents_to(ctx, buf, sh_size);
} else {
// A relaxed section is copied piece-wise.
memcpy(buf, contents.data(), deltas[0].offset);
for (i64 i = 0; i < deltas.size(); i++) {
i64 offset = deltas[i].offset;
i64 delta = deltas[i].delta;
i64 end = (i + 1 == deltas.size()) ? contents.size() : deltas[i + 1].offset;
i64 removed_bytes = get_removed_bytes(deltas, i);
memcpy(buf + offset + removed_bytes - delta,
contents.data() + offset + removed_bytes,
end - offset - removed_bytes);
}
}
} else {
copy_contents_to(ctx, buf, sh_size);
}
// Apply relocations
if (!ctx.arg.relocatable) {
if (shdr().sh_flags & SHF_ALLOC)
apply_reloc_alloc(ctx, buf);
else
apply_reloc_nonalloc(ctx, buf);
}
}
// Get the name of a function containin a given offset.
template <typename E>
std::string_view
InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
for (Symbol<E> *sym : file.symbols)
if (sym->file == &file)
if (const ElfSym<E> &esym = sym->esym();
esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size)
return ctx.arg.demangle ? demangle(*sym) : sym->name();
return "";
}
// Test if the symbol a given relocation refers to has already been resolved.
// If not, record that error and returns true.
template <typename E>
bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
// If a relocation refers to a linker-synthesized symbol for a
// section fragment, it's always been resolved.
if (file.elf_syms.size() <= rel.r_sym)
return false;
Symbol<E> &sym = *file.symbols[rel.r_sym];
const ElfSym<E> &esym = file.elf_syms[rel.r_sym];
// If a symbol is defined in a comdat group, and the comdat group is
// discarded, the symbol may not have an owner. It is technically an
// violation of the One Definition Rule, so it is a programmer's fault.
if (!sym.file) {
Error(ctx) << *this << ": " << sym << " refers to a discarded COMDAT section"
<< " probably due to an ODR violation";
return true;
}
auto record = [&] {
std::stringstream ss;
if (std::string_view source = file.get_source_name(); !source.empty())
ss << ">>> referenced by " << source << "\n";
else
ss << ">>> referenced by " << *this << "\n";
ss << ">>> " << file;
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
ss << ":(" << func << ")";
ss << '\n';
typename decltype(ctx.undef_errors)::accessor acc;
ctx.undef_errors.insert(acc, {&sym, {}});
acc->second.push_back(ss.str());
};
// A non-weak undefined symbol must be promoted to an imported symbol
// or resolved to an defined symbol. Otherwise, we need to report an
// error or warn on it.
//
// Every ELF file has an absolute local symbol as its first symbol.
// Referring to that symbol is always valid.
bool is_undef = esym.is_undef() && !esym.is_weak() && sym.sym_idx;
if (is_undef && sym.esym().is_undef()) {
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR && !sym.is_imported) {
record();
return true;
}
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN) {
record();
return false;
}
}
return false;
}
template <typename E>
MergeableSection<E>::MergeableSection(Context<E> &ctx, MergedSection<E> &parent,
std::unique_ptr<InputSection<E>> &isec)
: parent(parent), p2align(isec->p2align), input_section(std::move(isec)) {
input_section->uncompress(ctx);
std::scoped_lock lock(parent.mu);
parent.members.push_back(this);
}
static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
if (entsize == 1)
return data.find('\0', pos);
for (; pos <= data.size() - entsize; pos += entsize)
if (data.substr(pos, entsize).find_first_not_of('\0') == data.npos)
return pos;
return data.npos;
}
// Mergeable sections (sections with SHF_MERGE bit) typically contain
// string literals. Linker is expected to split the section contents
// into null-terminated strings, merge them with mergeable strings
// from other object files, and emit uniquified strings to an output
// file.
//
// This mechanism reduces the size of an output file. If two source
// files happen to contain the same string literal, the output will
// contain only a single copy of it.
//
// It is less common than string literals, but mergeable sections can
// contain fixed-sized read-only records too.
//
// This function splits the section contents into small pieces that we
// call "section fragments". Section fragment is a unit of merging.
//
// We do not support mergeable sections that have relocations.
template <typename E>
void MergeableSection<E>::split_contents(Context<E> &ctx) {
std::string_view data = input_section->contents;
if (data.size() > UINT32_MAX)
Fatal(ctx) << *input_section << ": mergeable section too large";
i64 entsize = parent.shdr.sh_entsize;
// Split sections
if (parent.shdr.sh_flags & SHF_STRINGS) {
for (i64 pos = 0; pos < data.size();) {
frag_offsets.push_back(pos);
size_t end = find_null(data, pos, entsize);
if (end == data.npos)
Fatal(ctx) << *input_section << ": string is not null terminated";
pos = end + entsize;
}
} else {
if (data.size() % entsize)
Fatal(ctx) << *input_section
<< ": section size is not multiple of sh_entsize";
frag_offsets.reserve(data.size() / entsize);
for (i64 pos = 0; pos < data.size(); pos += entsize)
frag_offsets.push_back(pos);
}
// Compute hashes for section pieces
HyperLogLog estimator;
hashes.reserve(frag_offsets.size());
for (i64 i = 0; i < frag_offsets.size(); i++) {
u64 hash = hash_string(get_contents(i));
hashes.push_back(hash);
estimator.insert(hash);
}
parent.estimator.merge(estimator);
static Counter counter("string_fragments");
counter += frag_offsets.size();
}
template <typename E>
void MergeableSection<E>::resolve_contents(Context<E> &ctx) {
fragments.reserve(frag_offsets.size());
for (i64 i = 0; i < frag_offsets.size(); i++)
fragments.push_back(parent.insert(ctx, get_contents(i), hashes[i], p2align));
// Reclaim memory as we'll never use this vector again
hashes.clear();
hashes.shrink_to_fit();
}
using E = MOLD_TARGET;
template bool cie_equals(const CieRecord<E> &, const CieRecord<E> &);
template class InputSection<E>;
template class MergeableSection<E>;
} // namespace mold
+50
View File
@@ -0,0 +1,50 @@
// 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"
#include <cstdlib>
#include <fcntl.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
namespace mold {
static int lock_fd = -1;
void acquire_global_lock() {
char *jobs = getenv("MOLD_JOBS");
if (!jobs || jobs != "1"s)
return;
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;
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
if (fd == -1)
return;
if (lockf(fd, F_LOCK, 0) == -1)
return;
lock_fd = fd;
}
void release_global_lock() {
if (lock_fd != -1)
close(lock_fd);
}
} // namespace mold
+6
View File
@@ -0,0 +1,6 @@
namespace mold {
void acquire_global_lock() {}
void release_global_lock() {}
} // namespace mold
Loaded 100 of 2177 files, more files were not shown because too many files have changed in this diff. Show more