Compare commits

..
92 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
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
98 changed files with 6973 additions and 1068 deletions

No files matched your search

+4 -4
View File
@@ -43,7 +43,7 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
@@ -59,7 +59,7 @@ jobs:
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
@@ -71,7 +71,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
@@ -87,7 +87,7 @@ jobs:
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
@@ -37,7 +37,7 @@ jobs:
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
+16 -12
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: rui314/setup-mold@staging
- run: sudo ./install-build-deps.sh
- name: build
@@ -27,7 +27,7 @@ jobs:
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-clang
@@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
@@ -51,7 +51,7 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: install-build-deps
run: |
sudo ./install-build-deps.sh
@@ -65,7 +65,7 @@ jobs:
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-multi-archs
@@ -89,7 +89,7 @@ jobs:
timeout-minutes: 60
container: ${{ matrix.distro }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- run: ./install-build-deps.sh
- name: build
run: |
@@ -98,11 +98,15 @@ jobs:
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@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-${{ matrix.distro }}
name: test-results-${{ steps.sanitize.outputs.name }}
path: |
build
!build/CMakeFiles
@@ -111,7 +115,7 @@ jobs:
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: build
run: |
mkdir build
@@ -123,7 +127,7 @@ jobs:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: build
run: |
mkdir build
@@ -135,7 +139,7 @@ jobs:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
@@ -154,7 +158,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build and test
uses: vmactions/freebsd-vm@v1
with:
+6 -6
View File
@@ -28,7 +28,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
@@ -42,7 +42,7 @@ jobs:
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
@@ -50,7 +50,7 @@ jobs:
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
@@ -65,7 +65,7 @@ jobs:
$version = $Env:TAG -replace '^v', ''
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-x86_64-windows.zip
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
@@ -74,9 +74,9 @@ jobs:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
path: dist
pattern: '*'
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install ronn
run: sudo apt-get update && sudo apt-get install -y ronn
+14 -5
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "MOLD" "1" "December 2025" ""
.TH "MOLD" "1" "May 2026" ""
.SH "NAME"
\fBmold\fR \- a modern linker
.SH "SYNOPSIS"
@@ -282,8 +282,8 @@ Synonym for \fB\-\-build\-id=sha256\fR\.
\fB\-\-no\-build\-id\fR
Synonym for \fB\-\-build\-id=none\fR\.
.TP
\fB\-\-compress\-debug\-sections\fR=[ \fBzlib\fR | \fBzlib\-gabi\fR | \fBzstd\fR | \fBnone\fR ]
Compress DWARF debug info (\fB\.debug_*\fR sections) using the zlib or zstd compression algorithm\. \fBzlib\-gabi\fR is an alias for \fBzlib\fR\.
\fB\-\-compress\-debug\-sections\fR=[ \fBzlib\fR | \fBzlib\-gabi\fR | \fBzlib:\fR\fIN\fR | \fBzstd\fR | \fBzstd:\fR\fIN\fR | \fBnone\fR ]
Compress DWARF debug info (\fB\.debug_*\fR sections) using the zlib or zstd compression algorithm\. \fBzlib\-gabi\fR is an alias for \fBzlib\fR\. \fBzlib:\fR\fIN\fR selects the zlib compression level, where \fIN\fR is between 0 and 9 (0 means no compression), and \fBzstd:\fR\fIN\fR selects the zstd compression level, where \fIN\fR is between 1 and 22\. Higher levels achieve better compression ratios but are slower\. \fBzlib\fR is equivalent to \fBzlib:1\fR and \fBzstd\fR is equivalent to \fBzstd:3\fR\.
.TP
\fB\-\-defsym\fR=\fIsymbol\fR=\fIvalue\fR
Define \fIsymbol\fR as an alias for \fIvalue\fR\.
@@ -371,10 +371,16 @@ Embed a specified string into the \fB\.note\.package\fR section\. This option is
.IP
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 (\fB,\fR) character in the argument\. This is useful because the compiler replaces all occurrences of commas in \fB\-Wl,\fR with spaces before forwarding them to the linker\. Note that \fBmold\fR always interprets the argument as percent\-encoded, so you also need to escape all occurrences of \fB%\fR as \fB%25\fR\.
.TP
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBnone\fR ]
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBandroid\fR | \fBandroid+relr\fR | \fBnone\fR ]
Compress dynamic relocations in the output file\.
.IP
If \fBrelr\fR is specified, all \fBR_*_RELATIVE\fR relocations are put into \fB\.relr\.dyn\fR section instead of \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section\. Since \fB\.relr\.dyn\fR 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\.
.IP
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. As of 2022, only ChromeOS, Android and Fuchsia support it\.
If \fBandroid\fR is specified, all dynamic relocations are encoded in Android's packed relocation format (APS2) and stored in the \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section, whose section type is changed to \fBSHT_ANDROID_REL\fR or \fBSHT_ANDROID_RELA\fR\. The dynamic table is updated to use \fBDT_ANDROID_REL\fR / \fBDT_ANDROID_RELA\fR and their \fBSZ\fR variants in place of the standard tags\.
.IP
\fBandroid+relr\fR combines both: \fBR_*_RELATIVE\fR relocations go to \fB\.relr\.dyn\fR and the rest are APS2\-encoded\. This is the configuration used by most Android system binaries\.
.IP
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. 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 \fBDT_ANDROID_REL(A)\fR\.
.TP
\fB\-\-pie\fR, \fB\-\-pic\-executable\fR, \fB\-\-no\-pie\fR, \fB\-\-no\-pic\-executable\fR
Create a position\-independent executable\.
@@ -440,6 +446,9 @@ How to handle undefined symbols\.
\fB\-\-version\-script\fR=\fIfile\fR
Read version script from \fIfile\fR\. If \fIfile\fR does not exist in the current directory, it is searched from library search paths for the sake of compatibility with GNU ld\.
.TP
\fB\-w\fR, \fB\-\-no\-warnings\fR
Suppress warnings and cancel \fB\-\-fatal\-warnings\fR\.
.TP
\fB\-\-warn\-common\fR, \fB\-\-no\-warn\-common\fR
Warn about common symbols\.
.TP
+29 -5
View File
@@ -506,9 +506,14 @@ but as `-o magic`.
* `--no-build-id`:
Synonym for `--build-id=none`.
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zstd` | `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`.
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_.
@@ -658,16 +663,32 @@ but as `-o magic`.
argument as percent-encoded, so you also need to escape all occurrences
of `%` as `%25`.
* `--pack-dyn-relocs`=[ `relr` | `none` ]:
* `--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 2022, only
ChromeOS, Android and Fuchsia support it.
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.
@@ -757,6 +778,9 @@ but as `-o magic`.
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.
+1 -1
View File
@@ -19,7 +19,7 @@ rocky | ol)
dnf install -y gcc-c++ cmake diffutils util-linux
;;
opensuse-*)
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux gawk
;;
gentoo)
emerge-webrsync
+8 -10
View File
@@ -45,12 +45,11 @@ static std::vector<std::span<u8>> split(std::span<u8> input) {
return vec;
}
static std::span<u8> zlib_compress(std::span<u8> input) {
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, we chose compression
// level 1.
// pretty well with lower compression levels, the default level is 1.
z_stream strm = {};
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
// Set an input buffer
strm.avail_in = input.size();
@@ -94,7 +93,7 @@ static std::span<u8> zlib_compress(std::span<u8> input) {
return {buf, (size_t)(bufsize - strm.avail_out)};
}
ZlibCompressor::ZlibCompressor(u8 *buf, i64 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());
@@ -103,7 +102,7 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
std::span<u8> in = inputs[i];
adlers[i] = adler32(1, in.data(), in.size());
shards[i] = zlib_compress(in);
shards[i] = zlib_compress(in, level);
});
// Combine checksums
@@ -139,22 +138,21 @@ void ZlibCompressor::write_to(u8 *buf) {
*(ub32 *)(end - 4) = checksum;
}
static std::span<u8> zstd_compress(std::span<u8> input) {
static std::span<u8> zstd_compress(std::span<u8> input, int level) {
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
u8 *buf = new u8[bufsize];
int level = 3; // compression level; must be between 1 to 22
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
assert(!ZSTD_isError(sz));
return {buf, sz};
}
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
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;
+6 -2
View File
@@ -95,11 +95,15 @@ static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
// [abc]: a, b or c
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [^a-z]: Any character except lowercase letters
// [!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] == '^') {
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
negate = true;
pat = pat.substr(1);
}
+8 -4
View File
@@ -395,11 +395,15 @@ public:
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
i64 bufsize = sizeof(Entry) * this->nbuckets;
// Allocate a zero-initialized buffer. We use mmap() if available
// because it's faster than malloc() and memset().
// 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);
@@ -665,7 +669,7 @@ protected:
class ZlibCompressor : public Compressor {
public:
ZlibCompressor(u8 *buf, i64 size);
ZlibCompressor(u8 *buf, i64 size, i64 level);
void write_to(u8 *buf) override;
private:
@@ -674,7 +678,7 @@ private:
class ZstdCompressor : public Compressor {
public:
ZstdCompressor(u8 *buf, i64 size);
ZstdCompressor(u8 *buf, i64 size, i64 level);
void write_to(u8 *buf) override;
};
+3 -1
View File
@@ -5,7 +5,9 @@
#include <ios>
#include <tbb/concurrent_vector.h>
#ifndef _WIN32
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/resource.h>
#include <sys/time.h>
#endif
+5
View File
@@ -86,6 +86,9 @@ void TarWriter::append(std::string path, std::string_view data) {
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';
@@ -99,6 +102,8 @@ void TarWriter::append(std::string path, std::string_view data) {
// Write Ustar header
UstarHeader ustar = {};
memcpy(ustar.mode, "0000664", 8);
if (data.size() >= 0100'000'000'000)
unreachable();
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
finalize(ustar);
fwrite(&ustar, sizeof(ustar), 1, out);
+31
View File
@@ -208,6 +208,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_AARCH64_MOVW_UABS_G3:
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
break;
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_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);
@@ -573,6 +597,13 @@ 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_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:
+3 -3
View File
@@ -46,10 +46,10 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
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,7 +65,7 @@ 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;
}
+82 -1
View File
@@ -981,7 +981,7 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
// pcaddi $t0, <offset>
if (is_relaxable_got_load(ctx, isec, i))
if (i64 dist = compute_distance(ctx, sym, isec, r);
is_int(dist, 22))
is_int(dist, 22) && (dist & 3) == 0)
remove(4);
break;
case R_LARCH_TLS_DESC_PC_HI20:
@@ -1010,6 +1010,87 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
isec.sh_size -= r_delta;
}
// With --emit-relocs, rewrite the emitted relocations to match the
// instructions that relaxation produced. The logic parallels apply_reloc_alloc.
template <>
void rewrite_reloc_types(Context<E> &ctx, InputSection<E> &isec, ElfRel<E> *buf) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::span<RelocDelta> deltas = isec.extra.r_deltas;
i64 k = 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
i64 removed_bytes = 0;
if (!deltas.empty()) {
while (k < deltas.size() && deltas[k].offset < rel.r_offset)
k++;
if (k < deltas.size() && deltas[k].offset == rel.r_offset)
removed_bytes = get_removed_bytes(deltas, k);
}
switch (rel.r_type) {
case R_LARCH_TLS_LE_HI20_R:
case R_LARCH_TLS_LE_ADD_R:
// lu12i.w + add.d + addi.d => addi.d
if (removed_bytes)
buf[i].r_type = R_LARCH_NONE;
break;
case R_LARCH_PCALA_HI20:
case R_LARCH_GOT_PC_HI20:
// pcalau12i + addi.d/ld.d => pcaddi. The high part vanishes and the low
// part becomes the pcaddi's PC-relative relocation.
if (removed_bytes) {
buf[i].r_type = R_LARCH_NONE;
buf[i + 2].r_type = R_LARCH_PCREL20_S2;
i += 3;
}
break;
case R_LARCH_CALL36:
// pcaddu18i + jirl => b or bl
if (removed_bytes)
buf[i].r_type = R_LARCH_B26;
break;
case R_LARCH_TLS_DESC_PC_HI20:
// pcalau12i + addi.d => pcaddi when TLSDESC is kept; both deleted when it
// is relaxed to IE/LE.
if (!sym.has_tlsdesc(ctx)) {
buf[i].r_type = R_LARCH_NONE;
} else if (removed_bytes) {
buf[i].r_type = R_LARCH_NONE;
buf[i + 2].r_type = R_LARCH_TLS_DESC_PCREL20_S2;
}
break;
case R_LARCH_TLS_DESC_PC_LO12:
if (!sym.has_tlsdesc(ctx))
buf[i].r_type = R_LARCH_NONE;
break;
case R_LARCH_TLS_DESC_LD:
// TLSDESC is relaxed to IE or LE. The ld.d slot holds the first
// instruction of the resulting sequence.
if (sym.has_tlsdesc(ctx))
break;
if (sym.has_gottp(ctx))
buf[i].r_type = R_LARCH_TLS_IE_PC_HI20; // pcalau12i
else if (removed_bytes)
buf[i].r_type = R_LARCH_NONE; // small TP offset (deleted)
else
buf[i].r_type = R_LARCH_TLS_LE_HI20; // large TP offset (lu12i.w)
break;
case R_LARCH_TLS_DESC_CALL:
// The jirl slot holds the second instruction of the IE or LE sequence.
if (sym.has_tlsdesc(ctx))
break;
if (sym.has_gottp(ctx))
buf[i].r_type = R_LARCH_TLS_IE_PC_LO12; // ld.d
else
buf[i].r_type = R_LARCH_TLS_LE_LO12; // ori or addi.w
break;
}
}
}
} // namespace mold
#endif
+13 -15
View File
@@ -486,7 +486,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
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;
}
@@ -643,30 +643,28 @@ void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
this->shdr.sh_size += ENTRY_SIZE;
}
i64 PPC64OpdSection::get_reldyn_size(Context<E> &ctx) const {
if (ctx.arg.pic)
return symbols.size() * 2;
return 0;
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);
ElfRel<E> *rel = nullptr;
if (ctx.arg.pic)
rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
for (Symbol<E> *sym : symbols) {
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
*buf++ = addr;
*buf++ = ctx.extra.TOC->value;
*buf++ = 0;
if (ctx.arg.pic) {
u64 loc = sym->get_opd_addr(ctx);
*rel++ = ElfRel<E>(loc, E::R_RELATIVE, 0, addr);
*rel++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
}
}
}
+66
View File
@@ -1000,6 +1000,72 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
isec.sh_size -= r_delta;
}
// With --emit-relocs, rewrite the emitted relocations to match the
// instructions that relaxation produced. The logic parallels apply_reloc_alloc.
template <>
void rewrite_reloc_types(Context<E> &ctx, InputSection<E> &isec, ElfRel<E> *buf) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::span<RelocDelta> deltas = isec.extra.r_deltas;
i64 k = 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
i64 removed_bytes = 0;
if (!deltas.empty()) {
while (k < deltas.size() && deltas[k].offset < rel.r_offset)
k++;
if (k < deltas.size() && deltas[k].offset == rel.r_offset)
removed_bytes = get_removed_bytes(deltas, k);
}
switch (rel.r_type) {
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
// auipc + jalr => jal, or c.j/c.jal
if (removed_bytes == 4)
buf[i].r_type = R_RISCV_JAL;
else if (removed_bytes == 6)
buf[i].r_type = R_RISCV_RVC_JUMP;
break;
case R_RISCV_GOT_HI20:
// auipc + l[dw] GOT load => the value is materialized directly, so both
// this and the paired PCREL_LO12 (the load) no longer need a relocation.
if (removed_bytes) {
buf[i].r_type = R_RISCV_NONE;
buf[i + 2].r_type = R_RISCV_NONE;
i += 3;
}
break;
case R_RISCV_HI20:
// lui (+ addi) => an instruction holding a link-time constant.
if (removed_bytes)
buf[i].r_type = R_RISCV_NONE;
break;
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_ADD:
// lui + add => deleted; the variable is accessed relative to tp directly.
if (removed_bytes)
buf[i].r_type = R_RISCV_NONE;
break;
case R_RISCV_TLSDESC_HI20:
if (!sym.has_tlsdesc(ctx))
buf[i].r_type = R_RISCV_NONE;
break;
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL: {
Symbol<E> &sym2 =
*isec.file.symbols[find_paired_reloc(ctx, isec, rels, sym, i).r_sym];
if (!sym2.has_tlsdesc(ctx))
buf[i].r_type = R_RISCV_NONE;
break;
}
}
}
}
// ISA name handlers
//
// An example of ISA name is "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0".
+67 -16
View File
@@ -78,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 <>
+57 -85
View File
@@ -18,7 +18,6 @@
#else
# include <direct.h>
# define isatty _isatty
# define chdir _chdir
# define STDERR_FILENO (_fileno(stderr))
#endif
@@ -62,6 +61,7 @@ Options:
-s, --strip-all Strip .symtab section
-u SYMBOL, --undefined SYMBOL
Force to resolve SYMBOL
-w, --no-warnings Suppress warnings
-y SYMBOL, --trace-symbol SYMBOL
Trace references to SYMBOL
--Bdynamic, --dy Link against shared libraries (default)
@@ -89,7 +89,7 @@ Options:
--color-diagnostics=[auto,always,never]
Use colors in diagnostics
--color-diagnostics Alias for --color-diagnostics=always
--compress-debug-sections [none,zlib,zlib-gabi,zstd]
--compress-debug-sections=[none,zlib,zlib:0,...,zlib:9,zstd,zstd:1,...,zstd:22]
Compress .debug_* sections
--dc Ignored
--dependency-file=FILE Write Makefile-style dependency rules to FILE
@@ -131,7 +131,7 @@ Options:
--no-undefined Report undefined symbols (even with --shared)
--noinhibit-exec Create an output file even if errors occur
--oformat=binary Omit ELF, section, and program headers
--pack-dyn-relocs=[relr,none]
--pack-dyn-relocs=[relr,android,android+relr,none]
Pack dynamic relocations
--package-metadata=PERCENT_ENCODED_STRING
Set a given string to .note.package
@@ -507,63 +507,6 @@ static bool is_file(const std::filesystem::path& path) {
return !std::filesystem::is_directory(path, error) && !error;
}
template <typename E>
static std::vector<SectionOrder>
parse_section_order(Context<E> &ctx, std::string_view arg) {
auto flags = std::regex_constants::ECMAScript | std::regex_constants::icase |
std::regex_constants::optimize;
static std::regex re1(R"(TEXT|DATA|RODATA|BSS)", flags);
static std::regex re2(R"([a-zA-Z0-9_.]\S*|EHDR|PHDR)", flags);
static std::regex re3(R"(=(0x[0-9a-f]+|\d+))", flags);
static std::regex re4(R"(%(0x[0-9a-f]+|\d+))", flags);
static std::regex re5(R"(!(\S+))", flags);
std::vector<SectionOrder> vec;
for (std::string_view tok : split_string(arg, " \t")) {
if (tok.empty())
continue;
vec.push_back(SectionOrder{ .token = tok });
SectionOrder &ord = vec.back();
std::cmatch m;
if (std::regex_match(tok.data(), tok.data() + tok.size(), m, re1)) {
ord.type = SectionOrder::GROUP;
ord.name = m[0].str();
} else if (std::regex_match(tok.data(), tok.data() + tok.size(), m, re2)) {
ord.type = SectionOrder::SECTION;
ord.name = m[0].str();
} else if (std::regex_match(tok.data(), tok.data() + tok.size(), m, re3)) {
ord.type = SectionOrder::ADDR;
std::string s = m[1];
ord.value = std::stoull(s, nullptr, s.starts_with("0x") ? 16 : 10);
} else if (std::regex_match(tok.data(), tok.data() + tok.size(), m, re4)) {
ord.type = SectionOrder::ALIGN;
std::string s = m[1];
ord.value = std::stoull(s, nullptr, s.starts_with("0x") ? 16 : 10);
} else if (std::regex_match(tok.data(), tok.data() + tok.size(), m, re5)) {
ord.type = SectionOrder::SYMBOL;
ord.name = m[1].str();
} else {
Fatal(ctx) << "--section-order: parse error: " << arg;
}
}
bool is_first = true;
for (SectionOrder &ord : vec) {
if (ord.type == SectionOrder::SECTION) {
if (is_first) {
is_first = false;
} else if (ord.name == "EHDR") {
Fatal(ctx) << "--section-order: EHDR must be the first "
<< "section specifier: " << arg;
}
}
}
return vec;
}
template <typename E>
static std::variant<Symbol<E> *, u64>
parse_defsym_value(Context<E> &ctx, std::string_view s) {
@@ -893,6 +836,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.detach = true;
} else if (read_flag("no-detach")) {
ctx.arg.detach = false;
} else if (read_arg("orphan-handling")) {
if (arg != "place" && arg != "warn" && arg != "error")
Fatal(ctx) << "--orphan-handling: unsupported mode: " << arg;
ctx.arg.orphan_handling = arg;
} else if (read_flag("warn-rwx-segments") ||
read_flag("no-warn-rwx-segments")) {
// mold doesn't warn about RWX segments
} else if (read_flag("default-symver")) {
ctx.arg.default_symver = true;
} else if (read_flag("noinhibit-exec")) {
@@ -1001,9 +951,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("pack-dyn-relocs=relr") ||
read_z_flag("pack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = true;
ctx.arg.pack_dyn_relocs_android = false;
} else if (read_flag("pack-dyn-relocs=android")) {
ctx.arg.pack_dyn_relocs_android = true;
ctx.arg.pack_dyn_relocs_relr = false;
} else if (read_flag("pack-dyn-relocs=android+relr")) {
ctx.arg.pack_dyn_relocs_android = true;
ctx.arg.pack_dyn_relocs_relr = true;
} else if (read_flag("pack-dyn-relocs=none") ||
read_z_flag("nopack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = false;
ctx.arg.pack_dyn_relocs_android = false;
} else if (read_flag("use-android-relr-tags")) {
ctx.arg.use_android_relr_tags = true;
} else if (read_flag("no-use-android-relr-tags")) {
ctx.arg.use_android_relr_tags = false;
} else if (read_arg("package-metadata")) {
ctx.arg.package_metadata = parse_package_metadata(ctx, arg);
} else if (read_flag("stats")) {
@@ -1039,14 +1001,31 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("zero-to-bss")) {
ctx.arg.zero_to_bss = true;
} else if (read_arg("compress-debug-sections")) {
if (arg == "zlib" || arg == "zlib-gabi")
if (arg == "zlib" || arg == "zlib-gabi") {
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
else if (arg == "zstd")
ctx.arg.compress_debug_sections_level = 1;
} else if (arg.starts_with("zlib:")) {
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
i64 level = parse_number(ctx, "compress-debug-sections", arg.substr(5));
if (level < 0 || 9 < level)
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg
<< " (zlib level must be between 0 and 9)";
ctx.arg.compress_debug_sections_level = level;
} else if (arg == "zstd") {
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
else if (arg == "none")
ctx.arg.compress_debug_sections_level = 3;
} else if (arg.starts_with("zstd:")) {
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
i64 level = parse_number(ctx, "compress-debug-sections", arg.substr(5));
if (level < 1 || 22 < level)
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg
<< " (zstd level must be between 1 and 22)";
ctx.arg.compress_debug_sections_level = level;
} else if (arg == "none") {
ctx.arg.compress_debug_sections = ELFCOMPRESS_NONE;
else
} else {
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
}
} else if (read_arg("wrap")) {
ctx.arg.wrap.insert(arg);
} else if (read_flag("omagic") || read_flag("N")) {
@@ -1074,8 +1053,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Fatal(ctx) << "--section-start: syntax error: " << arg;
ctx.arg.section_start[arg.substr(0, pos)] =
parse_hex(ctx, "section-start", arg.substr(pos + 1));
} else if (read_arg("section-order")) {
ctx.arg.section_order = parse_section_order(ctx, arg);
} else if (read_arg("Tbss")) {
ctx.arg.section_start[".bss"] = parse_hex(ctx, "Tbss", arg);
} else if (read_arg("Tdata")) {
@@ -1191,6 +1168,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.fatal_warnings = true;
} else if (read_flag("no-fatal-warnings")) {
ctx.arg.fatal_warnings = false;
} else if (read_flag("w") || read_flag("no-warnings")) {
ctx.arg.suppress_warnings = true;
} else if (read_flag("fork")) {
ctx.arg.fork = true;
} else if (read_flag("no-fork")) {
@@ -1226,8 +1205,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.ignore_data_address_equality = true;
} else if (read_arg("image-base")) {
ctx.arg.image_base = parse_number(ctx, "image-base", arg);
} else if (read_arg("physical-image-base")) {
ctx.arg.physical_image_base = parse_number(ctx, "physical-image-base", arg);
} else if (read_flag("print-icf-sections")) {
ctx.arg.print_icf_sections = "-";
} else if (read_eq("print-icf-sections")) {
@@ -1471,10 +1448,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.dependency_file = ctx.arg.chroot + "/" + ctx.arg.dependency_file;
}
if (!ctx.arg.directory.empty())
if (chdir(ctx.arg.directory.c_str()) == -1)
if (!ctx.arg.directory.empty()) {
std::error_code ec;
std::filesystem::current_path(ctx.arg.directory, ec);
if (ec)
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
<< ": " << errno_string();
<< ": " << ec.message();
}
if (!ctx.arg.sysroot.empty()) {
for (std::string &path : ctx.arg.library_paths) {
@@ -1496,6 +1476,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.pic)
ctx.arg.image_base = 0;
// A shared library is loaded by the dynamic linker, so it doesn't need
// PT_INTERP. (-pie makes the output an executable even with -shared.)
if (ctx.arg.shared && !ctx.arg.pie)
ctx.arg.dynamic_linker = "";
if (allow_shlib_undefined)
ctx.arg.allow_shlib_undefined = *allow_shlib_undefined;
else
@@ -1526,11 +1511,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
((u64)std::random_device()() << 32) | std::random_device()();
}
// --section-order implies `-z separate-loadable-segments`
if (z_separate_code)
ctx.arg.z_separate_code = *z_separate_code;
else if (!ctx.arg.section_order.empty())
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
// `-z dynamic-undefined-weak` is enabled by default for DSOs.
if (z_dynamic_undefined_weak)
@@ -1538,11 +1520,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
else
ctx.arg.z_dynamic_undefined_weak = ctx.arg.shared;
// --section-order implies `-z norelro`
if (z_relro)
ctx.arg.z_relro = *z_relro;
else if (!ctx.arg.section_order.empty())
ctx.arg.z_relro = false;
if (ctx.arg.nmagic || ctx.arg.omagic)
ctx.arg.z_relro = false;
@@ -1565,9 +1544,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.apply_dynamic_relocs)
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
if (!ctx.arg.section_start.empty() && !ctx.arg.section_order.empty())
Fatal(ctx) << "--section-start may not be used with --section-order";
if (ctx.arg.image_base % ctx.page_size)
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
@@ -1595,8 +1571,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Fatal(ctx) << "--version-script: file not found: " << path;
};
ReaderContext rctx;
Script(ctx, rctx, open()).parse_version_script();
Script(ctx, open()).parse_version_script();
}
if (separate_debug_file) {
@@ -1613,8 +1588,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.stats || ctx.arg.perf)
ctx.arg.detach = false;
ctx.arg.undefined.push_back(ctx.arg.entry);
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
std::variant<Symbol<E> *, u64> &val = ctx.arg.defsyms[i].second;
if (Symbol<E> **sym = std::get_if<Symbol<E> *>(&val))
@@ -1644,7 +1617,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
sym->gc_root = true;
for (Symbol<E> *sym : ctx.arg.require_defined)
sym->gc_root = true;
ctx.arg.entry->gc_root = true;
if (version_shown && remaining.empty())
exit(0);
+66
View File
@@ -81,7 +81,11 @@ enum : u32 {
SHT_RELR = 19,
SHT_CREL = 0x40000014,
SHT_LOOS = 0x60000000,
SHT_ANDROID_REL = 0x60000001,
SHT_ANDROID_RELA = 0x60000002,
SHT_LLVM_ADDRSIG = 0x6fff4c03,
SHT_ANDROID_RELR = 0x6fffff00,
SHT_GNU_SFRAME = 0x6ffffff4,
SHT_GNU_HASH = 0x6ffffff6,
SHT_GNU_VERDEF = 0x6ffffffd,
SHT_GNU_VERNEED = 0x6ffffffe,
@@ -191,6 +195,7 @@ enum : u32 {
PT_GNU_STACK = 0x6474e551,
PT_GNU_RELRO = 0x6474e552,
PT_GNU_PROPERTY = 0x6474e553,
PT_GNU_SFRAME = 0x6474e554,
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
PT_ARM_EXIDX = 0x70000001,
PT_RISCV_ATTRIBUTES = 0x70000003,
@@ -285,6 +290,13 @@ enum : u32 {
DT_RELRSZ = 35,
DT_RELR = 36,
DT_RELRENT = 37,
DT_ANDROID_REL = 0x6000000f,
DT_ANDROID_RELSZ = 0x60000010,
DT_ANDROID_RELA = 0x60000011,
DT_ANDROID_RELASZ = 0x60000012,
DT_ANDROID_RELR = 0x6fffe000,
DT_ANDROID_RELRSZ = 0x6fffe001,
DT_ANDROID_RELRENT = 0x6fffe003,
DT_GNU_HASH = 0x6ffffef5,
DT_DEPAUDIT = 0x6ffffefb,
DT_AUDIT = 0x6ffffefc,
@@ -1493,6 +1505,52 @@ template <typename E> using U64 = std::conditional_t<E::is_le, ul64, ub64>;
template <typename E> using Word = std::conditional_t<E::is_64, U64<E>, U32<E>>;
template <typename E> using SWord = std::conditional_t<E::is_64, I64<E>, I32<E>>;
//
// SFrame is a simple unwind information format used as a lightweight
// alternative to .eh_frame. A .sframe section consists of a header, an
// array of Function Descriptor Entries (FDEs) sorted by PC, and a blob
// of Frame Row Entries (FREs). mold understands SFrame Version 3.
//
// https://sourceware.org/binutils/docs/sframe-spec.html
//
static constexpr u16 SFRAME_MAGIC = 0xdee2;
static constexpr u8 SFRAME_F_FDE_SORTED = 0x1;
static constexpr u8 SFRAME_F_FRAME_POINTER = 0x2;
static constexpr u8 SFRAME_F_FDE_FUNC_START_PCREL = 0x4;
static constexpr u8 SFRAME_ABI_AARCH64_ENDIAN_BIG = 1;
static constexpr u8 SFRAME_ABI_AARCH64_ENDIAN_LITTLE = 2;
static constexpr u8 SFRAME_ABI_AMD64_ENDIAN_LITTLE = 3;
static constexpr u8 SFRAME_ABI_S390X_ENDIAN_BIG = 4;
template <typename E>
struct SFrameHeader {
U16<E> magic;
u8 version;
u8 flags;
u8 abi_arch;
i8 cfa_fixed_fp_offset;
i8 cfa_fixed_ra_offset;
u8 auxhdr_len;
U32<E> num_fdes;
U32<E> num_fres;
U32<E> fre_len;
U32<E> fdeoff;
U32<E> freoff;
};
// The index part of an SFrame Version 3 FDE. The func_start_offset field
// is PC-relative (relative to its own address) when the section flag
// SFRAME_F_FDE_FUNC_START_PCREL is set, which is how mold always emits it.
template <typename E>
struct SFrameFdeIdx {
I64<E> func_start_offset;
U32<E> func_size;
U32<E> func_start_fre_off;
};
template <typename E> requires E::is_64
struct ElfSym<E> {
bool is_undef() const { return st_shndx == SHN_UNDEF; }
@@ -1809,6 +1867,7 @@ struct ElfRel<SH4BE> {
template <typename E> concept supports_ifunc = requires { E::R_IRELATIVE; };
template <typename E> concept supports_tlsdesc = requires { E::R_TLSDESC; };
template <typename E> concept supports_sframe = requires { E::sframe_abi; };
template <typename E> concept needs_thunk = requires { E::thunk_size; };
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
@@ -1852,6 +1911,7 @@ struct X86_64 {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_X86_64;
static constexpr u8 sframe_abi = SFRAME_ABI_AMD64_ENDIAN_LITTLE;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 8;
@@ -1867,6 +1927,7 @@ struct X86_64 {
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
static constexpr u32 R_SFRAME = R_X86_64_PC64;
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
};
@@ -1902,6 +1963,7 @@ struct ARM64LE {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_AARCH64;
static constexpr u8 sframe_abi = SFRAME_ABI_AARCH64_ENDIAN_LITTLE;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
@@ -1919,12 +1981,14 @@ struct ARM64LE {
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
static constexpr u32 R_SFRAME = R_AARCH64_PREL64;
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
};
struct ARM64BE : ARM64LE {
static constexpr std::string_view name = "arm64be";
static constexpr bool is_le = false;
static constexpr u8 sframe_abi = SFRAME_ABI_AARCH64_ENDIAN_BIG;
};
struct ARM32LE {
@@ -2097,6 +2161,7 @@ struct S390X {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_S390X;
static constexpr u8 sframe_abi = SFRAME_ABI_S390X_ENDIAN_BIG;
static constexpr u32 plt_hdr_size = 48;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
@@ -2111,6 +2176,7 @@ struct S390X {
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
static constexpr u32 R_SFRAME = R_390_PC64;
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
};
+1 -1
View File
@@ -191,7 +191,7 @@ get_machine_type(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
return get_elf_type(child->data);
return "";
case FileType::TEXT:
return Script(ctx, rctx, mf).get_script_output_type();
return Script(ctx, mf, &rctx).get_script_output_type();
default:
return "";
}
+67 -19
View File
@@ -5,31 +5,57 @@
#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(const InputSection<E> &isec) {
static bool should_keep(Context<E> &ctx, const InputSection<E> &isec) {
u32 type = isec.shdr().sh_type;
u32 flags = isec.shdr().sh_flags;
std::string_view name = isec.name();
if constexpr (is_ppc32<E>)
if (name == ".got2")
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 ||
name.starts_with(".ctors") ||
name.starts_with(".dtors") ||
name.starts_with(".init") ||
name.starts_with(".fini") ||
is_c_identifier(name);
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>
@@ -72,7 +98,7 @@ collect_root_set(Context<E> &ctx) {
continue;
}
if (should_keep(*isec))
if (should_keep(ctx, *isec))
enqueue_section(isec.get());
}
});
@@ -96,9 +122,18 @@ collect_root_set(Context<E> &ctx) {
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(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
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
@@ -106,7 +141,7 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
auto mark = [&](InputSection<E> *sec) {
if (mark_section(sec)) {
if (depth < 3)
visit(ctx, sec, feeder, depth + 1);
visit_section(ctx, sec, feeder, depth + 1, start_stop_map);
else
feeder.add(sec);
}
@@ -123,10 +158,21 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
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())
if (SectionFragment<E> *frag = sym.get_frag()) {
frag->is_alive = true;
else
mark(sym.get_input_section());
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>)
@@ -136,12 +182,13 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
// Mark all reachable sections
template <typename E>
static void mark(Context<E> &ctx,
tbb::concurrent_vector<InputSection<E> *> &rootset) {
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(ctx, isec, feeder, 0);
visit_section(ctx, isec, feeder, 0, start_stop_map);
});
}
@@ -190,7 +237,8 @@ template <typename E>
void gc_sections(Context<E> &ctx) {
Timer t(ctx, "gc");
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
mark(ctx, rootset);
StartStopMap<E> start_stop_map = build_start_stop_map(ctx);
mark(ctx, rootset, start_stop_map);
sweep(ctx);
}
+7 -6
View File
@@ -456,11 +456,8 @@ read_address_ranges(Context<E> &ctx, const Compunit &cu) {
Fatal(ctx) << "--gdb-index: missing DW_AT_rnglists_base";
u8 *base = buf + rnglists_base;
i64 num_offsets = *(U32<E> *)(base - 4);
Offset *offsets = (Offset *)base;
for (i64 i = 0; i < num_offsets; i++)
read_rnglist_range<E>(vec, base + offsets[i], addrx, low_pc.value);
read_rnglist_range<E>(vec, base + offsets[ranges.value], addrx, low_pc.value);
}
return vec;
}
@@ -783,8 +780,12 @@ void write_gdb_index(Context<E> &ctx) {
});
// Write the final counts into the buffer.
for (Entry *ent : entries)
*(ul32 *)(base + ent->value.type_offset) = ent->value.count;
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) {
+19 -7
View File
@@ -123,24 +123,24 @@ 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();
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
shdr.sh_type == SHT_NOBITS || is_c_identifier(isec.name))
return false;
if (shdr.sh_flags & SHF_EXECINSTR)
return (ctx.arg.icf_all || !isec.address_taken) &&
name != ".init" && name != ".fini";
isec.name != ".init" && isec.name != ".fini";
// .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 (name == ".gcc_except_table" || name.starts_with(".gcc_except_table."))
if (isec.name == ".gcc_except_table" ||
isec.name.starts_with(".gcc_except_table."))
return true;
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
bool is_relro = isec.name().starts_with(".data.rel.ro");
bool is_relro = isec.name.starts_with(".data.rel.ro");
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
(is_readonly || is_relro);
}
@@ -255,7 +255,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
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()) {
@@ -614,6 +614,18 @@ void icf_sections(Context<E> &ctx) {
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.
+188 -36
View File
@@ -93,6 +93,15 @@ InputFile<E>::InputFile(Context<E> &ctx, MappedFile *mf)
shstrtab = this->get_string(ctx, shstrtab_idx);
}
template <typename E>
void InputFile<E>::populate_symbol_names() {
symbol_names.reserve(elf_syms.size());
for (const ElfSym<E> &esym : elf_syms) {
const char *p = symbol_strtab.data() + esym.st_name;
symbol_names.emplace_back(p, strlen(p));
}
}
template <typename E>
ElfShdr<E> *InputFile<E>::find_section(i64 type) {
for (ElfShdr<E> &sec : elf_sections)
@@ -329,7 +338,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
signature = this->shstrtab.data() +
this->elf_sections[get_shndx(esym)].sh_name;
} else {
signature = this->symbol_strtab.data() + esym.st_name;
signature = this->symbol_names[shdr.sh_info];
}
// Ignore a broken comdat group GCC emits for .debug_macros.
@@ -449,6 +458,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
if (name == ".eh_frame")
eh_frame_sections.push_back(isec);
if constexpr (supports_sframe<E>)
if (name == ".sframe")
sframe_sections.push_back(isec);
if (name == ".debug_info" && !(shdr.sh_flags & SHF_ALLOC))
debug_info = isec;
@@ -624,6 +637,96 @@ void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
}
}
// Returns the byte length of the SFrame FRE block at offset `offset`:
// a 5-byte attribute header followed by a series of frame row
// entries, each of which is a start address (whose width is given by
// the attribute header), a one-byte info field and a number of
// variable-width data words encoded in that info field.
template <typename E>
static i64 sframe_fre_block_size(std::string_view data, i64 offset) {
const u8 *attr = (const u8 *)data.data() + offset;
i64 num_fres = *(U16<E> *)attr;
i64 addr_size = 1 << bits(attr[2], 3, 0);
i64 p = offset + 5;
for (i64 i = 0; i < num_fres; i++) {
u8 info = data[p + addr_size];
i64 num_words = bits(info, 4, 1);
i64 word_size = 1 << bits(info, 6, 5);
p += addr_size + 1 + num_words * word_size;
}
return p - offset;
}
// .sframe is a compact stack-unwinding format. Just like .eh_frame, the
// linker has to understand its contents: an output .sframe section is a
// single header followed by a PC-sorted index of function descriptor
// entries (FDEs) and a blob of frame row entries (FREs). This function
// parses an input .sframe section into individual FDEs so that we can
// later drop the dead ones, sort the survivors and rewrite the header.
//
// Unlike .eh_frame, the variable-length FRE data carries no relocations,
// so we copy it to the output verbatim. The only relocated field is each
// FDE's func_start, which points to the function the FDE describes; we
// use it both to find the function (for garbage collection and to obtain
// its output address) and to re-emit a PC-relative offset.
template <typename E>
void ObjectFile<E>::parse_sframe(Context<E> &ctx) requires supports_sframe<E> {
for (InputSection<E> *isec : sframe_sections) {
// The input section is consumed by the linker; it's not copied to the
// output as-is but reconstructed into ctx.sframe.
isec->is_alive = false;
std::string_view data = this->get_string(ctx, isec->shdr());
const SFrameHeader<E> &hdr = *(const SFrameHeader<E> *)data.data();
if (hdr.magic != SFRAME_MAGIC)
Fatal(ctx) << *isec << ": corrupted .sframe section";
if (hdr.abi_arch != E::sframe_abi)
Fatal(ctx) << *isec << ": .sframe is incompatible with " << E::name;
// We support only SFrame Version 3. A section written in an older
// version (e.g. by an old assembler) isn't an error; we just ignore
// it, so the functions it covers won't have SFrame info in the output.
if (hdr.version != 3)
continue;
i64 hdr_len = sizeof(SFrameHeader<E>) + hdr.auxhdr_len;
i64 num_fdes = hdr.num_fdes;
i64 fde_off = hdr_len + hdr.fdeoff;
i64 fre_off = hdr_len + hdr.freoff;
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
i64 rel_idx = 0;
for (i64 i = 0; i < num_fdes; i++) {
i64 idx_off = fde_off + i * sizeof(SFrameFdeIdx<E>);
const SFrameFdeIdx<E> &ent =
*(const SFrameFdeIdx<E> *)(data.data() + idx_off);
// Find the relocation for this FDE's func_start field. An FDE without
// one isn't tied to any function (`ld -r` can emit such dead FDEs),
// so we drop it.
while (rel_idx < rels.size() && rels[rel_idx].r_offset < idx_off)
rel_idx++;
if (rel_idx == rels.size() || rels[rel_idx].r_offset != idx_off)
continue;
const ElfRel<E> &rel = rels[rel_idx];
i64 off = fre_off + ent.func_start_fre_off;
SFrameFde<E> fde;
fde.isec = get_section(this->elf_syms[rel.r_sym]);
fde.sym = this->symbols[rel.r_sym];
fde.addend = rel.r_addend;
fde.fre = data.substr(off, sframe_fre_block_size<E>(data, off));
fde.func_size = ent.func_size;
fde.num_fres = *(U16<E> *)(data.data() + off);
sframe_fdes.push_back(fde);
}
}
}
template <typename E>
void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
if (this->elf_syms.empty())
@@ -646,7 +749,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
if (esym.st_type == STT_SECTION)
name = this->shstrtab.data() + this->elf_sections[get_shndx(esym)].sh_name;
else
name = this->symbol_strtab.data() + esym.st_name;
name = this->symbol_names[i];
Symbol<E> &sym = this->local_syms[i];
sym.set_name(name);
@@ -674,7 +777,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
has_common_symbol = true;
// Get a symbol name
std::string_view key = this->symbol_strtab.data() + esym.st_name;
std::string_view key = this->symbol_names[i];
std::string_view name = key;
// Parse symbol version after atsign
@@ -734,11 +837,21 @@ void ObjectFile<E>::convert_mergeable_sections(Context<E> &ctx) {
continue;
const ElfShdr<E> &shdr = isec->shdr();
if (!(shdr.sh_flags & SHF_MERGE))
if (!(shdr.sh_flags & SHF_MERGE) || !isec->is_alive)
continue;
// Under a SECTIONS-driven layout, sections matched by different
// output section descriptions merge separately
i64 cmd = -1;
i32 rank = INT32_MAX;
if (!this->script_match.empty())
if (i32 r = this->script_match[i]; r != -1) {
cmd = ctx.script_ranks[r].cmd;
rank = r;
}
MergedSection<E> *parent =
MergedSection<E>::get_instance(ctx, isec->name(), shdr);
MergedSection<E>::get_instance(ctx, isec->name, shdr, cmd, rank);
if (parent) {
this->mergeable_sections[i] =
@@ -889,6 +1002,7 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
this->first_global = symtab_sec->sh_info;
this->elf_syms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
this->populate_symbol_names();
if (ElfShdr<E> *shdr = this->find_section(SHT_SYMTAB_SHNDX))
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, *shdr);
@@ -1260,7 +1374,7 @@ void SharedFile<E>::parse(Context<E> &ctx) {
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
soname = get_soname(ctx);
version_strings = read_verdef(ctx);
version_strings = read_version_strings(ctx);
// Read a symbol table.
std::span<ElfSym<E>> esyms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
@@ -1270,20 +1384,28 @@ void SharedFile<E>::parse(Context<E> &ctx) {
vers = this->template get_data<U16<E>>(ctx, *sec);
for (i64 i = symtab_sec->sh_info; i < esyms.size(); i++) {
u16 ver;
if (vers.empty() || esyms[i].is_undef())
ver = VER_NDX_GLOBAL;
else
ver = vers[i] & ~VERSYM_HIDDEN;
u16 ver = vers.empty() ? VER_NDX_GLOBAL : (vers[i] & ~VERSYM_HIDDEN);
if (ver == VER_NDX_LOCAL)
// A defined symbol with VER_NDX_LOCAL is bound locally and isn't
// really exposed for dynamic linking; skip it. We never skip
// undefined references — the dynamic loader doesn't distinguish
// local vs global on the undef side, and dropping them would hide
// the DSO's reference from us.
if (ver == VER_NDX_LOCAL && !esyms[i].is_undef())
continue;
this->elf_syms2.push_back(esyms[i]);
this->versyms.push_back(ver);
// resolve_symbols only consults versyms[] for defined symbols
// (see SharedFile::resolve_symbols), so VER_NDX_GLOBAL is fine
// for undefined entries.
this->versyms.push_back(esyms[i].is_undef() ? (u16)VER_NDX_GLOBAL : ver);
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
bool has_version = ver != VER_NDX_GLOBAL && ver < version_strings.size() &&
!version_strings[ver].empty();
auto get_versioned_sym = [&] {
std::string_view key = save_string(
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
@@ -1304,12 +1426,13 @@ void SharedFile<E>::parse(Context<E> &ctx) {
// and `foo@VERSION` as usual, but with information to forward
// references to `foo@VERSION` to `foo`. After name resolution, we
// visit all symbol references to redirect `foo@VERSION` to `foo`.
if (vers.empty() || ver == VER_NDX_GLOBAL) {
if (!has_version) {
// Unversioned symbol
this->symbols.push_back(get_symbol(ctx, name));
this->symbols2.push_back(nullptr);
} else if (vers[i] & VERSYM_HIDDEN) {
// Versioned non-default symbol
} else if (esyms[i].is_undef() || (vers[i] & VERSYM_HIDDEN)) {
// Versioned non-default symbol, or undefined reference whose
// version comes from .gnu.version_r.
this->symbols.push_back(get_versioned_sym());
this->symbols2.push_back(nullptr);
} else {
@@ -1375,32 +1498,61 @@ std::string_view SharedFile<E>::get_dt_audit(Context<E> &ctx) {
// If an undefiend symbol `foo` is resolved to a symbol defined by the
// shared object, it's marked so that it'll be resolved to (`foo`, the
// default version of the library) at load-time.
//
// Reads .gnu.version_d and .gnu.version_r and returns a vector of
// version names indexed by versym value. The versym index space is
// shared between the two sections (vd_ndx for defined symbols and
// vna_other for undefined ones), so a single vector covers both.
template <typename E>
std::vector<std::string_view> SharedFile<E>::read_verdef(Context<E> &ctx) {
ElfShdr<E> *verdef_sec = this->find_section(SHT_GNU_VERDEF);
if (!verdef_sec)
return {};
std::string_view verdef = this->get_string(ctx, *verdef_sec);
std::string_view strtab = this->get_string(ctx, verdef_sec->sh_link);
std::vector<std::string_view>
SharedFile<E>::read_version_strings(Context<E> &ctx) {
std::vector<std::string_view> vec;
u8 *ptr = (u8 *)verdef.data();
for (;;) {
ElfVerdef<E> *ver = (ElfVerdef<E> *)ptr;
if (ver->vd_ndx == VER_NDX_UNSPECIFIED)
Fatal(ctx) << *this << ": symbol version too large";
if (ElfShdr<E> *sec = this->find_section(SHT_GNU_VERDEF)) {
std::string_view verdef = this->get_string(ctx, *sec);
std::string_view strtab = this->get_string(ctx, sec->sh_link);
u8 *ptr = (u8 *)verdef.data();
if (vec.size() <= ver->vd_ndx)
vec.resize(ver->vd_ndx + 1);
for (;;) {
ElfVerdef<E> *ver = (ElfVerdef<E> *)ptr;
if (ver->vd_ndx == VER_NDX_UNSPECIFIED)
Fatal(ctx) << *this << ": symbol version too large";
ElfVerdaux<E> *aux = (ElfVerdaux<E> *)(ptr + ver->vd_aux);
vec[ver->vd_ndx] = strtab.data() + aux->vda_name;
if (!ver->vd_next)
break;
ptr += ver->vd_next;
if (vec.size() <= ver->vd_ndx)
vec.resize(ver->vd_ndx + 1);
ElfVerdaux<E> *aux = (ElfVerdaux<E> *)(ptr + ver->vd_aux);
vec[ver->vd_ndx] = strtab.data() + aux->vda_name;
if (!ver->vd_next)
break;
ptr += ver->vd_next;
}
}
if (ElfShdr<E> *sec = this->find_section(SHT_GNU_VERNEED)) {
std::string_view verneed = this->get_string(ctx, *sec);
std::string_view strtab = this->get_string(ctx, sec->sh_link);
u8 *ptr = (u8 *)verneed.data();
for (;;) {
ElfVerneed<E> *vn = (ElfVerneed<E> *)ptr;
u8 *aux_ptr = ptr + vn->vn_aux;
for (i64 i = 0; i < vn->vn_cnt; i++) {
ElfVernaux<E> *aux = (ElfVernaux<E> *)aux_ptr;
u16 idx = aux->vna_other & ~VERSYM_HIDDEN;
if (vec.size() <= idx)
vec.resize(idx + 1);
vec[idx] = strtab.data() + aux->vna_name;
if (!aux->vna_next)
break;
aux_ptr += aux->vna_next;
}
if (!vn->vn_next)
break;
ptr += vn->vn_next;
}
}
return vec;
}
+5 -1
View File
@@ -33,8 +33,12 @@ bool cie_equals(const CieRecord<E> &a, const CieRecord<E> &b) {
template <typename E>
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
: file(file), shndx(shndx) {
if (shndx < file.elf_sections.size())
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];
+2593 -178
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -675,6 +675,7 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
obj->symbol_strtab = save_string(ctx, strtab);
obj->elf_syms = obj->lto_elf_syms;
obj->populate_symbol_names();
obj->initialize_symbols(ctx);
plugin_symbols.clear();
return obj;
+50 -11
View File
@@ -2,6 +2,7 @@
#include "config.h"
#include <cstring>
#include <filesystem>
#include <functional>
#include <sys/stat.h>
#include <sys/types.h>
@@ -32,6 +33,7 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
file->as_needed =
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
assert(rctx.tg);
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
@@ -71,6 +73,7 @@ new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
file->priority = ctx.file_priority++;
file->as_needed = rctx.as_needed;
assert(rctx.tg);
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
@@ -108,7 +111,7 @@ void read_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
}
return;
case FileType::TEXT:
Script(ctx, rctx, mf).parse_linker_script();
Script(ctx, mf, &rctx).parse_linker_script();
return;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
@@ -146,7 +149,7 @@ detect_machine_type(Context<E> &ctx, std::vector<std::string> args) {
if (MappedFile *mf = open_file(ctx, arg))
if (get_file_type(ctx, mf) == FileType::TEXT)
if (std::string_view target =
Script(ctx, rctx, mf).get_script_output_type();
Script(ctx, mf, &rctx).get_script_output_type();
!target.empty())
return target;
}
@@ -282,6 +285,12 @@ int mold_main(int argc, char **argv) {
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv))
process_run_subcommand(ctx, argc, argv);
// parse_nonpositional_args() may chdir(2) for -C. If we end up
// restarting in redo_main(), we need to re-enter from the original
// directory so relative paths (e.g. response files) still resolve.
std::error_code ec;
std::filesystem::path orig_cwd = std::filesystem::current_path(ec);
// Parse non-positional command line options
ctx.cmdline_args = expand_response_files(ctx, argv);
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
@@ -291,8 +300,11 @@ int mold_main(int argc, char **argv) {
ctx.arg.emulation = detect_machine_type(ctx, file_args);
// Redo if -m does not match with our speculation.
if (ctx.arg.emulation != E::name)
if (ctx.arg.emulation != E::name) {
if (!ec)
std::filesystem::current_path(orig_cwd, ec);
return redo_main<E>(ctx.arg.emulation, argc, argv);
}
Timer t_all(ctx, "all");
@@ -322,6 +334,13 @@ int mold_main(int argc, char **argv) {
// Parse input files
read_input_files(ctx, file_args);
// The entry point is given by -e, by an ENTRY command in a linker
// script, or defaults to _start
if (!ctx.arg.entry)
ctx.arg.entry = get_symbol(ctx, "_start");
ctx.arg.entry->gc_root = true;
ctx.arg.undefined.push_back(ctx.arg.entry);
// Uniquify shared object files by soname
{
std::unordered_set<std::string_view> seen;
@@ -358,9 +377,19 @@ int mold_main(int argc, char **argv) {
std::erase_if(ctx.objs, [](InputFile<E> *file) { return !file->is_reachable; });
std::erase_if(ctx.dsos, [](InputFile<E> *file) { return !file->is_reachable; });
// Remove input sections that match a /DISCARD/ pattern and
// compute, for each input section, the output section description
// it matches. This runs before mergeable sections are split
// because the patterns match the original input sections.
apply_script_discards(ctx);
match_script_sections(ctx);
// Parse .eh_frame section contents.
parse_eh_frame_sections(ctx);
// Parse .sframe section contents.
parse_sframe_sections(ctx);
// Split mergeable section contents into section pieces.
create_merged_sections(ctx);
@@ -375,6 +404,9 @@ int mold_main(int argc, char **argv) {
// Create .bss sections for common symbols.
convert_common_symbols(ctx);
// Define symbols PROVIDE'd by linker scripts.
add_provided_symbols(ctx);
// Apply version scripts.
apply_version_script(ctx);
@@ -507,12 +539,6 @@ int mold_main(int argc, char **argv) {
// within an output section to input sections.
compute_section_sizes(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.
@@ -539,6 +565,11 @@ int mold_main(int argc, char **argv) {
// Here, we construct output .eh_frame contents.
ctx.eh_frame->construct(ctx);
// .sframe is likewise parsed and reconstructed by the linker. Build
// the merged, PC-sorted output .sframe.
if constexpr (supports_sframe<E>)
ctx.sframe->construct(ctx);
// If --emit-relocs is given, we'll copy relocation sections from input
// files to an output file.
if (ctx.arg.emit_relocs)
@@ -593,12 +624,20 @@ int mold_main(int argc, char **argv) {
filesize = set_osec_offsets(ctx);
}
// At this point, both memory and file layouts are fixed.
// Gather thunk symbols and attach them to themselves.
if constexpr (needs_thunk<E>)
gather_thunk_addresses(ctx);
// Re-finalize layout. fix_synthetic_symbols above may have changed
// addends for dynamic relocations referencing synthetic symbols, which
// can shift the encoded size of .rela.dyn under --pack-dyn-relocs=android.
// For other modes this is a cheap no-op convergence.
filesize = set_osec_offsets(ctx);
// At this point, both memory and file layouts are fixed.
ctx.reldyn->update_shdr(ctx);
t_before_copy.stop();
// Create an output file
+443 -73
View File
@@ -25,7 +25,9 @@
#include <variant>
#include <vector>
#ifndef _WIN32
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
#endif
@@ -469,6 +471,21 @@ struct FdeRecord {
Atomic<bool> is_alive = true;
};
// Represents a single function descriptor entry (FDE) read from an input
// .sframe section. Unlike .eh_frame, the variable-length Frame Row Entry
// (FRE) data carries no relocations, so it can be copied to the output
// verbatim. Only the FDE's func_start field is relocated, which we
// resolve here and re-emit as a PC-relative offset.
template <typename E>
struct SFrameFde {
InputSection<E> *isec = nullptr; // the function's input section (liveness)
Symbol<E> *sym = nullptr; // symbol the func_start reloc points to
i64 addend = 0; // addend of the func_start reloc
std::string_view fre; // the FRE block (attr + FREs), copied as-is
u32 func_size = 0;
u32 num_fres = 0;
};
// A struct to hold target-dependent input section members.
template <typename E>
struct InputSectionExtras {};
@@ -512,7 +529,6 @@ public:
void apply_reloc_nonalloc(Context<E> &ctx, u8 *base);
void kill();
std::string_view name() const;
i64 get_priority() const;
u64 get_addr() const;
ElfShdr<E> &shdr() const;
@@ -531,6 +547,7 @@ public:
OutputSection<E> *output_section = nullptr;
i64 sh_size = -1;
std::string_view name;
std::string_view contents;
i32 fde_begin = -1;
@@ -539,13 +556,12 @@ public:
i64 offset = -1;
i32 shndx = -1;
i32 relsec_idx = -1;
i32 reldyn_offset = 0;
bool uncompressed = false;
// For COMDAT de-duplication and garbage collection
Atomic<bool> is_alive = true;
u8 p2align = 0;
Atomic<u8> p2align = 0;
// For ICF
Atomic<bool> address_taken = false;
@@ -647,12 +663,13 @@ public:
virtual bool is_header() { return false; }
virtual OutputSection<E> *to_osec() { return nullptr; }
virtual void compute_section_size(Context<E> &ctx) {}
virtual i64 get_reldyn_size(Context<E> &ctx) const { return 0; }
virtual void construct_relr(Context<E> &ctx) {}
virtual void copy_buf(Context<E> &ctx) {}
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
virtual void update_shdr(Context<E> &ctx) {}
virtual std::vector<ElfRel<E>>
collect_dynrels(Context<E> &ctx) const { return {}; }
std::string_view name;
ElfShdr<E> shdr = { .sh_addralign = 1 };
i64 shndx = 0;
@@ -673,14 +690,16 @@ public:
i64 strtab_size = 0;
i64 strtab_offset = 0;
// Offset in .rel.dyn
i64 reldyn_offset = 0;
// For --section-order
// The chunk's position in a SECTIONS-driven layout: 2n for the
// chunk described by the n'th script command, 2n+1 for an orphan
// placed after it
i64 sect_order = 0;
// For --pack-dyn-relocs=relr
std::vector<u64> relr;
// For linker scripts: the index of the output section description
// in Context<E>::script_sections this chunk was created for, and
// the load address (LMA) assigned by the script.
i32 script_cmd = -1;
u64 lma = 0;
};
// ELF header which is at the beginning of each ELF file.
@@ -756,7 +775,6 @@ public:
enum AbsRelKind {
ABS_REL_NONE,
ABS_REL_BASEREL,
ABS_REL_RELR,
ABS_REL_IFUNC,
ABS_REL_DYNREL,
};
@@ -773,6 +791,14 @@ struct AbsRel {
// OutputSection represents the usual output section that contains input
// sections read from object files.
// A linker script data command's (e.g. `LONG(0xdeadbeef)`)
// contribution to an output section
struct ScriptData {
u64 offset = 0;
i64 size = 0;
u64 value = 0;
};
template <typename E>
class OutputSection : public Chunk<E> {
public:
@@ -783,8 +809,7 @@ public:
OutputSection<E> *to_osec() override { return this; }
void compute_section_size(Context<E> &ctx) override;
i64 get_reldyn_size(Context<E> &ctx) const override;
void construct_relr(Context<E> &ctx) override;
std::vector<ElfRel<E>> collect_dynrels(Context<E> &ctx) const override;
void copy_buf(Context<E> &ctx) override;
void write_to(Context<E> &ctx, u8 *buf) override;
@@ -795,11 +820,20 @@ public:
void create_range_extension_thunks(Context<E> &ctx);
std::vector<InputSection<E> *> members;
std::vector<MergedSection<E> *> merged_members;
std::vector<std::unique_ptr<Thunk<E>>> thunks;
std::unique_ptr<RelocSection<E>> reloc_sec;
std::vector<AbsRel<E>> abs_rels;
Atomic<u32> sh_flags;
// The fill pattern for gaps between members, e.g. `=0xcccccccc` in
// a linker script. If absent, executable sections are filled with
// trap instructions and other sections with zeros.
std::optional<u32> fill;
// Data inserted by linker script BYTE/SHORT/LONG/QUAD commands
std::vector<ScriptData> script_data;
// Used only by create_output_sections()
std::vector<std::vector<InputSection<E> *>> members_vec;
};
@@ -831,10 +865,9 @@ public:
u64 get_tlsld_addr(Context<E> &ctx) const;
bool has_tlsld(Context<E> &ctx) const { return tlsld_idx != -1; }
i64 get_reldyn_size(Context<E> &ctx) const override;
std::vector<ElfRel<E>> collect_dynrels(Context<E> &ctx) const override;
void copy_buf(Context<E> &ctx) override;
void construct_relr(Context<E> &ctx) override;
void compute_symtab_size(Context<E> &ctx) override;
void populate_symtab(Context<E> &ctx) override;
@@ -938,15 +971,27 @@ public:
template <typename E>
class RelDynSection : public Chunk<E> {
public:
RelDynSection() {
RelDynSection(Context<E> &ctx) {
this->name = E::is_rela ? ".rela.dyn" : ".rel.dyn";
this->shdr.sh_type = E::is_rela ? SHT_RELA : SHT_REL;
this->shdr.sh_flags = SHF_ALLOC;
this->shdr.sh_entsize = sizeof(ElfRel<E>);
this->shdr.sh_addralign = sizeof(Word<E>);
if (ctx.arg.pack_dyn_relocs_android) {
this->shdr.sh_type = E::is_rela ? SHT_ANDROID_RELA : SHT_ANDROID_REL;
this->shdr.sh_entsize = 0;
this->shdr.sh_addralign = 1;
} else {
this->shdr.sh_type = E::is_rela ? SHT_RELA : SHT_REL;
this->shdr.sh_entsize = sizeof(ElfRel<E>);
this->shdr.sh_addralign = sizeof(Word<E>);
}
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
std::vector<ElfRel<E>> relocs;
std::vector<u8> android_encoded;
bool keep_android_size = false;
};
// .relr.dyn is a relatively new section to contain base relocation
@@ -985,16 +1030,17 @@ public:
template <typename E>
class RelrDynSection : public Chunk<E> {
public:
RelrDynSection() {
RelrDynSection(Context<E> &ctx) {
this->name = ".relr.dyn";
this->shdr.sh_type = SHT_RELR;
this->shdr.sh_type = ctx.arg.use_android_relr_tags ? SHT_ANDROID_RELR : SHT_RELR;
this->shdr.sh_flags = SHF_ALLOC;
this->shdr.sh_entsize = sizeof(Word<E>);
this->shdr.sh_addralign = sizeof(Word<E>);
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
std::vector<u64> relocs;
};
// .strtab is referenced by .strtab and contains symbol names. Note that
@@ -1200,7 +1246,8 @@ template <typename E>
class MergedSection : public Chunk<E> {
public:
static MergedSection<E> *
get_instance(Context<E> &ctx, std::string_view name, const ElfShdr<E> &shdr);
get_instance(Context<E> &ctx, std::string_view name, const ElfShdr<E> &shdr,
i64 script_cmd = -1, i32 script_rank = INT32_MAX);
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
u64 hash, i64 p2align);
@@ -1214,6 +1261,12 @@ public:
std::vector<MergeableSection<E> *> members;
std::mutex mu;
// Under a SECTIONS-driven layout, a merged section may belong to an
// output section described by the script; it is then laid out and
// written as part of it.
OutputSection<E> *script_owner = nullptr;
std::atomic_int32_t script_rank = INT32_MAX;
ConcurrentMap<SectionFragment<E>> map;
HyperLogLog estimator;
bool resolved = false;
@@ -1285,6 +1338,48 @@ public:
void copy_buf(Context<E> &ctx) override;
};
// .sframe is a compact stack-unwinding format. Like .eh_frame, the linker
// has to parse and reconstruct it: the output section is a single sorted
// index of FDEs (one per live function) followed by their FREs, so we
// gather the live FDEs from all input files, drop dead ones, concatenate
// their FREs, sort the index by PC and rewrite the header. mold reads and
// writes SFrame Version 3.
template <typename E>
class SFrameSection : public Chunk<E> {
public:
SFrameSection() {
this->name = ".sframe";
this->shdr.sh_type = SHT_GNU_SFRAME;
this->shdr.sh_flags = SHF_ALLOC;
this->shdr.sh_addralign = 8;
}
void construct(Context<E> &ctx) requires supports_sframe<E>;
void copy_buf(Context<E> &ctx) override;
SFrameHeader<E> hdr = {};
std::vector<SFrameFde<E> *> fdes;
};
// SFrameRelocSection holds the relocations for .sframe. We use it only for
// relocatable outputs, where function addresses aren't known yet and each
// FDE's func_start has to remain a relocation for the final link to
// resolve. It is the .sframe counterpart of EhFrameRelocSection.
template <typename E>
class SFrameRelocSection : public Chunk<E> {
public:
SFrameRelocSection() {
this->name = ".rela.sframe";
this->shdr.sh_type = SHT_RELA;
this->shdr.sh_flags = SHF_INFO_LINK;
this->shdr.sh_addralign = sizeof(Word<E>);
this->shdr.sh_entsize = sizeof(ElfRel<E>);
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
};
// .copyrel and .copyrel.rel.ro represent memory regions to which the
// runtime copies symbols from other ELF files for copy relocations.
template <typename E>
@@ -1298,8 +1393,7 @@ public:
}
void add_symbol(Context<E> &ctx, Symbol<E> *sym);
i64 get_reldyn_size(Context<E> &ctx) const override { return symbols.size(); }
void copy_buf(Context<E> &ctx) override;
std::vector<ElfRel<E>> collect_dynrels(Context<E> &ctx) const override;
std::vector<Symbol<E> *> symbols;
};
@@ -1709,6 +1803,11 @@ public:
std::string_view shstrtab;
std::string_view symbol_strtab;
// Parallel to elf_syms; cached to avoid per-call strlen.
std::vector<std::string_view> symbol_names;
void populate_symbol_names();
bool as_needed = false;
bool has_init_array = false;
bool has_ctors = false;
@@ -1756,6 +1855,7 @@ public:
void parse(Context<E> &ctx);
void initialize_symbols(Context<E> &ctx);
void parse_ehframe(Context<E> &ctx);
void parse_sframe(Context<E> &ctx) requires supports_sframe<E>;
void convert_mergeable_sections(Context<E> &ctx);
void reattach_section_pieces(Context<E> &ctx);
void resolve_symbols(Context<E> &ctx) override;
@@ -1772,6 +1872,12 @@ public:
std::string archive_name;
std::vector<std::unique_ptr<InputSection<E>>> sections;
// Per-section linker script match ranks, indexed by section index.
// -1 means the section is not matched by any SECTIONS pattern.
// Empty unless a linker script has a SECTIONS command.
std::vector<i32> script_match;
std::vector<std::unique_ptr<MergeableSection<E>>> mergeable_sections;
std::vector<ElfShdr<E>> elf_sections2;
std::vector<CieRecord<E>> cies;
@@ -1779,6 +1885,8 @@ public:
std::vector<bool> has_symver;
std::vector<ComdatGroupRef<E>> comdat_groups;
std::vector<InputSection<E> *> eh_frame_sections;
std::vector<InputSection<E> *> sframe_sections;
std::vector<SFrameFde<E>> sframe_fdes;
std::vector<std::vector<ElfRel<E>>> decoded_crel;
bool exclude_libs = false;
std::map<u32, u32> gnu_properties;
@@ -1852,7 +1960,7 @@ private:
std::string get_soname(Context<E> &ctx);
void maybe_override_symbol(Symbol<E> &sym, const ElfSym<E> &esym);
std::vector<std::string_view> read_dt_needed(Context<E> &ctx);
std::vector<std::string_view> read_verdef(Context<E> &ctx);
std::vector<std::string_view> read_version_strings(Context<E> &ctx);
std::vector<u16> versyms;
const ElfShdr<E> *symtab_sec;
@@ -1880,11 +1988,169 @@ struct DynamicPattern {
bool is_cpp = false;
};
// ScriptExpr represents an expression in the linker script language.
// For simplicity, we use a single type for all kinds of expressions
// instead of a class hierarchy.
struct ScriptExpr {
enum Kind { INT, NAME, DOT, UNARY, BINARY, TERNARY, FUNC };
Kind kind = INT;
u64 value = 0; // integer literal value
std::string_view str; // name, operator or function name
std::vector<ScriptExpr> args; // operands
};
// A sort specifier for input section patterns. REVERSE composes with
// the others, so a pattern holds a list of these, outermost first.
enum class ScriptSort : u8 { NONE, NAME, ALIGNMENT, INIT_PRIORITY, REVERSE };
// A pattern group in an input section description. For example,
// `*(SORT_BY_NAME(.ctors.*))` contains one group whose `sorts` is
// {NAME} and whose `pats` is {".ctors.*"}.
struct ScriptPattern {
std::vector<ScriptSort> sorts;
std::vector<std::string_view> exclude_files; // EXCLUDE_FILE(...)
std::vector<std::string_view> pats;
};
// A memory region defined in a MEMORY { ... } command
struct ScriptRegion {
std::string_view loc; // command's first token for error reporting
std::string_view name;
std::string_view attrs; // e.g. "rwx" in `ram (rwx) : ...`
ScriptExpr origin;
ScriptExpr length;
i64 alias_of = -1; // for REGION_ALIAS
};
// A program header defined in a PHDRS { ... } command
struct ScriptPhdr {
std::string_view loc;
std::string_view name;
std::string_view type; // e.g. "PT_LOAD"
bool filehdr = false;
bool phdrs = false;
std::optional<ScriptExpr> at;
std::optional<ScriptExpr> flags;
};
// ScriptCmd represents a single linker script command. Commands form
// a tree; SECTIONS, OVERLAY and output section descriptions contain
// subcommands in `cmds`. As with ScriptExpr, we use a single type for
// all kinds of commands. Each kind uses only a subset of the fields.
struct ScriptCmd {
enum Kind {
SECTIONS, // SECTIONS { <cmds> }
OUTPUT_SECTION, // <name> <addr> (<type>) : { <cmds> } <region> ...
OVERLAY, // OVERLAY <addr> : { <cmds> } <region> ...
INPUT_SECTION, // KEEP(<file_pat>(<pats>))
ASSIGNMENT, // <name> <op> <value>;
DATA, // BYTE(<value>), SHORT, LONG, QUAD or SQUAD
FILL, // FILL(<value>)
ASCIZ, // ASCIZ "<msg>"
CONSTRUCTORS, // CONSTRUCTORS
ASSERT, // ASSERT(<value>, "<msg>")
ENTRY, // ENTRY(<name>)
MEMORY, // MEMORY { <memory> }
PHDRS, // PHDRS { <phdrs> }
INSERT, // INSERT BEFORE|AFTER <name>
REGION_ALIAS, // REGION_ALIAS(<name2>, <name>)
OTHER, // an unsupported command such as NOCROSSREFS
};
Kind kind;
std::string_view loc; // command's first token for error reporting
std::string_view name;
std::vector<ScriptCmd> cmds;
// For ASSIGNMENT, an index into Context<E>::script_assignments.
// For INPUT_SECTION, the first match rank (see ScriptMatchRank).
i32 aux = -1;
// For ASSIGNMENT
std::string_view op; // "=", "+=", etc.
bool provide = false;
bool hidden = false;
std::optional<ScriptExpr> value; // assignment RHS, data value or condition
std::string_view msg; // ASSERT or ASCIZ string
i64 data_size = 0; // 1, 2, 4 or 8 for DATA
// For INPUT_SECTION
bool keep = false; // KEEP(...)
std::string_view file_pat;
std::vector<ScriptSort> file_sorts;
std::vector<ScriptPattern> pats;
std::vector<std::string_view> exclude_files; // EXCLUDE_FILE outside parens
std::vector<std::string_view> names; // INPUT_SECTION_FLAGS or OTHER args
// For OUTPUT_SECTION and OVERLAY
std::optional<ScriptExpr> addr;
std::string_view type; // e.g. "NOLOAD"
std::optional<ScriptExpr> at; // AT(...)
std::optional<ScriptExpr> align; // ALIGN(...)
bool align_with_input = false;
std::optional<ScriptExpr> subalign; // SUBALIGN(...)
std::string_view constraint; // ONLY_IF_RO or ONLY_IF_RW
std::string_view region; // >region
std::string_view lma_region; // AT>region
std::vector<std::string_view> phdr_refs; // :phdr
std::optional<ScriptExpr> fill; // =<fill>
// For INSERT and REGION_ALIAS
bool insert_before = false;
std::string_view name2;
// For MEMORY and PHDRS
std::vector<ScriptRegion> memory;
std::vector<ScriptPhdr> phdrs;
};
// A linker script symbol assignment such as `end = ALIGN(16);` whose
// value can be computed only after the output file layout is fixed.
// Until then, it is kept in Context<E>::script_assignments.
template <typename E>
struct ScriptAssignment {
Symbol<E> *sym = nullptr;
ScriptExpr value;
bool provide = false;
bool hidden = false;
bool defined = false; // true if we are the symbol's definer
MappedFile *mf = nullptr; // for error reporting
std::string_view loc;
// True if the assignment appeared within SECTIONS. Such assignments
// are executed by the layout pass, where the location counter is
// live; the others are evaluated after the layout is fixed.
bool in_sections = false;
};
// The result of matching an input section against the section
// patterns in SECTIONS. Each pattern group has a rank; an output
// section's members are ordered by the rank of the pattern that
// matched them, which mirrors the order patterns appear in the script.
struct ScriptMatchRank {
i32 cmd = -1; // index into Context<E>::script_sections
bool keep = false;
std::vector<ScriptSort> sorts;
};
// A linker script ASSERT command, evaluated after layout
struct ScriptAssert {
ScriptExpr cond;
std::string_view msg;
MappedFile *mf = nullptr;
std::string_view loc;
};
template <typename E>
class Script {
public:
Script(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf)
: ctx(ctx), rctx(rctx), mf(mf) {}
// rctx is the command line reader's state, which is needed only to
// execute commands that read other input files, such as GROUP.
// Version scripts and dynamic lists are parsed without one.
Script(Context<E> &ctx, MappedFile *mf, ReaderContext *rctx = nullptr)
: ctx(ctx), rctx(rctx), mf(mf), input(mf->get_contents()) {}
std::string_view get_script_output_type();
void parse_linker_script();
@@ -1892,40 +2158,111 @@ public:
std::vector<DynamicPattern> parse_dynamic_list();
private:
// The language is context-dependent; e.g. `*` is a wildcard in a
// glob pattern but a multiplication operator in an expression, so
// the parser switches the lexer's behavior with these modes.
enum LexMode : u8 { LEX_PATH, LEX_GLOB, LEX_EXPR };
[[noreturn]] void error(std::string_view pos, std::string msg);
void tokenize();
std::string_view lex_one();
void set_lex_mode(LexMode mode);
std::span<std::string_view>
skip(std::span<std::string_view> tok, std::string_view str);
std::string_view peek(i64 n = 0);
std::string_view next();
bool at_eof();
bool consume(std::string_view str);
void skip(std::string_view str);
bool consume_label(std::string label);
bool consume_colon();
std::span<std::string_view> read_output_format(std::span<std::string_view> tok);
std::span<std::string_view> read_group(std::span<std::string_view> tok);
void read_command();
void read_output_format();
void read_group();
void read_include();
std::span<std::string_view>
read_version_script_commands(std::span<std::string_view> tok,
std::string_view ver_str, u16 ver_idx,
bool is_global, bool is_cpp);
ScriptExpr read_expr();
ScriptExpr read_expr1(ScriptExpr lhs, i64 min_prec);
ScriptExpr read_primary();
std::span<std::string_view> read_version_script(std::span<std::string_view> tok);
ScriptCmd read_assignment();
ScriptCmd read_assert();
ScriptCmd read_sections();
ScriptCmd read_output_section();
void read_output_section_body(ScriptCmd &cmd);
void read_output_section_tail(ScriptCmd &cmd);
ScriptCmd read_input_section();
ScriptPattern read_pattern();
ScriptCmd read_overlay();
ScriptCmd read_memory();
ScriptCmd read_phdrs();
void evaluate();
void evaluate_command(ScriptCmd &cmd);
void evaluate_sections(ScriptCmd &cmd);
void register_assignment(ScriptCmd &cmd);
MappedFile *file_of(std::string_view pos);
void read_version_script();
void read_version_script_commands(std::string_view ver_str, u16 ver_idx,
bool is_global, bool is_cpp);
MappedFile *resolve_path(std::string_view tok, bool check_target);
std::span<std::string_view>
read_dynamic_list_commands(std::span<std::string_view> tok,
std::vector<DynamicPattern> &result, bool is_cpp);
void read_dynamic_list_commands(std::vector<DynamicPattern> &result,
bool is_cpp);
ReaderContext &reader();
Context<E> &ctx;
ReaderContext &rctx;
MappedFile *mf = mf;
std::once_flag once;
ReaderContext *rctx;
MappedFile *mf;
// The unprocessed part of the script being parsed. Tokens are lexed
// on demand because how text should be tokenized depends on context.
std::string_view input;
// Tokens lexed so far. tokens[pos] is the next token to be read.
std::vector<std::string_view> tokens;
i64 pos = 0;
LexMode lex_mode = LEX_PATH;
// Parsed commands
std::vector<ScriptCmd> cmds;
// Files suspended by INCLUDE and all files read so far. The latter
// is kept so that an error message for a token can be attributed to
// the file the token came from.
std::vector<std::pair<MappedFile *, std::string_view>> include_stack;
std::vector<MappedFile *> files = {mf};
};
template <typename E>
std::vector<DynamicPattern>
parse_dynamic_list(Context<E> &ctx, std::string_view path);
template <typename E>
void eval_script_commands(Context<E> &ctx);
template <typename E>
void apply_script_discards(Context<E> &ctx);
template <typename E>
void match_script_sections(Context<E> &ctx);
template <typename E>
void match_synthetic_sections(Context<E> &ctx);
template <typename E>
void script_finalize_section(Context<E> &ctx, OutputSection<E> &osec);
template <typename E>
void set_virtual_addresses_by_script(Context<E> &ctx);
template <typename E>
std::vector<ElfPhdr<E>> create_script_phdrs(Context<E> &ctx);
//
// archive-file.cc
//
@@ -1978,6 +2315,13 @@ template <typename E>
i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
InputSection<E> &isec, const ElfRel<E> &rel);
// With --emit-relocs, rewrites the relocation types in `buf` (the output
// relocations for isec, one per input relocation) to match the instructions
// that instruction relaxation produced. Defined only for targets that support
// relaxation.
template <typename E> requires is_riscv<E> || is_loongarch<E>
void rewrite_reloc_types(Context<E> &ctx, InputSection<E> &isec, ElfRel<E> *buf);
//
// gc-sections.cc
//
@@ -2042,9 +2386,11 @@ template <typename E> void set_file_priority(Context<E> &);
template <typename E> void resolve_symbols(Context<E> &);
template <typename E> void do_lto(Context<E> &);
template <typename E> void parse_eh_frame_sections(Context<E> &);
template <typename E> void parse_sframe_sections(Context<E> &);
template <typename E> void create_merged_sections(Context<E> &);
template <typename E> void convert_common_symbols(Context<E> &);
template <typename E> void create_output_sections(Context<E> &);
template <typename E> void add_provided_symbols(Context<E> &);
template <typename E> void add_synthetic_symbols(Context<E> &);
template <typename E> void apply_section_align(Context<E> &);
template <typename E> void check_cet_errors(Context<E> &);
@@ -2064,7 +2410,6 @@ template <typename E> void sort_output_sections(Context<E> &);
template <typename E> void claim_unresolved_symbols(Context<E> &);
template <typename E> void scan_relocations(Context<E> &);
template <typename E> void compute_imported_symbol_weakness(Context<E> &);
template <typename E> void construct_relr(Context<E> &);
template <typename E> void sort_dynsyms(Context<E> &);
template <typename E> void sort_debug_info_sections(Context<E> &);
template <typename E> void create_output_symtab(Context<E> &);
@@ -2164,7 +2509,7 @@ public:
}
void add_symbol(Context<PPC64V1> &ctx, Symbol<PPC64V1> *sym);
i64 get_reldyn_size(Context<PPC64V1> &ctx) const override;
std::vector<ElfRel<PPC64V1>> collect_dynrels(Context<PPC64V1> &ctx) const override;
void copy_buf(Context<PPC64V1> &ctx) override;
static constexpr i64 ENTRY_SIZE = sizeof(Word<PPC64V1>) * 3;
@@ -2194,6 +2539,13 @@ public:
template <> u64 get_eflags(Context<PPC64V2> &ctx);
//
// arch-sparc64.cc
//
i64 sparc_plt_ptr_offset(Context<SPARC64> &ctx, i64 pltidx);
//
// main.cc
//
@@ -2257,13 +2609,6 @@ struct VersionPattern {
bool is_cpp = false;
};
struct SectionOrder {
enum { NONE, SECTION, GROUP, ADDR, ALIGN, SYMBOL } type = NONE;
std::string name;
u64 value = 0;
std::string_view token; // for error reporting
};
// Target-specific context members
template <typename E>
struct ContextExtras {};
@@ -2313,7 +2658,6 @@ struct ContextExtras<SPARC64> {
template <typename E>
struct Context {
Context() {
arg.entry = get_symbol(*this, "_start");
arg.fini = get_symbol(*this, "_fini");
arg.init = get_symbol(*this, "_init");
@@ -2353,6 +2697,7 @@ struct Context {
bool detach = true;
bool discard_all = false;
bool discard_locals = false;
std::string_view orphan_handling = "place";
bool dynamic_list_data = false;
bool eh_frame_hdr = true;
bool emit_relocs = false;
@@ -2373,6 +2718,7 @@ struct Context {
bool noinhibit_exec = false;
bool oformat_binary = false;
bool omagic = false;
bool pack_dyn_relocs_android = false;
bool pack_dyn_relocs_relr = false;
bool perf = false;
bool pic = false;
@@ -2394,6 +2740,7 @@ struct Context {
bool suppress_warnings = false;
bool trace = false;
bool undefined_version = false;
bool use_android_relr_tags = false;
bool warn_common = false;
bool warn_once = false;
bool warn_textrel = false;
@@ -2420,13 +2767,13 @@ struct Context {
bool z_text = false;
bool zero_to_bss = false;
i64 compress_debug_sections = ELFCOMPRESS_NONE;
i64 compress_debug_sections_level = 0;
i64 filler = -1;
i64 spare_dynamic_tags = 5;
i64 spare_program_headers = 0;
i64 z_stack_size = 0;
std::optional<i64> thread_count;
std::optional<std::vector<Symbol<E> *>> retain_symbols_file;
std::optional<u64> physical_image_base;
std::string Map;
std::string audit;
std::string chroot;
@@ -2452,7 +2799,6 @@ struct Context {
std::unordered_set<std::string_view> exclude_libs;
std::unordered_set<std::string_view> ignore_ir_file;
std::unordered_set<std::string_view> wrap;
std::vector<SectionOrder> section_order;
std::vector<Symbol<E> *> require_defined;
std::vector<Symbol<E> *> undefined;
std::vector<std::pair<Symbol<E> *, std::variant<Symbol<E> *, u64>>> defsyms;
@@ -2470,6 +2816,13 @@ struct Context {
std::optional<tbb::global_control> global_limit;
std::vector<VersionPattern> version_patterns;
std::vector<DynamicPattern> dynamic_list_patterns;
std::vector<ScriptAssignment<E>> script_assignments;
std::vector<ScriptAssert> script_asserts;
std::vector<ScriptCmd> script_discards;
std::vector<ScriptCmd> script_sections;
std::vector<ScriptPhdr> script_phdrs;
std::vector<ScriptRegion> script_regions;
std::vector<ScriptMatchRank> script_ranks;
i64 default_version = VER_NDX_UNSPECIFIED;
i64 page_size = E::page_size;
bool has_error = false;
@@ -2545,6 +2898,8 @@ struct Context {
EhFrameSection<E> *eh_frame = nullptr;
EhFrameHdrSection<E> *eh_frame_hdr = nullptr;
EhFrameRelocSection<E> *eh_frame_reloc = nullptr;
SFrameSection<E> *sframe = nullptr;
SFrameRelocSection<E> *sframe_reloc = nullptr;
CopyrelSection<E> *copyrel = nullptr;
CopyrelSection<E> *copyrel_relro = nullptr;
VersymSection<E> *versym = nullptr;
@@ -2922,7 +3277,7 @@ std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
template <typename E>
inline std::ostream &
operator<<(std::ostream &out, const InputSection<E> &isec) {
out << isec.file << ":(" << isec.name() << ")";
out << isec.file << ":(" << isec.name << ")";
return out;
}
@@ -2938,13 +3293,6 @@ inline u64 InputSection<E>::get_addr() const {
return output_section->shdr.sh_addr + offset;
}
template <typename E>
inline std::string_view InputSection<E>::name() const {
if (file.elf_sections.size() <= shndx)
return (shdr().sh_flags & SHF_TLS) ? ".tls_common" : ".common";
return file.shstrtab.data() + file.elf_sections[shndx].sh_name;
}
template <typename E>
inline i64 InputSection<E>::get_priority() const {
return ((i64)file.priority << 32) | shndx;
@@ -3038,20 +3386,19 @@ InputSection<E>::get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag) {
if (!isec || isec->is_alive)
return {};
std::string_view str = name();
if (!str.starts_with(".debug_"))
if (!name.starts_with(".debug_"))
return {};
// If the section was dead due to ICF, we don't want to emit debug
// info for that section but want to set real values to .debug_line so
// that users can set a breakpoint inside a merged section.
if (isec->icf_removed() && str == ".debug_line")
if (isec->icf_removed() && name == ".debug_line")
return {};
// 0 is an invalid value in most debug info sections, so we use it
// as a tombstone value. .debug_loc and .debug_ranges reserve 0 as
// the terminator marker, so we use 1 if that'str the case.
return (str == ".debug_loc" || str == ".debug_ranges") ? 1 : 0;
// the terminator marker, so we use 1 if that's the case.
return (name == ".debug_loc" || name == ".debug_ranges") ? 1 : 0;
}
template <typename E>
@@ -3186,7 +3533,7 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
if (isec->icf_removed())
return isec->leader->get_addr() + value;
if (isec->name() == ".eh_frame") {
if (isec->name == ".eh_frame") {
// .eh_frame contents are parsed and reconstructed by the linker,
// so pointing to a specific location in a source .eh_frame
// section doesn't make much sense. However, CRT files contain
@@ -3255,6 +3602,12 @@ inline u64 Symbol<E>::get_tlsdesc_addr(Context<E> &ctx) const {
return ctx.got->shdr.sh_addr + get_tlsdesc_idx(ctx) * sizeof(Word<E>);
}
// On SPARC, .plt uses 32-byte "small" entries until it grows past 0x100000
// bytes (the reach of a small entry's branch to the resolver), after which
// it switches to a "large" entry format. This is how many small entries fit.
constexpr i64 sparc_num_small_plt =
(0x100000 - SPARC64::plt_hdr_size) / SPARC64::plt_size;
template <typename E>
inline u64 to_plt_offset(i32 pltidx) {
if constexpr (is_ppc64v1<E>) {
@@ -3265,6 +3618,15 @@ inline u64 to_plt_offset(i32 pltidx) {
if (pltidx < 0x8000)
return E::plt_hdr_size + pltidx * 8;
return E::plt_hdr_size + 0x8000 * 8 + (pltidx - 0x8000) * 12;
} else if constexpr (is_sparc<E>) {
// SPARC large PLT entries are grouped into blocks of 160, each holding
// 160 24-byte code stubs followed by 160 8-byte data pointers (so a
// stub's `ldx` reaches its pointer within a signed 13-bit offset). This
// returns the offset of pltidx's code stub.
if (pltidx < sparc_num_small_plt)
return E::plt_hdr_size + pltidx * E::plt_size;
i64 i = pltidx - sparc_num_small_plt;
return 0x100000 + (i / 160) * 5120 + (i % 160) * 24;
} else {
return E::plt_hdr_size + pltidx * E::plt_size;
}
@@ -3451,7 +3813,15 @@ template <typename E>
inline bool Symbol<E>::is_local(Context<E> &ctx) const {
if (ctx.arg.relocatable)
return esym().st_bind == STB_LOCAL;
return !is_imported && !is_exported;
// A global symbol keeps its binding in the output symbol table as
// in GNU ld and LLD, except that hidden visibility or a version
// script's `local:` directive reduces it to local. Tools do depend on the binding; for example, the Linux
// kernel's build extracts addresses of global symbols from the nm
// output of a previous link.
return esym().st_bind == STB_LOCAL || visibility == STV_HIDDEN ||
visibility == STV_INTERNAL ||
(!is_imported && ver_idx == VER_NDX_LOCAL);
}
template <typename E>
+535 -207
View File
File diff suppressed because it is too large. Load diff
+427 -266
View File
@@ -113,22 +113,16 @@ void create_synthetic_sections(Context<E> &ctx) {
};
if (!ctx.arg.oformat_binary) {
auto find = [&](std::string_view name) {
for (SectionOrder &ord : ctx.arg.section_order)
if (ord.type == SectionOrder::SECTION && ord.name == name)
return true;
return false;
};
// With an explicit PHDRS command, the ELF and program headers are
// mapped to memory only if a segment asks for them with FILEHDR or
// PHDRS. Without one, the layout pass maps them if they fit in
// front of the first section, so leave them allocated for now.
bool hdrs = ctx.script_phdrs.empty();
for (ScriptPhdr &p : ctx.script_phdrs)
hdrs |= (p.filehdr || p.phdrs);
if (ctx.arg.section_order.empty() || find("EHDR"))
ctx.ehdr = push(new OutputEhdr<E>(SHF_ALLOC));
else
ctx.ehdr = push(new OutputEhdr<E>(0));
if (ctx.arg.section_order.empty() || find("PHDR"))
ctx.phdr = push(new OutputPhdr<E>(SHF_ALLOC));
else
ctx.phdr = push(new OutputPhdr<E>(0));
ctx.ehdr = push(new OutputEhdr<E>(hdrs ? (u64)SHF_ALLOC : 0));
ctx.phdr = push(new OutputPhdr<E>(hdrs ? (u64)SHF_ALLOC : 0));
if (ctx.arg.z_sectionheader)
ctx.shdr = push(new OutputShdr<E>);
@@ -139,11 +133,11 @@ void create_synthetic_sections(Context<E> &ctx) {
if constexpr (!is_sparc<E>)
ctx.gotplt = push(new GotPltSection<E>(ctx));
ctx.reldyn = push(new RelDynSection<E>);
ctx.reldyn = push(new RelDynSection<E>(ctx));
ctx.relplt = push(new RelPltSection<E>);
if (ctx.arg.pack_dyn_relocs_relr)
ctx.relrdyn = push(new RelrDynSection<E>);
ctx.relrdyn = push(new RelrDynSection<E>(ctx));
ctx.strtab = push(new StrtabSection<E>);
ctx.plt = push(new PltSection<E>);
@@ -152,6 +146,7 @@ void create_synthetic_sections(Context<E> &ctx) {
ctx.dynsym = push(new DynsymSection<E>);
ctx.dynstr = push(new DynstrSection<E>);
ctx.eh_frame = push(new EhFrameSection<E>);
ctx.sframe = push(new SFrameSection<E>);
ctx.copyrel = push(new CopyrelSection<E>(false));
ctx.copyrel_relro = push(new CopyrelSection<E>(true));
@@ -166,7 +161,7 @@ void create_synthetic_sections(Context<E> &ctx) {
ctx.eh_frame_hdr = push(new EhFrameHdrSection<E>);
if (ctx.arg.gdb_index && has_debug_info_section(ctx))
ctx.gdb_index = push(new GdbIndexSection<E>);
if (ctx.arg.z_relro && ctx.arg.section_order.empty())
if (ctx.arg.z_relro && ctx.script_sections.empty())
ctx.relro_padding = push(new RelroPaddingSection<E>);
if (ctx.arg.hash_style_sysv)
ctx.hash = push(new HashSection<E>);
@@ -374,12 +369,11 @@ void resolve_symbols(Context<E> &ctx) {
//
// Therefore, only let IR files claim ownership for COMDAT keys that have
// no reachable regular-object owner.
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (ObjectFile<E> *file : ctx.objs)
if (file->is_reachable)
for (ComdatGroup *g : file->lto_comdat_groups)
if (g && g->owner == (u32)-1)
update_minimum(g->owner, file->priority);
});
g->owner = file->priority;
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
if (file->is_reachable)
@@ -446,6 +440,16 @@ void parse_eh_frame_sections(Context<E> &ctx) {
});
}
template <typename E>
void parse_sframe_sections(Context<E> &ctx) {
if constexpr (supports_sframe<E>) {
Timer t(ctx, "parse_sframe_sections");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->parse_sframe(ctx);
});
}
}
template <typename E>
void create_merged_sections(Context<E> &ctx) {
Timer t(ctx, "create_merged_sections");
@@ -509,6 +513,7 @@ struct OutputSectionKey {
bool operator==(const OutputSectionKey &) const = default;
std::string_view name;
u64 type;
i32 script_cmd = -1;
struct Hash {
size_t operator()(const OutputSectionKey &k) const {
@@ -567,6 +572,16 @@ template <typename E>
static OutputSectionKey
get_output_section_key(Context<E> &ctx, InputSection<E> &isec,
bool ctors_in_init_array) {
// A section matched by a linker script goes to the output section
// the script says, period.
if (!ctx.script_sections.empty()) {
i32 rank = isec.file.script_match[isec.shndx];
if (rank != -1) {
i32 cmd = ctx.script_ranks[rank].cmd;
return {ctx.script_sections[cmd].name, SHT_PROGBITS, cmd};
}
}
// If .init_array/.fini_array exist, .ctors/.dtors must be merged
// with them.
//
@@ -576,15 +591,14 @@ get_output_section_key(Context<E> &ctx, InputSection<E> &isec,
// We do not place them into .init_array/.fini_array because such
// invalid pointer values would simply make the program to crash.
if (ctors_in_init_array && !isec.get_rels(ctx).empty()) {
std::string_view name = isec.name();
if (name == ".ctors" || name.starts_with(".ctors."))
if (isec.name == ".ctors" || isec.name.starts_with(".ctors."))
return {".init_array", SHT_INIT_ARRAY};
if (name == ".dtors" || name.starts_with(".dtors."))
if (isec.name == ".dtors" || isec.name.starts_with(".dtors."))
return {".fini_array", SHT_FINI_ARRAY};
}
const ElfShdr<E> &shdr = isec.shdr();
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
std::string_view name = get_output_name(ctx, isec.name, shdr.sh_flags);
u64 type = canonicalize_type<E>(name, shdr.sh_type);
return {name, type};
}
@@ -645,7 +659,7 @@ void create_output_sections(Context<E> &ctx) {
~SHF_COMPRESSED & ~SHF_GNU_RETAIN;
if (ctx.arg.relocatable && (sh_flags & SHF_GROUP)) {
OutputSection<E> *osec = new OutputSection<E>(isec->name(), shdr.sh_type);
OutputSection<E> *osec = new OutputSection<E>(isec->name, shdr.sh_type);
osec->sh_flags = sh_flags;
isec->output_section = osec;
ctx.osec_pool.emplace_back(osec);
@@ -669,6 +683,7 @@ void create_output_sections(Context<E> &ctx) {
std::unique_ptr<OutputSection<E>> osec =
std::make_unique<OutputSection<E>>(key.name, key.type);
osec->script_cmd = key.script_cmd;
std::unique_lock lock(mu);
auto [it, inserted] = map.insert({key, osec.get()});
@@ -715,6 +730,72 @@ void create_output_sections(Context<E> &ctx) {
osec->members = flatten(osec->members_vec);
osec->members_vec.clear();
osec->members_vec.shrink_to_fit();
if (osec->script_cmd != -1)
script_finalize_section(ctx, *osec);
}
// An output section description that inserts data with a command
// like BYTE must materialize even if no input section is placed in
// it, e.g. the .signature section of the Linux kernel's setup.ld.
if (!ctx.script_sections.empty()) {
std::vector<bool> taken(ctx.script_sections.size());
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
if (osec->script_cmd != -1)
taken[osec->script_cmd] = true;
for (i64 i = 0; i < ctx.script_sections.size(); i++) {
ScriptCmd &cmd = ctx.script_sections[i];
if (cmd.kind != ScriptCmd::OUTPUT_SECTION || taken[i])
continue;
bool need = false;
for (ScriptCmd &b : cmd.cmds)
need |= (b.kind == ScriptCmd::DATA);
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
need |= (ms->script_cmd == i);
if (!need)
continue;
// Data commands are loadable content; a section created only
// to hold merged sections takes its flags from them when they
// are attached below
bool has_data = false;
for (ScriptCmd &b : cmd.cmds)
has_data |= (b.kind == ScriptCmd::DATA);
OutputSection<E> *osec = new OutputSection<E>(cmd.name, SHT_PROGBITS);
osec->script_cmd = i;
osec->sh_flags = has_data ? SHF_ALLOC : 0;
osec->shdr.sh_flags = osec->sh_flags;
osec->shdr.sh_addralign = 1;
script_finalize_section(ctx, *osec);
ctx.osec_pool.emplace_back(osec);
}
// A merged section matched by an output section description is
// laid out and written as part of the section created for it
std::unordered_map<i64, OutputSection<E> *> osec_of;
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
if (osec->script_cmd != -1)
osec_of[osec->script_cmd] = osec.get();
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections) {
if (ms->script_cmd != -1) {
OutputSection<E> *osec = osec_of[ms->script_cmd];
ms->script_owner = osec;
osec->merged_members.push_back(ms.get());
osec->sh_flags |= ms->shdr.sh_flags & ~(u64)SHF_MERGE &
~(u64)SHF_STRINGS;
osec->shdr.sh_flags = osec->sh_flags;
}
}
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
ranges::stable_sort(osec->merged_members, {},
[](MergedSection<E> *ms) {
return ms->script_rank.load();
});
}
// Add output sections and mergeable sections to ctx.chunks
@@ -722,7 +803,8 @@ void create_output_sections(Context<E> &ctx) {
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
chunks.push_back(osec.get());
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
chunks.push_back(osec.get());
if (!osec->script_owner)
chunks.push_back(osec.get());
// Sections are added to the section lists in an arbitrary order
// because they are created in parallel. Sort them to to make the
@@ -774,14 +856,112 @@ void create_internal_file(Context<E> &ctx) {
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++)
add(ctx.arg.defsyms[i].first);
// Add --section-order symbols
for (SectionOrder &ord : ctx.arg.section_order)
if (ord.type == SectionOrder::SYMBOL)
add(get_symbol(ctx, ord.name));
// Add symbols defined by linker scripts. PROVIDE'd symbols are not
// added here because they must yield to definitions in input files;
// they are handled in add_synthetic_symbols() instead.
for (ScriptAssignment<E> &a : ctx.script_assignments) {
if (!a.provide) {
add(a.sym);
a.defined = true;
}
}
obj->elf_syms = ctx.internal_esyms;
}
// Define symbols PROVIDE'd by linker scripts. A PROVIDE'd symbol is
// defined only if no input file defines it, so this pass must run
// after symbol resolution. It must also run before
// compute_import_export() so that PROVIDE'd symbols are exported just
// like any other defined symbol.
template <typename E>
void add_provided_symbols(Context<E> &ctx) {
if (!ctx.internal_obj)
return;
ObjectFile<E> &obj = *ctx.internal_obj;
bool changed = false;
// A PROVIDE'd symbol is defined only if it is actually referenced,
// either by an input file or by another linker script expression
// that is evaluated. Note that if it is not, its value expression
// is not even evaluated; an expression referring to a nonexistent
// symbol or section is not an error in that case.
std::unordered_set<Symbol<E> *> candidates;
for (ScriptAssignment<E> &a : ctx.script_assignments)
if (a.provide && !a.sym->file)
candidates.insert(a.sym);
std::mutex mu;
std::unordered_set<Symbol<E> *> referenced;
if (!candidates.empty()) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
if (file == ctx.internal_obj)
return;
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
if (file->elf_syms[i].is_undef()) {
Symbol<E> *sym = file->symbols[i];
if (candidates.contains(sym)) {
std::scoped_lock lock(mu);
referenced.insert(sym);
}
}
}
});
// References from expressions we will evaluate, propagated
// transitively through PROVIDEs that become defined
std::function<void(const ScriptExpr &)> mark = [&](const ScriptExpr &e) {
if (e.kind == ScriptExpr::NAME)
referenced.insert(get_symbol(ctx, e.str));
for (const ScriptExpr &arg : e.args)
mark(arg);
};
for (ScriptAssignment<E> &a : ctx.script_assignments)
if (!a.provide)
mark(a.value);
for (ScriptAssert &a : ctx.script_asserts)
mark(a.cond);
for (bool changed = true; changed;) {
changed = false;
for (ScriptAssignment<E> &a : ctx.script_assignments) {
if (a.provide && referenced.contains(a.sym)) {
i64 size = referenced.size();
mark(a.value);
changed |= (referenced.size() != size);
}
}
}
}
for (ScriptAssignment<E> &a : ctx.script_assignments) {
if (a.provide && !a.sym->file && referenced.contains(a.sym)) {
ElfSym<E> esym;
memset(&esym, 0, sizeof(esym));
esym.st_type = STT_NOTYPE;
esym.st_shndx = SHN_ABS;
esym.st_bind = STB_GLOBAL;
esym.st_visibility = a.hidden ? STV_HIDDEN : STV_DEFAULT;
ctx.internal_esyms.push_back(esym);
a.sym->value = 0xdeadbeef; // unique dummy value
if (a.hidden)
a.sym->visibility = STV_HIDDEN;
obj.symbols.push_back(a.sym);
a.defined = true;
changed = true;
}
}
if (changed) {
obj.elf_syms = ctx.internal_esyms;
obj.resolve_symbols(ctx);
}
}
template <typename E>
static std::optional<std::string>
get_start_stop_name(Context<E> &ctx, Chunk<E> &chunk) {
@@ -838,10 +1018,15 @@ void add_synthetic_symbols(Context<E> &ctx) {
ctx._DYNAMIC = add("_DYNAMIC");
ctx._GLOBAL_OFFSET_TABLE_ = add("_GLOBAL_OFFSET_TABLE_");
ctx._PROCEDURE_LINKAGE_TABLE_ = add("_PROCEDURE_LINKAGE_TABLE_");
ctx.__bss_start = add("__bss_start");
ctx._end = add("_end");
ctx._etext = add("_etext");
ctx._edata = add("_edata");
// A linker script may define these; in that case the script wins
if (!get_symbol(ctx, "__bss_start")->file)
ctx.__bss_start = add("__bss_start");
if (!get_symbol(ctx, "_end")->file)
ctx._end = add("_end");
if (!get_symbol(ctx, "_etext")->file)
ctx._etext = add("_etext");
if (!get_symbol(ctx, "_edata")->file)
ctx._edata = add("_edata");
ctx.__executable_start = add("__executable_start");
ctx.__rel_iplt_start =
@@ -891,11 +1076,6 @@ void add_synthetic_symbols(Context<E> &ctx) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
add_start_stop("__start_" + *name);
add_start_stop("__stop_" + *name);
if (ctx.arg.physical_image_base) {
add_start_stop("__phys_start_" + *name);
add_start_stop("__phys_stop_" + *name);
}
}
}
@@ -1298,7 +1478,7 @@ void check_symbol_types(Context<E> &ctx) {
template <typename E>
static i64 get_init_fini_priority(InputSection<E> *isec) {
static std::regex re(R"(\.(\d+)$)", std::regex_constants::optimize);
std::string_view name = isec->name();
std::string_view name = isec->name;
std::cmatch m;
if (std::regex_search(name.data(), name.data() + name.size(), m, re))
return std::stoi(m[1]);
@@ -1315,14 +1495,14 @@ static i64 get_ctor_dtor_priority(InputSection<E> *isec) {
// crtbegin.o and crtend.o contain marker symbols such as
// __CTOR_LIST__ or __DTOR_LIST__. So they have to be at the
// beginning or end of the section.
std::smatch m;
if (std::regex_search(isec->file.filename, m, re1))
if (std::regex_search(isec->file.filename, re1))
return -2;
if (std::regex_search(isec->file.filename, m, re2))
if (std::regex_search(isec->file.filename, re2))
return 65536;
std::string name(isec->name());
if (std::regex_search(name, m, re3))
std::string_view name = isec->name;
std::cmatch m;
if (std::regex_search(name.data(), name.data() + name.size(), m, re3))
return std::stoi(m[1]);
return -1;
}
@@ -1346,8 +1526,8 @@ void sort_init_fini(Context<E> &ctx) {
std::vector<Entry> vec;
for (InputSection<E> *isec : osec->members) {
std::string_view name = isec->name();
if (name.starts_with(".ctors") || name.starts_with(".dtors"))
if (isec->name.starts_with(".ctors") ||
isec->name.starts_with(".dtors"))
vec.push_back({isec, 65535 - get_ctor_dtor_priority(isec)});
else
vec.push_back({isec, get_init_fini_priority(isec)});
@@ -1555,13 +1735,13 @@ void fixup_ctors_in_init_array(Context<E> &ctx) {
if (Chunk<E> *chunk = find_chunk(ctx, ".init_array"))
if (OutputSection<E> *osec = chunk->to_osec())
for (InputSection<E> *isec : osec->members)
if (isec->name().starts_with(".ctors"))
if (isec->name.starts_with(".ctors"))
reverse_contents(*isec);
if (Chunk<E> *chunk = find_chunk(ctx, ".fini_array"))
if (OutputSection<E> *osec = chunk->to_osec())
for (InputSection<E> *isec : osec->members)
if (isec->name().starts_with(".dtors"))
if (isec->name.starts_with(".dtors"))
reverse_contents(*isec);
}
@@ -1664,9 +1844,13 @@ void compute_section_sizes(Context<E> &ctx) {
if constexpr (needs_thunk<E>) {
std::vector<Chunk<E> *> vec = ctx.chunks;
// A section described by a linker script is laid out by the
// script engine, which doesn't support range extension thunks
// yet; a branch that cannot reach its target is reported as an
// out-of-range relocation.
auto tail = ranges::partition(vec, [&](Chunk<E> *chunk) {
return chunk->to_osec() && (chunk->shdr.sh_flags & SHF_EXECINSTR) &&
!ctx.arg.relocatable;
!ctx.arg.relocatable && chunk->script_cmd == -1;
});
// create_range_extension_thunks is not thread-safe
@@ -1681,6 +1865,13 @@ void compute_section_sizes(Context<E> &ctx) {
chunk->compute_section_size(ctx);
});
}
// Merged sections owned by a script-described output section are
// not in ctx.chunks; their content layout happens here, their
// placement in the address walk.
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
if (ms->script_owner)
ms->compute_section_size(ctx);
}
// Find all unresolved symbols and attach them to the most appropriate files.
@@ -1964,6 +2155,12 @@ void copy_chunks(Context<E> &ctx) {
copy(*chunk);
});
tbb::parallel_for_each(ctx.merged_sections,
[&](std::unique_ptr<MergedSection<E>> &ms) {
if (ms->script_owner)
copy(*ms);
});
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
if (is_rel(*chunk))
copy(*chunk);
@@ -1992,15 +2189,6 @@ void copy_chunks(Context<E> &ctx) {
zero(chunks.back(), ctx.output_file->filesize);
}
template <typename E>
void construct_relr(Context<E> &ctx) {
Timer t(ctx, "construct_relr");
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
chunk->construct_relr(ctx);
});
}
// The hash function for .gnu.hash.
static u32 djb_hash(std::string_view name) {
u32 h = 5381;
@@ -2187,8 +2375,8 @@ void parse_symbol_version(Context<E> &ctx) {
if (sym->file != file)
continue;
const char *name = file->symbol_strtab.data() + file->elf_syms[i].st_name;
std::string_view ver = strchr(name, '@') + 1;
std::string_view name = file->symbol_names[i];
std::string_view ver = name.substr(name.find('@') + 1);
bool is_default = false;
if (ver.starts_with('@')) {
@@ -2511,130 +2699,156 @@ void compute_address_significance(Context<E> &ctx) {
// Other file layouts are possible, but this layout is chosen to keep
// the number of segments as few as possible.
template <typename E>
void sort_output_sections_regular(Context<E> &ctx) {
auto get_rank1 = [&](Chunk<E> *chunk) {
u64 type = chunk->shdr.sh_type;
u64 flags = chunk->shdr.sh_flags;
static i64 get_regular_rank1(Context<E> &ctx, Chunk<E> *chunk) {
u64 type = chunk->shdr.sh_type;
u64 flags = chunk->shdr.sh_flags;
if (chunk == ctx.ehdr)
return 0;
if (chunk == ctx.phdr)
return 1;
if (chunk == ctx.interp)
return 2;
if (type == SHT_NOTE && (flags & SHF_ALLOC))
return 3;
if (chunk == ctx.hash)
return 4;
if (chunk == ctx.gnu_hash)
return 5;
if (chunk == ctx.dynsym)
return 6;
if (chunk == ctx.dynstr)
return 7;
if (chunk == ctx.versym)
return 8;
if (chunk == ctx.verneed)
return 9;
if (chunk == ctx.reldyn)
return 10;
if (chunk == ctx.relplt)
return 11;
if (chunk == ctx.shdr)
return INT32_MAX - 1;
if (chunk == ctx.gdb_index)
return INT32_MAX;
bool alloc = (flags & SHF_ALLOC);
bool writable = (flags & SHF_WRITE);
bool exec = (flags & SHF_EXECINSTR);
bool tls = (flags & SHF_TLS);
bool relro = chunk->is_relro;
bool is_bss = (type == SHT_NOBITS);
return (1 << 10) | (!alloc << 9) | (writable << 8) | (exec << 7) |
(!tls << 6) | (!relro << 5) | (is_bss << 4);
};
// Ties are broken by additional rules
auto get_rank2 = [&](Chunk<E> *chunk) -> i64 {
ElfShdr<E> &shdr = chunk->shdr;
if (shdr.sh_type == SHT_NOTE)
return -shdr.sh_addralign;
if (chunk == ctx.got)
return 2;
if (chunk->name == ".toc")
return 3;
if (chunk == ctx.relro_padding)
return INT64_MAX;
if (chunk == ctx.ehdr)
return 0;
if (chunk == ctx.phdr)
return 1;
if (chunk == ctx.interp)
return 2;
if (type == SHT_NOTE && (flags & SHF_ALLOC))
return 3;
if (chunk == ctx.hash)
return 4;
if (chunk == ctx.gnu_hash)
return 5;
if (chunk == ctx.dynsym)
return 6;
if (chunk == ctx.dynstr)
return 7;
if (chunk == ctx.versym)
return 8;
if (chunk == ctx.verneed)
return 9;
if (chunk == ctx.reldyn)
return 10;
if (chunk == ctx.relplt)
return 11;
if (chunk == ctx.shdr)
return INT32_MAX - 1;
if (chunk == ctx.gdb_index)
return INT32_MAX;
bool alloc = (flags & SHF_ALLOC);
bool writable = (flags & SHF_WRITE);
bool exec = (flags & SHF_EXECINSTR);
bool tls = (flags & SHF_TLS);
bool relro = chunk->is_relro;
bool is_bss = (type == SHT_NOBITS);
return (1 << 10) | (!alloc << 9) | (writable << 8) | (exec << 7) |
(!tls << 6) | (!relro << 5) | (is_bss << 4);
}
// Ties are broken by additional rules
template <typename E>
static i64 get_regular_rank2(Context<E> &ctx, Chunk<E> *chunk) {
ElfShdr<E> &shdr = chunk->shdr;
if (shdr.sh_type == SHT_NOTE)
return -shdr.sh_addralign;
if (chunk == ctx.got)
return 2;
if (chunk->name == ".toc")
return 3;
if (chunk == ctx.relro_padding)
return INT64_MAX;
return 0;
}
template <typename E>
void sort_output_sections_regular(Context<E> &ctx) {
ranges::stable_sort(ctx.chunks, {}, [&](Chunk<E> *x) {
return std::tuple{get_regular_rank1(ctx, x), get_regular_rank2(ctx, x),
x->name};
});
}
// Sort output sections according to a linker script. Sections the
// script describes appear in script order; orphans are inserted after
// the most similar script section.
template <typename E>
static void sort_output_sections_by_script(Context<E> &ctx) {
match_synthetic_sections(ctx);
// Find the insertion point for an orphan: the position the default
// section order would give it, i.e. after the last script section
// that the default order ranks no higher. This keeps related
// orphans, such as .tdata and .tbss, together.
i64 first_osec = 0;
for (i64 i = 0; i < ctx.script_sections.size(); i++) {
if (ctx.script_sections[i].kind == ScriptCmd::OUTPUT_SECTION) {
first_osec = i;
break;
}
}
auto get_anchor = [&](Chunk<E> *chunk) -> i64 {
i64 rank = get_regular_rank1(ctx, chunk);
// An orphan that orders before everything the script describes
// is placed right after the first section; placing it in front
// would displace the address the script gives the section.
i64 anchor = first_osec;
i64 equal = -1;
for (Chunk<E> *sc : ctx.chunks) {
if (sc->script_cmd == -1 || !(sc->shdr.sh_flags & SHF_ALLOC))
continue;
i64 r = get_regular_rank1(ctx, sc);
if (r <= rank)
anchor = std::max<i64>(anchor, sc->script_cmd);
if (r == rank)
equal = std::max<i64>(equal, sc->script_cmd);
}
// Prefer a section of exactly the same class; e.g. exec orphans
// such as .plt go with .text rather than after some read-only
// data, which would create an executable island in a page shared
// with data.
return (equal != -1) ? equal : anchor;
};
auto get_rank = [&](Chunk<E> *chunk) -> i64 {
if (chunk == ctx.ehdr)
return -2;
if (chunk == ctx.phdr)
return -1;
if (chunk == ctx.shdr)
return INT64_MAX;
if (chunk->script_cmd != -1)
return 2 * (i64)chunk->script_cmd;
if (!(chunk->shdr.sh_flags & SHF_ALLOC))
return INT64_MAX - 1;
if (ctx.arg.orphan_handling == "warn")
Warn(ctx) << "placing orphan section " << chunk->name;
else if (ctx.arg.orphan_handling == "error")
Error(ctx) << "orphan section " << chunk->name;
return 2 * get_anchor(chunk) + 1;
};
for (Chunk<E> *chunk : ctx.chunks)
chunk->sect_order = get_rank(chunk);
// Orphans that share an insertion point are ordered as the default
// layout would order them, which keeps pairs like .tdata/.tbss in
// the order other passes depend on.
ranges::stable_sort(ctx.chunks, {}, [&](Chunk<E> *x) {
return std::tuple{get_rank1(x), get_rank2(x), x->name};
return std::tuple{x->sect_order, get_regular_rank1(ctx, x),
get_regular_rank2(ctx, x), x->name};
});
}
template <typename E>
static std::string_view get_section_order_group(Chunk<E> &chunk) {
if (chunk.shdr.sh_type == SHT_NOBITS)
return "BSS";
if (chunk.shdr.sh_flags & SHF_EXECINSTR)
return "TEXT";
if (chunk.shdr.sh_flags & SHF_WRITE)
return "DATA";
return "RODATA";
};
// Sort sections according to a --section-order argument.
template <typename E>
void sort_output_sections_by_order(Context<E> &ctx) {
auto get_rank = [&](Chunk<E> *chunk) -> i64 {
u64 flags = chunk->shdr.sh_flags;
if (chunk == ctx.ehdr && !(chunk->shdr.sh_flags & SHF_ALLOC))
return -2;
if (chunk == ctx.phdr && !(chunk->shdr.sh_flags & SHF_ALLOC))
return -1;
if (chunk == ctx.shdr)
return INT32_MAX;
if (!(flags & SHF_ALLOC))
return INT32_MAX - 1;
for (i64 i = 0; i < ctx.arg.section_order.size(); i++)
if (SectionOrder &arg = ctx.arg.section_order[i];
arg.type == SectionOrder::SECTION && arg.name == chunk->name)
return i;
std::string_view group = get_section_order_group(*chunk);
for (i64 i = 0; i < ctx.arg.section_order.size(); i++)
if (SectionOrder &arg = ctx.arg.section_order[i];
arg.type == SectionOrder::GROUP && arg.name == group)
return i;
Error(ctx) << "--section-order: missing section specification for "
<< chunk->name;
return 0;
};
// It is an error if a section order cannot be determined by a given
// section order list.
for (Chunk<E> *chunk : ctx.chunks)
chunk->sect_order = get_rank(chunk);
// Sort output sections by --section-order
ranges::stable_sort(ctx.chunks, {}, &Chunk<E>::sect_order);
}
template <typename E>
void sort_output_sections(Context<E> &ctx) {
if (ctx.arg.section_order.empty())
if (ctx.script_sections.empty())
sort_output_sections_regular(ctx);
else
sort_output_sections_by_order(ctx);
sort_output_sections_by_script(ctx);
}
template <typename E>
@@ -2787,69 +3001,6 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
}
}
template <typename E>
static void set_virtual_addresses_by_order(Context<E> &ctx) {
std::vector<Chunk<E> *> vec;
for (Chunk<E> *c : ctx.chunks)
if (c->shdr.sh_flags & SHF_ALLOC)
vec.push_back(c);
u64 addr = ctx.arg.image_base;
i64 i = 0;
for (i64 j = 0; j < ctx.arg.section_order.size(); j++) {
SectionOrder &ord = ctx.arg.section_order[j];
switch (ord.type) {
case SectionOrder::SECTION:
case SectionOrder::GROUP:
for (; i < vec.size() && vec[i]->sect_order == j; i++) {
// Memory protection works on page size granularity. We need to
// put sections with different memory attributes into different
// pages. We do it by inserting a padding.
if (i != 0) {
i64 flags1 = to_phdr_flags(ctx, vec[i - 1]);
i64 flags2 = to_phdr_flags(ctx, vec[i]);
if (flags1 != flags2) {
switch (ctx.arg.z_separate_code) {
case SEPARATE_LOADABLE_SEGMENTS:
addr = align_to(addr, ctx.page_size);
break;
case SEPARATE_CODE:
if ((flags1 & PF_X) != (flags2 & PF_X))
addr = align_to(addr, ctx.page_size);
break;
default:
break;
}
}
}
addr = align_to(addr, vec[i]->shdr.sh_addralign);
vec[i]->shdr.sh_addr = addr;
addr += vec[i]->shdr.sh_size;
}
break;
case SectionOrder::ADDR:
if (addr != ctx.arg.image_base && ord.value < addr)
Error(ctx) << "--section-order: address goes backward: requested "
<< std::hex << std::showbase
<< ord.value << " < current " << addr
<< " (at token '" << ord.token << "')";
addr = ord.value;
break;
case SectionOrder::ALIGN:
addr = align_to(addr, ord.value);
break;
case SectionOrder::SYMBOL:
get_symbol(ctx, ord.name)->value = addr;
break;
default:
unreachable();
}
}
}
// Returns the smallest integer N that satisfies N >= val and
// N % align == skew % align.
//
@@ -2905,6 +3056,12 @@ static i64 set_file_offsets(Context<E> &ctx) {
if (chunks[i]->shdr.sh_addr < first.shdr.sh_addr)
break;
// This section requires larger alignment, we need to adjust the
// offset to ensure offset % align == vaddr % align.
if (chunks[i]->shdr.sh_addralign > ctx.page_size &&
chunks[i]->shdr.sh_addralign > chunks[i - 1]->shdr.sh_addralign)
break;
i64 gap_size = chunks[i]->shdr.sh_addr - chunks[i - 1]->shdr.sh_addr -
chunks[i - 1]->shdr.sh_size;
@@ -2963,6 +3120,12 @@ void compute_section_headers(Context<E> &ctx) {
if (!chunk->is_header())
chunk->shndx = shndx++;
// Symbols in a merged section within a script-described output
// section belong to that section
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
if (ms->script_owner)
ms->shndx = ms->script_owner->shndx;
if (ctx.symtab && SHN_LORESERVE <= shndx) {
SymtabShndxSection<E> *sec = new SymtabShndxSection<E>;
sec->shndx = shndx++;
@@ -2992,16 +3155,32 @@ i64 set_osec_offsets(Context<E> &ctx) {
Timer t(ctx, "set_osec_offsets");
for (;;) {
if (ctx.arg.section_order.empty())
set_virtual_addresses_regular(ctx);
if (!ctx.script_sections.empty())
set_virtual_addresses_by_script(ctx);
else
set_virtual_addresses_by_order(ctx);
set_virtual_addresses_regular(ctx);
if (ctx.arg.pack_dyn_relocs_relr || ctx.arg.pack_dyn_relocs_android) {
i64 x = ctx.reldyn->shdr.sh_size;
i64 y = ctx.relrdyn ? (i64)ctx.relrdyn->shdr.sh_size : 0;
ctx.reldyn->update_shdr(ctx);
if (x != (i64)ctx.reldyn->shdr.sh_size ||
y != (ctx.relrdyn ? (i64)ctx.relrdyn->shdr.sh_size : 0))
continue;
}
ctx.checkpoint();
// Assigning new offsets may change the contents and the length
// of the program header, so repeat it until converge.
i64 fileoff = set_file_offsets(ctx);
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
if (Chunk<E> *p = ms->script_owner)
ms->shdr.sh_offset =
p->shdr.sh_offset + ms->shdr.sh_addr - p->shdr.sh_addr;
if (ctx.phdr) {
i64 sz = ctx.phdr->shdr.sh_size;
ctx.phdr->update_shdr(ctx);
@@ -3022,15 +3201,6 @@ static i64 get_num_irelative_relocs(Context<E> &ctx) {
return n;
}
template <typename E>
static u64 to_paddr(Context<E> &ctx, u64 vaddr) {
for (ElfPhdr<E> &phdr : ctx.phdr->phdrs)
if (phdr.p_type == PT_LOAD)
if (phdr.p_vaddr <= vaddr && vaddr < phdr.p_vaddr + phdr.p_memsz)
return phdr.p_paddr + (vaddr - phdr.p_vaddr);
return 0;
}
template <typename E>
void fix_synthetic_symbols(Context<E> &ctx) {
auto start = [](Symbol<E> *sym, auto &chunk, i64 bias = 0) {
@@ -3197,18 +3367,6 @@ void fix_synthetic_symbols(Context<E> &ctx) {
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
start(get_symbol(ctx, save_string(ctx, "__start_" + *name)), chunk);
stop(get_symbol(ctx, save_string(ctx, "__stop_" + *name)), chunk);
if (ctx.arg.physical_image_base) {
u64 paddr = to_paddr(ctx, chunk->shdr.sh_addr);
Symbol<E> *x = get_symbol(ctx, save_string(ctx, "__phys_start_" + *name));
x->set_output_section(chunk);
x->value = paddr;
Symbol<E> *y = get_symbol(ctx, save_string(ctx, "__phys_stop_" + *name));
y->set_output_section(chunk);
y->value = paddr + chunk->shdr.sh_size;
}
}
}
@@ -3228,10 +3386,8 @@ void fix_synthetic_symbols(Context<E> &ctx) {
}
}
// --section-order symbols
for (SectionOrder &ord : ctx.arg.section_order)
if (ord.type == SectionOrder::SYMBOL)
get_symbol(ctx, ord.name)->set_output_section(sections[0]);
// Linker script symbol assignments and ASSERT commands
eval_script_commands(ctx);
}
template <typename E>
@@ -3304,6 +3460,10 @@ template <typename E>
void sort_reldyn(Context<E> &ctx) {
Timer t(ctx, "sort_reldyn");
// .rela.dyn contains APS2-encoded bytes, not ElfRel entries.
if (ctx.arg.pack_dyn_relocs_android)
return;
ElfRel<E> *begin = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset);
ElfRel<E> *end = begin + ctx.reldyn->shdr.sh_size / sizeof(ElfRel<E>);
@@ -3641,9 +3801,11 @@ template void create_synthetic_sections(Context<E> &);
template void resolve_symbols(Context<E> &);
template void do_lto(Context<E> &);
template void parse_eh_frame_sections(Context<E> &);
template void parse_sframe_sections(Context<E> &);
template void create_merged_sections(Context<E> &);
template void convert_common_symbols(Context<E> &);
template void create_output_sections(Context<E> &);
template void add_provided_symbols(Context<E> &);
template void add_synthetic_symbols(Context<E> &);
template void check_cet_errors(Context<E> &);
template void apply_section_align(Context<E> &);
@@ -3666,7 +3828,6 @@ template void scan_relocations(Context<E> &);
template void report_undef_errors(Context<E> &);
template void create_reloc_sections(Context<E> &);
template void copy_chunks(Context<E> &);
template void construct_relr(Context<E> &);
template void sort_dynsyms(Context<E> &);
template void sort_debug_info_sections(Context<E> &);
template void create_output_symtab(Context<E> &);
+5
View File
@@ -50,6 +50,8 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
ctx.shdr = push(new OutputShdr<E>);
ctx.eh_frame = push(new EhFrameSection<E>);
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
ctx.sframe = push(new SFrameSection<E>);
ctx.sframe_reloc = push(new SFrameRelocSection<E>);
ctx.strtab = push(new StrtabSection<E>);
ctx.symtab = push(new SymtabSection<E>);
ctx.shstrtab = push(new ShstrtabSection<E>);
@@ -161,6 +163,9 @@ void combine_objects(Context<E> &ctx) {
ctx.eh_frame->construct(ctx);
if constexpr (supports_sframe<E>)
ctx.sframe->construct(ctx);
create_reloc_sections(ctx);
create_comdat_group_sections(ctx);
+6 -4
View File
@@ -32,12 +32,14 @@ namespace mold {
using E = MOLD_TARGET;
// We create thunks for each 25.6/3.2/6.4 MiB code block for
// We create thunks for each 32/8/16 MiB code block for
// ARM64/ARM32/PPC, respectively.
static constexpr i64 batch_size = branch_distance<E> / 5;
static constexpr i64 batch_size =
(is_arm64<E> ? 32 : is_arm32<E> ? 8 : 16) * 1024 * 1024;
// We assume that a single thunk group is smaller than 1 MiB.
static constexpr i64 max_thunk_size = 1024 * 1024;
// We assume that a single thunk group is smaller than 16/4/8 MiB
// for ARM64/ARM32/PPC, respectively.
static constexpr i64 max_thunk_size = batch_size / 2;
// We align thunks to 16 byte boundaries because many processor vendors
// recommend we align branch targets to 16 byte boundaries for performance
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = aarch64 ] || skip
command -v timeout >/dev/null || skip
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
.section .custom_rodata_a, "a", %progbits
.hidden A
.global A
A:
.space 8
.section .custom_rodata_b_huge, "a", %progbits
.balign 16384
.hidden B
.global B
B:
.space 8
.section .data, "aw", %progbits
.rept 7439
.quad A
.endr
.global P_1
P_1:
.quad A
.global P_2
P_2:
.quad B
EOF
timeout 5s ./mold -m aarch64elf -shared -o $t/b.so $t/a.o \
--pack-dyn-relocs=android -z max-page-size=16384
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
. $(dirname $0)/common.inc
# In the REL relocation format, an addend is read from the relocated
# place itself, and its value is signed. The Linux kernel's real-mode
# setup code, for example, presets fields relocated with R_386_8 and
# R_386_16 with negative values such as -1 and -512. Make sure we
# sign-extend such in-place addends instead of zero-extending them.
cat <<'EOF' | $CC -c -o $t/a.o -xassembler -
.data
.globl x8, x16, xpc8, xpc16
x8: .byte abs8 - 1
x16: .short abs16 - 512
xpc8: .byte target - . - 1
xpc16: .short target - . - 512
.section .nonalloc
.byte abs8 - 1
.short abs16 - 512
EOF
cat <<'EOF' | $CC -c -o $t/b.o -xassembler -
.globl abs8, abs16
abs8 = 100
abs16 = 1000
.data
.globl target
target:
.byte 0
EOF
cat <<'EOF' | $CC -c -o $t/c.o -xc -
#include <stdio.h>
extern char target;
extern unsigned char x8, xpc8;
extern unsigned short x16, xpc16;
int main() {
printf("%d %d %d %d\n", x8, x16,
(signed char)xpc8 == (char *)&target - (char *)&xpc8 - 1,
(short)xpc16 == (char *)&target - (char *)&xpc16 - 512);
}
EOF
$CC -B. -no-pie -o $t/exe $t/a.o $t/b.o $t/c.o
$QEMU $t/exe | grep '^99 488 1 1$'
$OBJDUMP -s -j .nonalloc $t/exe | grep -w 63e801
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
. $(dirname $0)/common.inc
# When mold relaxes an instruction sequence, the relocations attached to the
# original instructions no longer match the relaxed code. With --emit-relocs,
# mold must rewrite them so that the emitted relocations describe the relaxed
# instructions, both in type and in offset.
cat <<'EOF' | $CC -o $t/a.o -c -xassembler -
.text
.globl _start
_start:
# pcaddu18i + jirl => bl (R_LARCH_CALL36 => R_LARCH_B26)
.reloc ., R_LARCH_RELAX
pcaddu18i $ra, %call36(get_zero)
jirl $ra, $ra, 0
# pcalau12i + addi.d => pcaddi (R_LARCH_PCALA_* => R_LARCH_PCREL20_S2)
.reloc ., R_LARCH_RELAX
pcalau12i $t0, %pc_hi20(v)
.reloc ., R_LARCH_RELAX
addi.d $t0, $t0, %pc_lo12(v)
ld.w $t0, $t0, 0
or $a0, $a0, $t0
# pcalau12i + ld.d => pcaddi (R_LARCH_GOT_PC_* => R_LARCH_PCREL20_S2)
.reloc ., R_LARCH_RELAX
pcalau12i $t1, %got_pc_hi20(v)
.reloc ., R_LARCH_RELAX
ld.d $t1, $t1, %got_pc_lo12(v)
ld.w $t1, $t1, 0
or $a0, $a0, $t1
li.w $a7, 93 # __NR_exit
syscall 0
get_zero:
li.w $a0, 0
ret
.section .rodata,"a"
.p2align 2
.hidden v
v:
.word 0
EOF
$CC -B. -o $t/exe $t/a.o --static -nostdlib -Wl,-e,_start,--emit-relocs,--relax
$QEMU $t/exe
$OBJDUMP -dr $t/exe > $t/exe.objdump
# Relaxation took place.
grep -Fw pcaddi $t/exe.objdump
grep -Fw bl $t/exe.objdump
# The relocations were rewritten to match the relaxed instructions. The dead
# high-part relocations are turned into R_LARCH_NONE.
grep -Fw R_LARCH_PCREL20_S2 $t/exe.objdump
grep -Fw R_LARCH_B26 $t/exe.objdump
grep -Fw R_LARCH_NONE $t/exe.objdump
# The stale pre-relaxation relocations are gone.
not grep -Fw R_LARCH_PCALA_HI20 $t/exe.objdump
not grep -Fw R_LARCH_PCALA_LO12 $t/exe.objdump
not grep -Fw R_LARCH_GOT_PC_HI20 $t/exe.objdump
not grep -Fw R_LARCH_GOT_PC_LO12 $t/exe.objdump
not grep -Fw R_LARCH_CALL36 $t/exe.objdump
# Each rewritten relocation must sit at the address of the relaxed instruction
# it describes, not at its original pre-relaxation offset. This checks that the
# relocation offsets were adjusted: R_LARCH_PCREL20_S2 must land on a pcaddi
# and R_LARCH_B26 on a b/bl.
awk '
$3 == "pcaddi" { insn[$1] = $3 }
$3 == "b" || $3 == "bl" { insn[$1] = $3 }
$2 == "R_LARCH_PCREL20_S2" { if (insn[$1] != "pcaddi") { bad=1; print "bad offset: " $0 } }
$2 == "R_LARCH_B26" { if (insn[$1] !~ /^bl?$/) { bad=1; print "bad offset: " $0 } }
END { exit bad+0 }
' $t/exe.objdump
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
. $(dirname $0)/common.inc
# Depending on the symbol, mold relaxes a TLSDESC sequence into a local-exec
# (LE) or initial-exec (IE) sequence, or keeps it as TLSDESC but folds its
# first two instructions into a single pcaddi. With --emit-relocs, the emitted
# relocations must be rewritten to match whichever relaxation was applied.
cat <<'EOF' | $CC -o $t/a.o -c -xc - -fPIC
_Thread_local char foo[4] = "foo";
_Thread_local char padding[100000] = "pad";
_Thread_local char bar[4] = "bar";
EOF
cat <<'EOF' | $CC -o $t/b.o -c -xc - -fPIC -mtls-dialect=desc -O2
extern _Thread_local char foo[4];
extern _Thread_local char bar[4];
char *get_foo() { return foo; }
char *get_bar() { return bar; }
EOF
cat <<EOF | $CC -o $t/c.o -c -xc - -mtls-dialect=desc
#include <stdio.h>
char *get_foo();
char *get_bar();
int main() { printf("%s %s\n", get_foo(), get_bar()); }
EOF
#
# Local TLS in an executable: TLSDESC => LE
#
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -Wl,--emit-relocs,--relax
{ $QEMU $t/exe1; true; } |& grep -q 'unexpected reloc type' && skip
$QEMU $t/exe1 | grep -F 'foo bar'
$OBJDUMP -dr $t/exe1 > $t/exe1.objdump
# foo has a small TP offset, so its sequence relaxes to a single instruction
# with only a low-part relocation. bar has a large offset, so it keeps both
# the high- and low-part relocations.
grep -E $'R_LARCH_TLS_LE_LO12[ \t]+foo' $t/exe1.objdump
not grep -E $'R_LARCH_TLS_LE_HI20[ \t]+foo' $t/exe1.objdump
grep -E $'R_LARCH_TLS_LE_HI20[ \t]+bar' $t/exe1.objdump
grep -E $'R_LARCH_TLS_LE_LO12[ \t]+bar' $t/exe1.objdump
# No TLSDESC relocation survives.
not grep -Fw R_LARCH_TLS_DESC_PC_HI20 $t/exe1.objdump
not grep -Fw R_LARCH_TLS_DESC_PC_LO12 $t/exe1.objdump
not grep -Fw R_LARCH_TLS_DESC_LD $t/exe1.objdump
not grep -Fw R_LARCH_TLS_DESC_CALL $t/exe1.objdump
#
# TLS defined in a shared library: TLSDESC => IE
#
$CC -B. -shared -o $t/d.so $t/a.o -Wl,--relax
$CC -B. -o $t/exe2 $t/b.o $t/c.o $t/d.so -Wl,--emit-relocs,--relax -Wl,-rpath=$t
$QEMU $t/exe2 | grep -F 'foo bar'
$OBJDUMP -dr $t/exe2 > $t/exe2.objdump
grep -Fw R_LARCH_TLS_IE_PC_HI20 $t/exe2.objdump
grep -Fw R_LARCH_TLS_IE_PC_LO12 $t/exe2.objdump
not grep -Fw R_LARCH_TLS_DESC_LD $t/exe2.objdump
not grep -Fw R_LARCH_TLS_DESC_CALL $t/exe2.objdump
#
# Shared library output: TLSDESC is kept, but pcalau12i+addi.d => pcaddi, so
# the low-part relocation becomes the pcaddi's TLS_DESC_PCREL20_S2 while the
# ld.d/jirl relocations are left unchanged.
#
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o -Wl,--emit-relocs,--relax
$OBJDUMP -dr $t/e.so > $t/e.objdump
grep -Fw R_LARCH_TLS_DESC_PCREL20_S2 $t/e.objdump
grep -Fw R_LARCH_TLS_DESC_LD $t/e.objdump
grep -Fw R_LARCH_TLS_DESC_CALL $t/e.objdump
not grep -Fw R_LARCH_TLS_DESC_PC_HI20 $t/e.objdump
not grep -Fw R_LARCH_TLS_DESC_PC_LO12 $t/e.objdump
#
# Relocatable output: no relaxation happens and TLSDESC is not downgraded, so
# every relocation must be passed through unchanged rather than rewritten as
# if it had been relaxed to LE/IE.
#
$CC -B. -o $t/reloc.o $t/b.o -r
$OBJDUMP -r $t/reloc.o > $t/reloc.objdump
grep -Fw R_LARCH_TLS_DESC_PC_HI20 $t/reloc.objdump
grep -Fw R_LARCH_TLS_DESC_PC_LO12 $t/reloc.objdump
grep -Fw R_LARCH_TLS_DESC_LD $t/reloc.objdump
grep -Fw R_LARCH_TLS_DESC_CALL $t/reloc.objdump
not grep -Fw R_LARCH_TLS_LE_HI20 $t/reloc.objdump
not grep -Fw R_LARCH_TLS_LE_LO12 $t/reloc.objdump
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
# pcaddi takes a 20-bit immediate that is shifted left by 2 to form
# a PC-relative offset. Therefore, relaxing a GOT load (pcalau12i +
# ld.d) to pcaddi is valid only when the symbol's address is 4-byte
# aligned. Here, `foo` is placed at an odd address, so the relaxation
# must not happen.
cat <<EOF | $CC -o $t/a.o -c -xassembler -
.data
.byte 0
.globl foo
.type foo, @object
foo:
.byte 0x42
.size foo, 1
EOF
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC -O
extern char foo;
int get_foo() { return foo; }
EOF
cat <<EOF | $CC -o $t/c.o -c -xc -
#include <stdio.h>
int get_foo();
int main() { printf("%d\n", get_foo()); }
EOF
$CC -B. -o $t/exe $t/a.o $t/b.o $t/c.o -pie -Wl,--relax
$QEMU $t/exe | grep '^66$'
$OBJDUMP -d $t/exe | grep -A2 '<get_foo>:' | grep -Fw pcalau12i
+98
View File
@@ -0,0 +1,98 @@
#!/bin/bash
. $(dirname $0)/common.inc
# When mold relaxes instructions, the relocations attached to the relaxed code
# must be rewritten for --emit-relocs. A shortened call still references its
# symbol, so R_RISCV_CALL_PLT becomes R_RISCV_JAL or R_RISCV_RVC_JUMP. A
# relaxation that folds an address into the instruction as a link-time
# constant (lui/addi or an AUIPC+LD GOT load) leaves the instruction without a
# relocation, so we emit R_RISCV_NONE.
cat <<'EOF' | $CC -o $t/a.o -c -xassembler -
.text
.globl _start
_start:
# CALL_PLT => jal (near target)
.reloc ., R_RISCV_RELAX
call func
# lui + addi of a small absolute symbol => lui is deleted (HI20 => NONE),
# while the low-part relocation stays.
lui t0, %hi(small)
.reloc ., R_RISCV_RELAX
addi t0, t0, %lo(small)
# GOT load of an absolute symbol => the value is materialized directly
# (GOT_HI20 and its paired PCREL_LO12_I both => NONE).
got_anchor:
.reloc ., R_RISCV_GOT_HI20, small
.reloc ., R_RISCV_RELAX
auipc t1, 0
.reloc ., R_RISCV_PCREL_LO12_I, got_anchor
.reloc ., R_RISCV_RELAX
ld t1, 0(t1)
# CALL with rd=zero (tail call) to a near target => c.j (=> RVC_JUMP)
.reloc ., R_RISCV_RELAX
tail do_exit
func:
ret
do_exit:
li a0, 0
li a7, 93 # __NR_exit
ecall
EOF
cat <<'EOF' | $CC -o $t/b.o -c -xassembler -
.globl small
small = 0xba
EOF
$CC -B. -o $t/exe $t/a.o $t/b.o --static -nostdlib -Wl,-e,_start,--emit-relocs,--relax
$QEMU $t/exe
$OBJDUMP -dr $t/exe > $t/exe.objdump
# A shortened call keeps a relocation that names the symbol.
grep -Fw R_RISCV_JAL $t/exe.objdump
grep -Fw R_RISCV_RVC_JUMP $t/exe.objdump
# Address-materializing relaxations drop their relocations.
grep -Fw R_RISCV_NONE $t/exe.objdump
# The stale pre-relaxation relocations are gone.
not grep -Fw R_RISCV_CALL_PLT $t/exe.objdump
not grep -Fw R_RISCV_GOT_HI20 $t/exe.objdump
not grep -Fw R_RISCV_HI20 $t/exe.objdump
# The low-part relocation of the lui/addi pair survives (the addi is kept).
grep -Fw R_RISCV_LO12_I $t/exe.objdump
# Each rewritten call relocation must sit on the relaxed instruction it
# describes: R_RISCV_JAL on a jal and R_RISCV_RVC_JUMP on a c.j. This checks
# that the relocation offsets were adjusted for the deleted bytes.
awk '
$3 == "jal" { insn[$1] = "jal" }
$3 == "j" || $3 == "c.j" { insn[$1] = "cj" }
$2 == "R_RISCV_JAL" { if (insn[$1] != "jal") { bad=1; print "bad offset: " $0 } }
$2 == "R_RISCV_RVC_JUMP" { if (insn[$1] != "cj") { bad=1; print "bad offset: " $0 } }
END { exit bad+0 }
' $t/exe.objdump
# TLS local-exec: `lui + add` that materializes TP + %tprel_hi are deleted for
# a variable within 2 KiB of TP (TPREL_HI20 and TPREL_ADD => NONE), while the
# access relative to TP keeps its low-part relocation.
cat <<'EOF' | $CC -o $t/tls.o -c -xc - -O2 -fno-section-anchors
__thread int tv;
int main(void) { return tv; } // tv is zero-initialized, so exits 0
EOF
$CC -B. -o $t/tls $t/tls.o -static -Wl,--emit-relocs,--relax
$QEMU $t/tls
$OBJDUMP -dr $t/tls > $t/tls.objdump
grep -E $'R_RISCV_TPREL_LO12_[IS][ \t]+tv' $t/tls.objdump
not grep -E $'R_RISCV_TPREL_HI20[ \t]+tv' $t/tls.objdump
not grep -E $'R_RISCV_TPREL_ADD[ \t]+tv' $t/tls.objdump
+167
View File
@@ -0,0 +1,167 @@
#!/bin/bash
. $(dirname $0)/common.inc
# Verify that the .sframe section mold produces is not just well-formed but
# actually usable for stack unwinding. We link a program that, using only
# its own merged .sframe (read via libsframe), walks a known call chain and
# checks that each recovered frame maps to the expected function.
# Skip unless the assembler emits SFrame Version 3.
cat <<EOF | $CC -Wa,--gsframe -o $t/probe.o -c -xc - 2>/dev/null || skip
int main() { return 0; }
EOF
readelf --sframe=.sframe $t/probe.o 2>/dev/null | grep -q SFRAME_VERSION_3 || skip
# Skip unless libsframe and its header are available.
echo 'int main(){return 0;}' | \
$CC -o $t/probe -xc - -lsframe -include sframe-api.h 2>/dev/null || skip
cat <<'EOF' | $CC -O0 -g -fno-omit-frame-pointer -Wa,--gsframe -B. -o $t/exe -xc - -lsframe
#define _GNU_SOURCE
#include <sframe-api.h>
#include <link.h>
#include <stdint.h>
#include <stdio.h>
#ifndef PT_GNU_SFRAME
#define PT_GNU_SFRAME 0x6474e554
#endif
static uintptr_t sec_addr;
static size_t sec_size;
static int find_cb(struct dl_phdr_info *info, size_t sz, void *u) {
(void)sz; (void)u;
for (int i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *p = &info->dlpi_phdr[i];
if (p->p_type == PT_GNU_SFRAME) {
sec_addr = info->dlpi_addr + p->p_vaddr;
sec_size = p->p_memsz;
return 1;
}
}
return 0;
}
static sframe_decoder_ctx *dctx;
static int32_t fixed_ra;
#define MAXF 4096
static uintptr_t fstart[MAXF], fend[MAXF];
static int nfde;
static int find_fde(uintptr_t pc) {
for (int i = 0; i < nfde; i++)
if (fstart[i] <= pc && pc < fend[i])
return i;
return -1;
}
// One SFrame unwind step per loop iteration; records the start address of
// the function each frame belongs to.
static int unwind(uintptr_t pc, uintptr_t sp, uintptr_t fp,
uintptr_t *out, int max) {
int n = 0;
while (n < max) {
int idx = find_fde(pc);
if (idx < 0)
break;
out[n++] = fstart[idx];
uint32_t num_fres, func_size;
int64_t spo;
unsigned char fi, fi2;
uint8_t rep;
if (sframe_decoder_get_funcdesc_v3(dctx, idx, &num_fres, &func_size,
&spo, &fi, &fi2, &rep))
break;
uint32_t fde_type = SFRAME_V3_FDE_TYPE(fi2);
int64_t off = pc - fstart[idx];
sframe_frame_row_entry fre, cur;
int have = 0;
for (uint32_t k = 0; k < num_fres; k++) {
if (sframe_decoder_get_fre(dctx, idx, k, &cur))
break;
if ((int64_t)cur.fre_start_addr <= off) { fre = cur; have = 1; }
else break;
}
if (!have)
break;
int e = 0;
int base = sframe_fre_get_base_reg_id(&fre, &e);
int32_t cfa_off = sframe_fre_get_cfa_offset(dctx, &fre, fde_type, &e);
uintptr_t cfa = (base == SFRAME_BASE_REG_SP ? sp : fp) + cfa_off;
uintptr_t ra = *(uintptr_t *)(cfa + fixed_ra);
e = 0;
int32_t fp_off = sframe_fre_get_fp_offset(dctx, &fre, fde_type, &e);
uintptr_t next_fp = (e == 0) ? *(uintptr_t *)(cfa + fp_off) : fp;
if (!ra)
break;
sp = cfa;
fp = next_fp;
pc = ra;
}
return n;
}
static uintptr_t chain[16];
static int chain_n;
__attribute__((noinline)) static int level3(int x) {
uintptr_t pc, sp, fp;
asm volatile("leaq 1f(%%rip), %0\n1:\n\t"
"movq %%rsp, %1\n\t"
"movq %%rbp, %2"
: "=r"(pc), "=r"(sp), "=r"(fp));
chain_n = unwind(pc, sp, fp, chain, 16);
return x + 1;
}
__attribute__((noinline)) static int level2(int x) { return level3(x) + x; }
__attribute__((noinline)) static int level1(int x) { return level2(x) + x; }
int main(void) {
if (!dl_iterate_phdr(find_cb, NULL) || !sec_addr)
return 1;
int err = 0;
dctx = sframe_decode((const char *)sec_addr, sec_size, &err);
if (!dctx)
return 1;
fixed_ra = sframe_decoder_get_fixed_ra_offset(dctx);
uint32_t num = sframe_decoder_get_num_fidx(dctx);
for (uint32_t i = 0; i < num && nfde < MAXF; i++) {
uint32_t num_fres, func_size;
int64_t spo;
unsigned char fi, fi2;
uint8_t rep;
if (sframe_decoder_get_funcdesc_v3(dctx, i, &num_fres, &func_size,
&spo, &fi, &fi2, &rep))
continue;
int e = 0;
uint32_t fo = sframe_decoder_get_offsetof_fde_start_addr(dctx, i, &e);
fstart[nfde] = sec_addr + fo + spo; // PCREL: relative to the field
fend[nfde] = fstart[nfde] + func_size;
nfde++;
}
level1(0);
uintptr_t want[] = { (uintptr_t)&level3, (uintptr_t)&level2,
(uintptr_t)&level1, (uintptr_t)&main };
int n = sizeof(want) / sizeof(want[0]);
if (chain_n < n)
return 1;
for (int i = 0; i < n; i++)
if (chain[i] != want[i])
return 1;
return 0;
}
EOF
$QEMU $t/exe
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -g -o $t/a.o -xc -
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() { printf("Hello world\n"); }
EOF
# Level 0 stores data uncompressed; level 9 compresses hardest.
# The level-0 executable must be larger than the level-9 one.
$CC -B. -o $t/exe0 $t/a.o -Wl,--compress-debug-sections=zlib:0
$CC -B. -o $t/exe9 $t/a.o -Wl,--compress-debug-sections=zlib:9
readelf -WS $t/exe0 | grep '\.debug_info .* [Cx] '
readelf -WS $t/exe9 | grep '\.debug_info .* [Cx] '
[ $(wc -c < $t/exe0) -gt $(wc -c < $t/exe9) ]
# Out-of-range level should fail with a descriptive error
not $CC -B. -o $t/exe-1 $t/a.o -Wl,--compress-debug-sections=zlib:-1 |&
grep 'zlib level must be between 0 and 9'
not $CC -B. -o $t/exe10 $t/a.o -Wl,--compress-debug-sections=zlib:10 |&
grep 'zlib level must be between 0 and 9'
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -g -o $t/a.o -xc -
#include <stdio.h>
int main() { printf("Hello world\n"); }
EOF
# Test zstd:1 (lowest level)
$CC -B. -o $t/exe1 $t/a.o -Wl,--compress-debug-sections=zstd:1
readelf -WS $t/exe1 | grep '\.debug_info .* [Cx] '
# Test zstd:22 (highest level)
$CC -B. -o $t/exe22 $t/a.o -Wl,--compress-debug-sections=zstd:22
readelf -WS $t/exe22 | grep '\.debug_info .* [Cx] '
# Out-of-range level should fail with a descriptive error
not $CC -B. -o $t/exe0 $t/a.o -Wl,--compress-debug-sections=zstd:0 |&
grep 'zstd level must be between 1 and 22'
not $CC -B. -o $t/exe23 $t/a.o -Wl,--compress-debug-sections=zstd:23 |&
grep 'zstd level must be between 1 and 22'
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
. $(dirname $0)/common.inc
# A section whose name is a valid C identifier should be GC'd if
# neither its contents nor its __start_/__stop_ marker symbols are
# referenced from a live section.
cat <<EOF | $CC -c -o $t/a.o -xc -
__attribute__((section("foo"))) int dead_data = 42;
__attribute__((section("bar"))) int live_data = 99;
extern int __start_bar[];
extern int __stop_bar[];
int main() {
return __stop_bar - __start_bar + live_data;
}
EOF
$CC -B. -o $t/exe $t/a.o -Wl,-gc-sections
readelf --symbols $t/exe > $t/log
not grep dead_data $t/log
grep live_data $t/log
grep __start_bar $t/log
grep __stop_bar $t/log
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
on_qemu && skip
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
test_cflags -gdwarf-5 -g || skip
# Generate many translation units that all reference common types and
# names so the per-name CU index list contains entries from multiple
# threads. This stresses the parallel writer in --gdb-index that fills
# in (type, cu) tuples for each name, which must produce a deterministic
# output regardless of thread scheduling.
objs=
for i in $(seq 1 24); do
cat <<EOF > $t/u$i.c
#include <stdio.h>
struct s$i { int x; };
int fn$i(struct s$i *p) { return printf("%d\n", p->x); }
EOF
$CC -c -o $t/u$i.o $t/u$i.c -fPIC -g -ggnu-pubnames
objs="$objs $t/u$i.o"
done
$CC -B. -shared -o $t/out1.so -Wl,--gdb-index $objs
$CC -B. -shared -o $t/out2.so -Wl,--gdb-index $objs
$CC -B. -shared -o $t/out3.so -Wl,--gdb-index $objs
$CC -B. -shared -o $t/out4.so -Wl,--gdb-index $objs
$CC -B. -shared -o $t/out5.so -Wl,--gdb-index $objs
cmp $t/out1.so $t/out2.so
cmp $t/out1.so $t/out3.so
cmp $t/out1.so $t/out4.so
cmp $t/out1.so $t/out5.so
+3 -3
View File
@@ -63,8 +63,8 @@ $CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -ffunction-sections
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
readelf -WS $t/e.so 2> /dev/null | grep -F .gdb_index
readelf --debug=gdb_index $t/e.so 2> /dev/null | grep 'fn1: .* \[global, function\]'
readelf --debug=gdb_index $t/e.so 2> /dev/null | grep 'char: .* \[static, type\]'
readelf --debug-dump=gdb_index $t/e.so 2> /dev/null | grep 'fn1: .* \[global, function\]'
readelf --debug-dump=gdb_index $t/e.so 2> /dev/null | grep 'char: .* \[static, type\]'
cat <<EOF | $CC -c -o $t/f.o -fPIC -g -ggnu-pubnames -gdwarf-5 -xc - -gz
void fn1();
@@ -76,7 +76,7 @@ EOF
$CC -B. -o $t/exe $t/e.so $t/f.o -Wl,--gdb-index
readelf -WS $t/exe 2> /dev/null | grep -F .gdb_index
readelf --debug=gdb_index $t/exe 2> /dev/null | grep 'main: .* \[global, function\]'
readelf --debug-dump=gdb_index $t/exe 2> /dev/null | grep 'main: .* \[global, function\]'
$QEMU $t/exe | grep 'Hello world'
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
. $(dirname $0)/common.inc
# Regression test for https://github.com/rui314/mold/issues/1578.
# When DWARF 5 .debug_rnglists' offset table contains entries for both
# the CU root and child DIEs, mold must read only the entry indexed by
# the CU's DW_FORM_rnglistx. Otherwise child DIE ranges leak into the
# .gdb_index address table, overlapping the CU's enclosing range.
[ $MACHINE = $(uname -m) ] || skip
echo 'int main() {}' | clang++ -gdwarf-5 -g -o /dev/null -xc++ - 2> /dev/null || skip
cat <<EOF > $t/a.cc
void may_throw(int x) {
if (x < 0)
throw x;
}
struct Guard {
int v;
~Guard() { may_throw(v); }
};
int main() {
{
Guard g{1};
may_throw(2);
}
}
EOF
clang++ -B. -O0 -ggdb3 -ggnu-pubnames -gdwarf-5 \
-Wl,--gdb-index $t/a.cc -o $t/exe
readelf --debug-dump=gdb_index $t/exe |
sed -n '/^Address table:/,/^$/p' |
grep -E '^[0-9a-f]+ [0-9a-f]+' |
sort |
awk 'NR > 1 && $1 < prev { exit 1 } { prev = $2 }'
Executable
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
. $(dirname $0)/common.inc
# Verify that mold can link object files compiled with Clang's HWASAN
# instrumentation. On aarch64 such objects contain relocations like
# R_AARCH64_MOVW_PREL_G3 that mold previously rejected
# (https://github.com/rui314/mold/issues/1592).
[ "$CC" = cc ] || skip
# Disable HWASAN's global-variable tagging. It is orthogonal to the
# relocations exercised here, and old Clang implements it by baking a tag
# into the high bits of a 32-bit relocation's addend, producing objects
# that no linker (mold, GNU ld or lld) can resolve.
cat <<EOF | clang -fsanitize=hwaddress -mllvm -hwasan-globals=0 -c -o $t/a.o -xc - 2> /dev/null || skip
#include <stdio.h>
int main() { printf("Hello world\n"); }
EOF
# Skip unless this toolchain can produce a HWASAN executable at all (e.g.
# the sanitizer runtime may not be installed). We probe with Clang's
# default linker so that a mold-specific failure below is still reported
# as a test failure rather than silently skipped.
clang -fsanitize=hwaddress -o $t/exe $t/a.o 2> /dev/null || skip
clang -fsanitize=hwaddress -B. -o $t/exe $t/a.o
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
. $(dirname $0)/common.inc
# This test is only applicable to x86 and x86_64 because they use the lowest
# bit of member function pointers for virtuality.
case $MACHINE in
x86_64 | i686) ;;
*) skip ;;
esac
# We use assembly to define functions to have full control over alignment.
# We swap the order so that regular_func (alignment 1) comes first and
# is more likely to be chosen as the leader by ICF.
# We make pre_func size 2 to try to make regular_func start at an odd address.
cat <<EOF | $CC -c -o $t/fns.o -x assembler -
.text
.section .text.pre_func, "ax", @progbits
.globl pre_func
.p2align 0
pre_func:
.byte 0x90, 0x90 # two nops (size 2)
.section .text.regular_func, "ax", @progbits
.globl regular_func
.p2align 0 # 1-byte alignment
regular_func:
ret
.section .text.member_func, "ax", @progbits
.globl _ZN3Foo11member_funcEv
.p2align 2 # 4-byte alignment
_ZN3Foo11member_funcEv:
ret
EOF
cat <<EOF | $CXX -c -o $t/main.o -xc++ -
#include <iostream>
#include <stdint.h>
struct Foo {
void member_func();
};
extern "C" void regular_func();
int main() {
void (Foo::*ptr)() = &Foo::member_func;
union {
void (Foo::*mem_ptr)();
struct {
void* ptr;
ptrdiff_t adj;
} parts;
} u;
u.mem_ptr = ptr;
std::cout << "member_func address: " << u.parts.ptr << std::endl;
std::cout << "regular_func address: " << (void*)&regular_func << std::endl;
if (u.parts.ptr != (void*)&regular_func) {
std::cerr << "FAIL: functions were not folded!" << std::endl;
return 2;
}
if (reinterpret_cast<uintptr_t>(u.parts.ptr) & 1) {
std::cerr << "FAIL: member function pointer has odd address (treated as virtual)" << std::endl;
return 1;
}
return 0;
}
EOF
$CXX -B. -o $t/exe $t/main.o $t/fns.o -Wl,--icf=all
$QEMU $t/exe
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
. $(dirname $0)/common.inc
# On PPC64V1, function pointers refer function descriptors in .opd
# instead of directly referring .text section.
[ $MACHINE = ppc64 ] && skip
cat <<EOF | $CC -c -o $t/a.o -ffunction-sections -fPIC -xc -
void local_fn(void) {}
__attribute__((visibility("hidden"))) void local_fn_hidden(void) {
local_fn();
}
void global_fn(void) {}
void caller_local(void) {
local_fn_hidden();
}
void caller_global(void) {
global_fn();
}
EOF
# Link as shared library with --icf=all
$CC -B. -shared -o $t/libtest.so $t/a.o -Wl,--icf=all -Wl,--print-icf-sections > $t/out 2>&1
# Verify that caller_local and caller_global were NOT folded.
# If they were folded, one of them would be removed.
not grep -q "removing identical section.*caller_local" $t/out
not grep -q "removing identical section.*caller_global" $t/out
# Also check with nm to be sure they have different addresses
nm $t/libtest.so | grep -E 'caller_local|caller_global' | awk '{print $1}' | uniq | wc -l | grep -q 2
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() {}
EOF
echo 'ASSERT(SIZEOF(.text) > 0, "text must not be empty")' > $t/good
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/good
echo 'ASSERT(SIZEOF(.text) == 0, "this should fail")' > $t/bad
not $CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/bad |& grep 'this should fail'
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() { return 0; }
EOF
# `. = ASSERT(expr, msg);` after SECTIONS is a common idiom for a
# standalone assertion, e.g. the Linux kernel's KERNEL_IMAGE_SIZE
# check
cat <<'EOF' > $t/script
SECTIONS {
. = 0x10000;
.text : { *(.text .text.*) }
_end = .;
}
. = ASSERT(_end - 0x10000 < 0x100000, "image too big");
EOF
./mold -o $t/exe $t/a.o -T $t/script
cat <<'EOF' > $t/script2
SECTIONS {
. = 0x10000;
.text : { *(.text .text.*) }
_end = .;
}
. = ASSERT(_end - 0x10000 > 0x100000, "this should fail");
EOF
not ./mold -o $t/exe2 $t/a.o -T $t/script2 |& grep 'this should fail'
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() {}
EOF
cat <<'EOF' > $t/script
text_end = ADDR(.text) + SIZEOF(.text);
big = MAX(0x100, 0x200);
aligned = ALIGN(0x1234, 0x1000);
cond = DEFINED(no_such_symbol) ? no_such_symbol : 7;
EOF
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script
readelf -sW $t/exe > $t/log
# text_end must be relative to the .text output section, not absolute
text=$(readelf -SW $t/exe | grep -F ' .text' | head -1 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
grep -E "text_end" $t/log | grep -vw ABS | grep -w $text
grep -E '200 .* ABS big' $t/log
grep -E '2000 .* ABS aligned' $t/log
grep -E '7 .* ABS cond' $t/log
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".mydata"))) int d = 3;
int main() { return 0; }
EOF
# .mydata runs at a high address but is stored (loaded) right after
# .text, like a kernel or firmware image
cat <<'EOF' > $t/script
SECTIONS {
. = 0x100000;
.text : { *(.text .text.*) }
. = 0x800000;
.mydata : AT(ADDR(.mydata) - 0x600000) { *(.mydata) }
data_lma = LOADADDR(.mydata);
data_vma = ADDR(.mydata);
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -lW $t/exe > $t/log
# The load segment for .mydata must have PhysAddr = VirtAddr - 0x600000
grep -E 'LOAD .*0x0*800000 0x0*200000' $t/log
readelf -sW $t/exe > $t/log2
grep -E '200000 .* data_lma' $t/log2
grep -E '800000 .* data_vma' $t/log2
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".aa"))) int a = 1;
__attribute__((section(".bb"))) int b = 2;
int main() { return 0; }
EOF
# end_aa lies exactly on the boundary of .aa and .bb. It must be
# associated with the preceding section, as in GNU ld; tools strip
# sections (e.g. objcopy --remove-section) together with the symbols
# they contain.
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.aa : { *(.aa) }
end_aa = .;
.bb : { *(.bb) }
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
idx=$(readelf -SW $t/exe | grep -F " .aa" | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
readelf -sW $t/exe | grep -w end_aa | grep -w $idx
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() { return 0; }
EOF
# A section made only of data commands, padded with a fill pattern.
# The script's own ASSERTs check the layout.
cat <<'EOF' > $t/script
SECTIONS {
. = 0x10000;
.text : { *(.text .text.*) }
.blob : ALIGN(16) {
blob_start = .;
BYTE(0x12)
SHORT(0x3456)
LONG(0x789abcde)
QUAD(0x1122334455667788)
. = ALIGN(16);
blob_end = .;
} =0xaaaaaaaa
ASSERT(blob_end - blob_start == 16, "bad blob size")
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
off=$(readelf -SW $t/exe | grep -F .blob | awk '{print strtonum("0x" $6)}')
test "$(dd if=$t/exe bs=1 skip=$off count=1 2>/dev/null | od -An -tx1 | tr -d ' ')" = 12
# The padding byte at offset 15 comes from the fill pattern, which
# starts anew at each gap
test "$(dd if=$t/exe bs=1 skip=$((off + 15)) count=1 2>/dev/null | od -An -tx1 | tr -d ' ')" = aa
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() {}
EOF
# A symbol assignment to an integer literal behaves like --defsym
cat <<'EOF' > $t/script
foo = 0x1234;
bar = 256;
EOF
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script
readelf -sW $t/exe > $t/log
grep -E '1234 .* ABS foo' $t/log
grep -E '100 .* ABS bar' $t/log
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".foo"))) int foo_var = 1;
__attribute__((section(".bar.x"))) int bar_var = 2;
__attribute__((section(".keep"))) int keep_var = 3;
int main() { return 0; }
EOF
cat <<'EOF' > $t/script
SECTIONS {
/DISCARD/ : { *(.foo) *(.bar.*) }
}
EOF
$CC -B. -o $t/exe1 $t/a.o -Wl,-T,$t/script
readelf -SW $t/exe1 > $t/log1
not grep -F .foo $t/log1
not grep -F .bar $t/log1
grep -F .keep $t/log1
# EXCLUDE_FILE and per-file patterns restrict what is discarded
cat <<'EOF' > $t/script2
SECTIONS {
/DISCARD/ : { *(EXCLUDE_FILE(*a.o) .foo) no-such-file.o(.bar.*) }
}
EOF
$CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/script2
readelf -SW $t/exe2 > $t/log2
grep -F .foo $t/log2
grep -F .bar.x $t/log2
# A /DISCARD/ with output section attributes is not a plain discard
# and must be rejected
cat <<'EOF' > $t/script3
SECTIONS {
/DISCARD/ 0x1000 : { *(.foo) }
}
EOF
not $CC -B. -o $t/exe3 $t/a.o -Wl,-T,$t/script3 |& grep 'cannot have output section attributes'
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".dat"), aligned(4))) int d = 7;
int main() { return 0; }
EOF
# Location counter arithmetic in an output section body. The script's
# own assertions verify the layout, including a symbol defined from an
# earlier body symbol.
cat <<'EOF' > $t/script
SECTIONS {
. = 0x10000;
.text : { *(.text .text.*) }
.dat : ALIGN(32) {
dat_start = .;
. += 16;
reserved_end = .;
*(.dat)
. = dat_start + 64;
dat_end = .;
}
ASSERT(reserved_end - dat_start == 16, "bad reservation")
ASSERT(dat_end - dat_start == 64, "bad section size")
ASSERT(SIZEOF(.dat) == 64, "bad SIZEOF")
ASSERT(ADDR(.dat) % 32 == 0, "bad alignment")
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -SW $t/exe | grep -E "\.dat .* 000040"
# The input section was placed after the 16-byte reservation
readelf -sW $t/exe > $t/log
dat=$(grep -w dat_start $t/log | awk '{print strtonum("0x" $2)}')
d=$(grep -E " d$" $t/log | grep OBJECT | awk '{print strtonum("0x" $2)}')
test $((d - dat)) = 16
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
. $(dirname $0)/common.inc
# A vDSO-shaped link: a shared library whose script places the
# linker-synthesized dynamic sections and defines the program headers
cat <<EOF | $CC -o $t/a.o -c -xc -fPIC -
int foo() { return 42; }
EOF
cat <<'EOF' > $t/script
PHDRS {
text PT_LOAD FLAGS(5) FILEHDR PHDRS;
dyn PT_DYNAMIC FLAGS(4);
}
SECTIONS {
. = SIZEOF_HEADERS;
.dynsym : { *(.dynsym) } :text
.dynstr : { *(.dynstr) }
.text : { *(.text .text.*) }
.dynamic : { *(.dynamic) } :text :dyn
}
EOF
./mold -o $t/b.so -shared $t/a.o -T $t/script
readelf -lW $t/b.so > $t/log
# The script's segments, with FILEHDR extending :text back to offset 0
grep -E 'LOAD .*0x000000 .* R E' $t/log
grep -E 'DYNAMIC ' $t/log
grep -E '01 .*\.dynamic' $t/log
# Synthesized sections are placed where the script says: .dynsym
# before .text, .dynamic after
readelf -SW $t/b.so > $t/log2
dynsym=$(grep -F ' .dynsym' $t/log2 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
text=$(grep -wF ' .text' $t/log2 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
dynamic=$(grep -F ' .dynamic' $t/log2 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
test "$dynsym" -lt "$text"
test "$text" -lt "$dynamic"
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() { return 0; }
EOF
# Sections that never materialize: their body symbols still get
# addresses, the location counter still moves, and SIZEOF/ADDR of
# such sections read as zero so assertions about them work.
cat <<'EOF' > $t/script
SECTIONS {
. = 0x10000;
.text : { *(.text .text.*) }
. = 0x20000;
.empty1 : { empty1_start = .; KEEP(*(.no_such_input)) empty1_end = .; }
.reserve : { . += 0x100; }
after_reserve = .;
ASSERT(SIZEOF(.empty1) == 0, "SIZEOF of an empty section must be 0")
ASSERT(after_reserve == 0x20100, "the reservation must move the dot")
.data : { *(.data .data.*) }
.bss : { *(.bss .bss.*) }
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -sW $t/exe > $t/log
grep -E '20000 .* empty1_start' $t/log
grep -E '20000 .* empty1_end' $t/log
grep -E '20100 .* after_reserve' $t/log
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
void foo() {}
int main() {}
EOF
echo 'ENTRY(foo)' > $t/script
# ENTRY(foo) sets the entry point
$CC -B. -o $t/exe1 $t/a.o -Wl,-T,$t/script
sym=$(readelf -sW $t/exe1 | grep -w foo | awk '{print $2}' | sed 's/^0*//')
readelf -h $t/exe1 | grep "Entry point address:.*0x$sym"
# The -e option takes precedence over ENTRY()
$CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/script -Wl,-e,main
sym=$(readelf -sW $t/exe2 | grep -w main | awk '{print $2}' | sed 's/^0*//')
readelf -h $t/exe2 | grep "Entry point address:.*0x$sym"
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int foo() { return 3; }
EOF
rm -f $t/libfoo.a
ar rcs $t/libfoo.a $t/a.o
cat <<EOF | $CC -o $t/b.o -c -xc -
int main() {}
EOF
# Without EXTERN, the archive member is not pulled out
$CC -B. -o $t/exe1 $t/b.o $t/libfoo.a
not grep -qw foo <(readelf -sW $t/exe1)
# EXTERN(foo) is equivalent to -u foo
echo 'EXTERN(foo)' > $t/script
$CC -B. -o $t/exe2 $t/b.o $t/libfoo.a -Wl,-T,$t/script
readelf -sW $t/exe2 | grep -w foo
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".dat"))) char a = 1;
int main() { return 0; }
EOF
cat <<EOF | $CC -o $t/b.o -c -xc -
__attribute__((section(".dat"))) char b = 2;
EOF
# Input sections can be selected by the file they come from
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.first : { *b.o(.dat) }
.rest : { *(.dat) }
}
EOF
./mold -o $t/exe $t/a.o $t/b.o -T $t/script
readelf -SW $t/exe > $t/log
grep -F .first $t/log
grep -F .rest $t/log
off=$(readelf -SW $t/exe | grep -F .first | awk '{print strtonum("0x" $6)}')
test "$(dd if=$t/exe bs=1 skip=$off count=1 2>/dev/null | od -An -tx1 | tr -d ' ')" = 02
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() { return 0; }
EOF
# The gap created by moving the location counter is written with the
# fill pattern
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.pad : {
BYTE(0x11)
. += 15;
BYTE(0x22)
} =0xcafe01aa
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
off=$(readelf -SW $t/exe | grep -F " .pad" | awk '{print strtonum("0x" $6)}')
bytes=$(dd if=$t/exe bs=1 skip=$off count=17 2>/dev/null | od -An -tx1 | tr -d ' \n')
# member byte, then 15 fill bytes (the big-endian pattern restarts at
# each gap), then the second byte
test "$bytes" = 11cafe01aacafe01aacafe01aacafe0122
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() {}
EOF
echo 'INCLUDE inner.script' > $t/outer.script
echo 'foo = 0xbeef;' > $t/inner.script
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/outer.script
readelf -sW $t/exe | grep -E 'beef .* ABS foo'
# An error in an included file should be reported with the name of
# the included file.
echo 'INCLUDE bad.script' > $t/outer2.script
echo 'NO_SUCH_COMMAND' > $t/bad.script
not $CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/outer2.script |& grep 'bad.script:1:'
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".tbl.20"))) char t20 = 20;
__attribute__((section(".tbl.3"))) char t3 = 3;
int main() { return 0; }
EOF
# The Linux kernel idiom: KEEP'ed, sorted table sections
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.tbl : {
tbl_start = .;
KEEP(*(SORT_BY_INIT_PRIORITY(.tbl.*)))
tbl_end = .;
}
}
EOF
./mold -o $t/exe $t/a.o -T $t/script --gc-sections -e main
readelf -sW $t/exe > $t/log
# Both survive gc, in numeric order
a3=$(grep -E " t3$" $t/log | awk '{print strtonum("0x" $2)}')
a20=$(grep -E " t20$" $t/log | awk '{print strtonum("0x" $2)}')
test $a3 -lt $a20
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
__attribute__((section(".keepme"))) int keep_var = 1;
__attribute__((section(".dropme"))) int drop_var = 2;
int main() { return 0; }
EOF
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.keepme : { KEEP(*(.keepme)) }
.dropme : { *(.dropme) }
}
EOF
./mold -o $t/exe $t/a.o -T $t/script --gc-sections -e main
readelf -SW $t/exe > $t/log
# KEEP saves an otherwise unreferenced section from --gc-sections
grep -F .keepme $t/log
not grep -F .dropme $t/log
+84
View File
@@ -0,0 +1,84 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".text.main"))) int main() { return 0; }
__attribute__((section(".rodata.str"))) const char ro[16] = "read-only";
__attribute__((section(".data.var"))) int var = 7;
__attribute__((section(".bss.var"))) int zero;
EOF
# The classic microcontroller layout: code and constants in flash,
# data in RAM with its initialization image stacked in flash
cat <<'EOF' > $t/script
MEMORY {
flash (rx) : ORIGIN = 0x8000000, LENGTH = 128K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}
REGION_ALIAS("rom", flash);
SECTIONS {
.text : { *(.text .text.*) } >rom
.rodata : { *(.rodata .rodata.*) } >flash
.data : { *(.data .data.*) } >ram AT>flash
.bss : { *(.bss .bss.*) } >ram
data_lma = LOADADDR(.data);
flash_end = ORIGIN(flash) + LENGTH(flash);
ASSERT(data_lma + SIZEOF(.data) <= flash_end, "flash overflow")
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
addr_of() {
readelf -SW $1 | grep -F " $2 " | sed 's/.*BITS//' | \
awk '{print strtonum("0x" $1)}'
}
in_range() {
test $1 -ge $(($2)) && test $1 -lt $(($3))
}
# Code and constants are in flash, data and bss in RAM
test $(addr_of $t/exe .text) = $((0x8000000))
in_range $(addr_of $t/exe .rodata) 0x8000000 0x8020000
in_range $(addr_of $t/exe .data) 0x20000000 0x20010000
in_range $(addr_of $t/exe .bss) 0x20000000 0x20010000
# .data's load image is in flash, after the read-only sections
readelf -sW $t/exe > $t/log2
rodata_end=$(readelf -SW $t/exe | grep -F " .rodata " | sed 's/.*PROGBITS//' | \
awk '{print strtonum("0x" $1) + strtonum("0x" $3)}')
lma=$(grep -w data_lma $t/log2 | awk '{print strtonum("0x" $2)}')
test $lma -ge $rodata_end
test $lma -lt $((0x8020000))
readelf -lW $t/exe | grep -E 'LOAD .*0x0*2000[0-9a-f]{4} 0x0*8[0-9a-f]{6} .* RW'
# Overflowing a region must be reported
cat <<'EOF' > $t/script2
MEMORY {
flash (rx) : ORIGIN = 0x8000000, LENGTH = 4
ram (rw) : ORIGIN = 0x20000000, LENGTH = 64K
}
SECTIONS { .text : { *(.text .text.*) } >flash }
EOF
not ./mold -o $t/exe2 $t/a.o -T $t/script2 |& \
grep 'overflows memory region flash'
# A section that names no region is placed by region attributes
cat <<'EOF' > $t/script3
MEMORY {
flash (rx) : ORIGIN = 0x8000000, LENGTH = 128K
ram (rw) : ORIGIN = 0x20000000, LENGTH = 64K
}
SECTIONS {
.text : { *(.text .text.*) }
.data : { *(.data .data.*) }
.bss : { *(.bss .bss.*) }
}
EOF
./mold -o $t/exe3 $t/a.o -T $t/script3
test $(addr_of $t/exe3 .text) = $((0x8000000))
in_range $(addr_of $t/exe3 .data) 0x20000000 0x20010000
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -O2 -o $t/a.o -c -xc -
const char *f() { return "hello mold script"; }
EOF
cat <<EOF | $CC -O2 -o $t/b.o -c -xc -
const char *g() { return "hello mold script"; }
int main() { return 0; }
EOF
# Identical string literals from different files must be merged into
# a single copy, placed inside the output section the script names
cat <<'EOF' > $t/script
SECTIONS {
. = 0x10000;
.text : { *(.text .text.*) }
.rodata : { ro_start = .; *(.rodata .rodata.*) ro_end = .; }
ASSERT(ro_end - ro_start > 0, "rodata must not be empty")
}
EOF
./mold -o $t/exe $t/a.o $t/b.o -T $t/script
test $(readelf -p .rodata $t/exe | grep -c 'hello mold script') = 1
# The merged strings lie within the script's section bounds
readelf -sW $t/exe > $t/log
ro_start=$(grep -w ro_start $t/log | awk '{print strtonum("0x" $2)}')
ro_end=$(grep -w ro_end $t/log | awk '{print strtonum("0x" $2)}')
size=$(readelf -SW $t/exe | grep -F " .rodata " | sed 's/.*PROGBITS//' | \
awk '{print strtonum("0x" $3)}')
test $((ro_end - ro_start)) = $size
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".reserve"))) int r = 1;
__attribute__((section(".meta"))) int m = 2;
int main() { return 0; }
EOF
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.reserve (NOLOAD) : { *(.reserve) }
.meta (INFO) : { *(.meta) }
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -SW $t/exe > $t/log
# A NOLOAD section becomes NOBITS; an INFO section loses SHF_ALLOC
grep -F .reserve $t/log | grep -w NOBITS
grep -F .meta $t/log | grep -vw A | grep -w PROGBITS
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".orphan"))) int o = 1;
int main() { return 0; }
EOF
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.data : { *(.data .data.*) }
}
EOF
# An orphan section is placed even though the script doesn't mention it
./mold -o $t/exe $t/a.o -T $t/script
readelf -SW $t/exe | grep -F .orphan
./mold -o $t/exe $t/a.o -T $t/script --orphan-handling=warn |& grep 'placing orphan section .orphan'
not ./mold -o $t/exe2 $t/a.o -T $t/script --orphan-handling=error |& grep 'orphan section .orphan'
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".lo"))) int lo = 1;
int main() { return 0; }
EOF
# Sections may be given addresses in any order
cat <<'EOF' > $t/script
SECTIONS {
.text 0x20000 : { *(.text .text.*) }
.lo 0x10000 : { *(.lo) }
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -SW $t/exe > $t/log
grep -E '\.text .* 0*20000 ' $t/log
grep -E '\.lo .* 0*10000 ' $t/log
# Loadable segments must still be sorted by address
readelf -lW $t/exe | grep LOAD | head -1 | grep -E '0x0*10000 '
# Overlapping sections are an error
cat <<'EOF' > $t/script2
SECTIONS {
.text 0x20000 : { *(.text .text.*) }
.lo 0x20004 : { *(.lo) }
}
EOF
not ./mold -o $t/exe2 $t/a.o -T $t/script2 |& grep 'overlaps with .text'
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".foo"))) char foo[8] = {1};
__attribute__((section(".mydata"))) int d = 1;
__attribute__((section(".mydata2"))) int d2 = 2;
int main() { return 0; }
EOF
cat <<'EOF' > $t/script
PHDRS {
text PT_LOAD FLAGS(5);
data PT_LOAD FLAGS(6);
note PT_NOTE FLAGS(4);
}
SECTIONS {
. = 0x200000;
.text : { *(.text .text.*) } :text
.foo : { *(.foo) } :text :note
. = 0x400000;
.mydata : { *(.mydata) } :data
.mydata2 : { *(.mydata2) }
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -lW $t/exe > $t/log
# Exactly the segments the script asks for, with the given flags
grep -E 'LOAD .* R E' $t/log
grep -E 'LOAD .* RW ' $t/log
grep -E 'NOTE ' $t/log
test $(grep -c LOAD $t/log) = 2
# .foo is in both :text and :note; .mydata2 inherits :data from the
# previous section
grep -E '00 .*\.text .*\.foo' $t/log
grep -E '01 .*\.mydata \.mydata2' $t/log
grep -E '02 .*\.foo' $t/log
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -fPIC -
int foo = 3;
extern char provided;
extern char hidden_sym;
void *use1() { return &provided; }
void *use2() { return &hidden_sym; }
EOF
cat <<'EOF' > $t/script
PROVIDE(foo = 0x999);
PROVIDE(provided = 0x42);
PROVIDE_HIDDEN(hidden_sym = 0x43);
PROVIDE(unreferenced = no_such_symbol);
EOF
$CC -B. -o $t/b.so -shared $t/a.o -Wl,-T,$t/script
readelf -sW $t/b.so > $t/log
# A PROVIDE'd symbol must yield to a definition in an input file
grep -w foo $t/log | grep -v 999
# A referenced PROVIDE'd symbol is defined with the given value
grep -E '42 .* ABS provided' $t/log
grep -E '43 .* ABS hidden_sym' $t/log
# An unreferenced one is not defined, and its value expression is
# not even evaluated
not grep -w unreferenced $t/log
# A PROVIDE'd symbol is exported from a shared library while a
# PROVIDE_HIDDEN'd one is not
readelf --dyn-syms -W $t/b.so > $t/log2
grep -w provided $t/log2
not grep -w hidden_sym $t/log2
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -fPIC -
extern char chain_a;
void *use() { return &chain_a; }
int main() { return 0; }
EOF
# chain_a is referenced by the program; chain_b and chain_c only by
# other PROVIDE expressions. All three must be defined transitively,
# while unused (and its bogus expression) must not be evaluated.
cat <<'EOF' > $t/script
PROVIDE(chain_a = chain_b + 1);
PROVIDE(chain_b = chain_c + 1);
PROVIDE(chain_c = 0x1000);
PROVIDE(unused = no_such_symbol);
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -sW $t/exe > $t/log
grep -E '1002 .* chain_a' $t/log
grep -E '1001 .* chain_b' $t/log
grep -E '1000 .* chain_c' $t/log
not grep -w unused $t/log
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
. $(dirname $0)/common.inc
# Range extension thunks are not supported in script layout yet
[[ $MACHINE = ppc* ]] && skip
# An executable laid out by a linker script must actually run. The
# script places the standard sections; everything else (dynamic
# sections, TLS, etc.) is placed as orphans.
cat <<EOF | $CC -o $t/a.o -c -xc -
#include <stdio.h>
__attribute__((section(".greeting"))) char msg[6] = "Hello";
int main() { printf("%s world\n", msg); }
EOF
cat <<'EOF' > $t/script
SECTIONS {
. = 0x400000 + SIZEOF_HEADERS;
.text : { *(.text .text.*) }
. = ALIGN(CONSTANT(MAXPAGESIZE));
.rodata : { *(.rodata .rodata.*) }
.greeting : { KEEP(*(.greeting)) }
. = ALIGN(CONSTANT(MAXPAGESIZE));
.data : { *(.data .data.*) }
.bss : { *(.bss .bss.* COMMON) }
}
EOF
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script -no-pie
$QEMU $t/exe | grep 'Hello world'
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
. $(dirname $0)/common.inc
mkdir -p $t/dir
cat <<EOF | $CC -o $t/a.o -c -xc -
int foo() { return 3; }
EOF
rm -f $t/dir/libfoo.a
ar rcs $t/dir/libfoo.a $t/a.o
cat <<EOF | $CC -o $t/b.o -c -xc -
#include <stdio.h>
int foo();
int main() { printf("%d\n", foo()); }
EOF
# SEARCH_DIR(dir) is equivalent to -L
cat <<EOF > $t/script
SEARCH_DIR($t/dir)
GROUP(-lfoo)
EOF
$CC -B. -o $t/exe $t/b.o -Wl,-T,$t/script
$QEMU $t/exe | grep 3
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".foo..bar"))) int a = 1;
__attribute__((section(".foo.x"))) int b = 2;
__attribute__((section(".foo"))) int c = 3;
int main() { return 0; }
EOF
# The first matching pattern in the script wins: .foo..bar must go to
# the first output section even though *(.foo.*) also matches it.
cat <<'EOF' > $t/script
SECTIONS {
. = 0x10000;
.foo..bar : { *(.foo..bar) }
.foo : { *(.foo) *(.foo.*) }
.text : { *(.text .text.*) }
.data : { *(.data .data.*) }
.bss : { *(.bss .bss.*) }
}
EOF
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script -no-pie -nostdlib
readelf -SW $t/exe > $t/log
grep -F .foo..bar $t/log
test $(readelf -SW $t/exe | grep -F " .foo" | wc -l) = 2
grep -E "\.foo\.\.bar.*000004" $t/log
grep -E " \.foo .*000008" $t/log
# Sections appear in script order at script addresses
adr1=$(readelf -SW $t/exe | grep -F ".foo..bar" | awk '{print $5}' | sed 's/^0*//')
test "$adr1" = 10000
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIC -
int main() { return 0; }
EOF
# SIZEOF/ADDR of a section the script declares but that doesn't
# materialize evaluate to zero. Our always-created .got placeholder
# must not be claimed by the script's .got description, so the Linux
# kernel's GOT assertions hold in a static link.
cat <<'EOF' > $t/script
SECTIONS {
.text : { *(.text .text.*) }
.got : { *(.got) }
got_size = SIZEOF(.got);
nowhere = ADDR(.got);
ASSERT(SIZEOF(.got) == 0, "expected no GOT input sections")
}
EOF
./mold -o $t/exe $t/a.o -T $t/script
readelf -sW $t/exe > $t/log
grep -E ' 0 .* ABS got_size' $t/log || \
grep -E '^.* 0{8,16} .* got_size' $t/log
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
__attribute__((section(".x.10"))) char c10 = 10;
__attribute__((section(".x.2"))) char c2 = 2;
__attribute__((section(".x.1"))) char c1 = 1;
int main() { return 0; }
EOF
# SORT_BY_NAME is lexicographic: .x.1 .x.10 .x.2
cat <<'EOF' > $t/script1
SECTIONS {
.text : { *(.text .text.*) }
.x : { *(SORT_BY_NAME(.x.*)) }
}
EOF
./mold -o $t/exe1 $t/a.o -T $t/script1
readelf -sW $t/exe1 > $t/log1
a1=$(grep -E " c1$" $t/log1 | awk '{print strtonum("0x" $2)}')
a2=$(grep -E " c2$" $t/log1 | awk '{print strtonum("0x" $2)}')
a10=$(grep -E " c10$" $t/log1 | awk '{print strtonum("0x" $2)}')
test $a1 -lt $a10 && test $a10 -lt $a2
# SORT_BY_INIT_PRIORITY is numeric: .x.1 .x.2 .x.10
cat <<'EOF' > $t/script2
SECTIONS {
.text : { *(.text .text.*) }
.x : { *(SORT_BY_INIT_PRIORITY(.x.*)) }
}
EOF
./mold -o $t/exe2 $t/a.o -T $t/script2
readelf -sW $t/exe2 > $t/log2
a1=$(grep -E " c1$" $t/log2 | awk '{print strtonum("0x" $2)}')
a2=$(grep -E " c2$" $t/log2 | awk '{print strtonum("0x" $2)}')
a10=$(grep -E " c10$" $t/log2 | awk '{print strtonum("0x" $2)}')
test $a1 -lt $a2 && test $a2 -lt $a10
# SORT_NONE keeps the input order
cat <<'EOF2' > $t/script3
SECTIONS {
.text : { *(.text .text.*) }
.x : { *(SORT_NONE(.x.*)) }
}
EOF2
./mold -o $t/exe3 $t/a.o -T $t/script3
readelf -sW $t/exe3 > $t/log3
a2=$(grep -E " c2$" $t/log3 | awk '{print strtonum("0x" $2)}')
a10=$(grep -E " c10$" $t/log3 | awk '{print strtonum("0x" $2)}')
test $a10 -lt $a2
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int main() {}
EOF
# Commands that mold cannot faithfully execute yet must be rejected
# rather than silently ignored.
cat <<'EOF' > $t/script
SECTIONS {
OVERLAY 0x1000 : { .o1 { *(.o1) } }
}
EOF
not ./mold $t/a.o -T $t/script -o $t/exe |& grep 'not supported yet'
echo 'OUTPUT(foo.exe)' > $t/script2
not ./mold $t/a.o -T $t/script2 -o $t/exe |& grep 'not supported yet'
cat <<'EOF' > $t/script3
SECTIONS {
.text : SUBALIGN(8) { *(.text*) }
}
EOF
not ./mold $t/a.o -T $t/script3 -o $t/exe |& grep 'not supported yet'
# A memory region must be declared before use
cat <<'EOF' > $t/script5
SECTIONS {
.text : { *(.text*) } >ram
}
EOF
not ./mold $t/a.o -T $t/script5 -o $t/exe |& grep 'no such memory region'
echo 'SECTIONS { .text : { *(.text*) } }' > $t/script4
not ./mold $t/a.o -T $t/script4 -r -o $t/r.o |& grep 'not supported in relocatable'
Regular → Executable
View File
File mode changed.
View File
File mode changed.
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ "$(uname)" = FreeBSD ] && skip
# libfoo.so defines foo@VER_1.0 as a non-default version. Referencing
# the bare name `foo` does NOT bind to this symbol.
cat <<EOF > $t/libfoo.ver
VER_1.0 {};
EOF
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc - \
-Wl,--version-script=$t/libfoo.ver
__asm__(".symver foo_impl,foo@VER_1.0");
int foo_impl(void) { return 42; }
EOF
# libbar.so references foo@VER_1.0 via .symver. Its dynsym holds an
# undefined symbol with name "foo" whose versym indexes into the
# verneed (.gnu.version_r) entry naming "VER_1.0".
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
__asm__(".symver foo_v1,foo@VER_1.0");
int foo_v1(void);
int call_foo_v1(void) { return foo_v1(); }
EOF
cat <<EOF | $CC -c -o $t/a.o -xc -
int call_foo_v1(void);
int main(void) { return call_foo_v1(); }
EOF
# Both libs are present; libbar.so's undef foo@VER_1.0 is satisfied by
# libfoo.so. --no-allow-shlib-undefined must accept the link.
$CC -B. -o $t/exe $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lbar -lfoo
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
. $(dirname $0)/common.inc
# Object with an entry point: used for PIE / runnable-.so cases
cat <<EOF | $CC -fPIC -xc -c -o $t/a.o -
void _start(void) {}
EOF
# Object without an entry point: used for the regular shared case
cat <<EOF | $CC -fPIC -xc -c -o $t/b.o -
int foo(void) { return 42; }
EOF
DL=/lib/ld.so
# Control: bare -shared (no --dynamic-linker) must NOT emit PT_INTERP or .interp
./mold -shared -o $t/control.so $t/b.o
readelf -l $t/control.so | not grep INTERP
readelf -S $t/control.so | not grep '\.interp'
# Bug case: -shared with --dynamic-linker must NOT emit PT_INTERP or .interp
./mold -shared --dynamic-linker=$DL -o $t/regular.so $t/b.o
readelf -l $t/regular.so | not grep INTERP
readelf -S $t/regular.so | not grep '\.interp'
# Regression: -pie executable still gets PT_INTERP and .interp
./mold -pie --dynamic-linker=$DL -e _start -o $t/pie-exe $t/a.o
readelf -l $t/pie-exe | grep INTERP
readelf -S $t/pie-exe | grep '\.interp'
# Regression: -shared -pie (runnable .so) still gets PT_INTERP and .interp -- BFD parity
./mold -shared -pie --dynamic-linker=$DL -e _start -o $t/runnable.so $t/a.o
readelf -l $t/runnable.so | grep INTERP
readelf -S $t/runnable.so | grep '\.interp'
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -fcommon -xc -c -o $t/a.o -
int foo;
EOF
cat <<EOF | $CC -fcommon -xc -c -o $t/b.o -
int foo;
int main() { return 0; }
EOF
# Control: -warn-common emits a warning by default
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common 2> $t/log
grep -q "multiple common symbols" $t/log
# -w suppresses the warning
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,-w 2> $t/log
not grep -q "multiple common symbols" $t/log
# --no-warnings does the same
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,--no-warnings 2> $t/log
not grep -q "multiple common symbols" $t/log
# Sanity: -fatal-warnings without -w would have failed
not $CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,-fatal-warnings 2> /dev/null
# -w cancels -fatal-warnings (lld parity)
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,-fatal-warnings -Wl,-w
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
# Skip if llvm-readelf isn't available; we use it to decode APS2.
command -v llvm-readelf >/dev/null || skip
cat <<EOF | $CC -o $t/a.o -fPIC -c -xc -
extern int x, y, z;
void *ptrs[] = { &x, &y, &z, &x, &y, &z };
int x = 1, y = 2, z = 3;
int main() { return 0; }
EOF
# Probe whether the host toolchain can produce a working PIE for this $CC.
$CC -o $t/probe $t/a.o -pie 2> /dev/null || skip
# --pack-dyn-relocs=android: all dynrels packed into APS2 .rela.dyn.
$CC -B. -o $t/exe1 $t/a.o -pie -Wl,--pack-dyn-relocs=android
llvm-readelf -SW $t/exe1 | grep -Ew '\.rela?\.dyn' | grep -E 'ANDROID_RELA?'
llvm-readelf --dynamic $t/exe1 | grep -E 'ANDROID_RELA?\b'
llvm-readelf --dynamic $t/exe1 | grep -E 'ANDROID_RELA?SZ'
# llvm-readelf decodes APS2 back to individual relocations. Check the
# section was decoded with a non-zero count (we don't grep for a specific
# type name because llvm-readelf prints "Unknown" for relocation types
# of arches it doesn't recognize, e.g. SH4/M68K).
llvm-readelf -r $t/exe1 | grep -E "'\.rela?\.dyn'.*contains [1-9]"
# --pack-dyn-relocs=android+relr: R_RELATIVE -> .relr.dyn, others -> APS2.
$CC -B. -o $t/exe2 $t/a.o -pie -Wl,--pack-dyn-relocs=android+relr
llvm-readelf -SW $t/exe2 | grep -Ew '\.rela?\.dyn' | grep -E 'ANDROID_RELA?'
llvm-readelf -SW $t/exe2 | grep -F .relr.dyn
llvm-readelf --dynamic $t/exe2 | grep -E 'ANDROID_RELA?\b'
llvm-readelf --dynamic $t/exe2 | grep -Ew 'RELR'
-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = ppc64 ] && skip
# Test if grep supports backreferences
echo abab | grep -E '(ab)\1' || skip
cat <<EOF | $CC -o $t/a.o -c -xc -
#include <stdio.h>
__attribute__((section("foo"))) int bar;
int main() {
printf("Hello world\n");
return 0;
}
EOF
$CC -B. -no-pie -o $t/exe1 $t/a.o -Wl,--image-base=0x200000 \
-Wl,--physical-image-base=0x800000
$QEMU $t/exe1 | grep 'Hello world'
readelf -W --segments $t/exe1 | grep -E 'LOAD\s+0x000000 0x0*200000 0x0*800000'
readelf -Ws $t/exe1 | grep __phys_start_foo
$CC -B. -no-pie -o $t/exe2 $t/a.o -Wl,--physical-image-base=0x800000 \
-Wl,--section-order='=0x800000 TEXT RODATA =0x900000 DATA BSS'
readelf -W --segments $t/exe2 | grep -E 'LOAD\s+\S+\s+(\S+)\s\1.*R E 0'
readelf -W --segments $t/exe2 | grep -E 'LOAD\s+\S+\s+(\S+)\s\1.*R 0'
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIC -
__attribute__((section(".sum"))) __attribute__((aligned(8192))) void sum() {};
int main() {}
EOF
$CC -B. -o $t/exe1 $t/a.o
lines=$(readelf -Wl $t/exe1 | grep "LOAD")
while IFS= read -ra line; do
read -ra row <<< "$line"
offset=${row[1]}
vaddr=${row[2]}
align=${row[-1]}
if (( offset % align != vaddr % align )); then
false
fi
done <<< "$lines"
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = sh4 ] && skip
# Regression test for https://github.com/rui314/mold/issues/1538.
#
# On targets that use range extension thunks (ARM and PPC), mold's first
# thunk-creation pass pessimistically routes every PLT-resolved call
# through a thunk. If a single batch of input sections references a huge
# number of distinct imported functions, that first-pass thunk group can
# grow past max_thunk_size and trip an assertion in
# create_range_extension_thunks(). 50,000 imports exceed the historical
# 1 MiB cap on AArch64 (32 bytes per thunk entry), so this exercises the
# large-thunk-group path and checks that every call reaches its target.
n=50000
seq 1 $n | sed 's/.*/int func&(void) { return &; }/' > $t/a.c
$CC -B. -o $t/b.so -shared $t/a.c
echo '#include <stdio.h>' > $t/b.c
seq 1 $n | sed 's/.*/int func&(void);/' >> $t/b.c
echo 'int main() {' >> $t/b.c
echo ' int sum = 0;' >> $t/b.c
seq 1 $n | sed 's/.*/ sum += func&();/' >> $t/b.c
echo ' printf("%d\n", sum);' >> $t/b.c
echo '}' >> $t/b.c
$CC -c -o $t/c.o $t/b.c
$CC -B. -o $t/exe $t/c.o $t/b.so
sum=$((n * (n + 1) / 2))
$QEMU $t/exe | grep -E "^$sum\$"
-43
View File
@@ -1,43 +0,0 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIC $flags -
#include <stdio.h>
__attribute__((section(".fn1"))) void fn1() { printf(" fn1"); }
__attribute__((section(".fn2"))) void fn2() { printf(" fn2"); }
int main() {
printf("Hello world\n");
}
EOF
$CC -B. -o $t/exe1 $t/a.o -no-pie \
-Wl,--section-order='=0x100000 EHDR PHDR =0x200000 .fn2 TEXT =0x300000 .fn1 DATA BSS RODATA'
$QEMU $t/exe1 | grep Hello
readelf -SW $t/exe1 | grep '\.fn2 .*00200000'
readelf -SW $t/exe1 | grep '\.fn1 .*00300000'
$CC -B. -o $t/exe2 $t/a.o -no-pie \
-Wl,--section-order='=0x200000 EHDR RODATA =0x300000 PHDR =0x400000 .fn2 TEXT DATA BSS'
readelf -SW $t/exe2 | grep '\.fn2 .*00400000'
readelf -sW $t/exe2 | grep -E ': 0+200000\s.*\s__ehdr_start$'
readelf -W --segments $t/exe2 | grep -E 'PHDR\s.*0x0+300000\s'
$CC -B. -o $t/exe3 $t/a.o -no-pie \
-Wl,--section-order='=0x200000 !ehdr_start EHDR %0x20 !rodata_start RODATA =0x300000 !phdr_start PHDR %4096 !phdr_end =0x400000 !text_start TEXT DATA BSS'
readelf -sW $t/exe3 > $t/log3
grep -E '\b0+200000 .* ehdr_start$' $t/log3
grep -E '\b0+200040 .* rodata_start$' $t/log3
grep -E '\b0+300000 .* phdr_start$' $t/log3
grep -E '\b0+301000 .* phdr_end$' $t/log3
grep -E '\b0+400000 .* text_start$' $t/log3
not $CC -B. -o $t/exe4 $t/a.o -no-pie \
-Wl,--section-order='=0x800000 EHDR PHDR =0x200000 .fn2 TEXT =0x300000 .fn1 DATA BSS RODATA' |&
grep 'address goes backward'
+1 -1
View File
@@ -36,7 +36,7 @@ gdb $t/exe1 -ex 'list main' -ex 'quit' | grep -F return1
# See issue #1535 for more details
readelf -W --sections $t/exe1.dbg | not grep -E '[EPS]HDR'
readelf -W --symbols $t/exe1.dbg | not grep -E '<corrupt>'
readelf -W --symbols $t/exe1.dbg | not grep -E '^ *[0-9][0-9]:.*UND *$'
readelf -W --symbols $t/exe1.dbg | grep -vw REGISTER | not grep -E '^ *[0-9][0-9]:.*UND *$'
$CC -c -o $t/a.o $t/a.c -g -I$t
Executable
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
. $(dirname $0)/common.inc
# .sframe is a compact stack-unwinding format. The linker parses the
# .sframe sections in the input files, drops the entries for dead
# functions, concatenates the rest, sorts the resulting index by PC and
# rewrites the header. mold handles SFrame Version 3.
# Skip if the assembler cannot emit SFrame Version 3.
cat <<EOF | $CC -Wa,--gsframe -o $t/probe.o -c -xc - 2>/dev/null || skip
int main() { return 0; }
EOF
readelf --sframe=.sframe $t/probe.o 2>/dev/null | grep -q SFRAME_VERSION_3 || skip
# Compile two translation units, each with several functions, so that the
# linker has to merge and sort more than one .sframe section.
cat <<EOF | $CC -O1 -Wa,--gsframe -o $t/a.o -c -xc -
int foo(int x) { return x + 1; }
int bar(int x) { return foo(x) * 2; }
EOF
cat <<EOF | $CC -O1 -Wa,--gsframe -o $t/b.o -c -xc -
int foo(int);
int bar(int);
int baz(int x) { return foo(x) - bar(x); }
int main(void) { return baz(7) + 8; }
EOF
$CC -B. -Wa,--gsframe -o $t/exe $t/a.o $t/b.o
$QEMU $t/exe
# The output is a single Version 3 section whose index is sorted by PC.
readelf --sframe=.sframe $t/exe > $t/log
grep -q SFRAME_VERSION_3 $t/log
grep -q SFRAME_F_FDE_SORTED $t/log
# A PT_GNU_SFRAME segment must point at the section.
readelf -Wl $t/exe | grep -q GNU_SFRAME
# Verify that the FDEs really are sorted by function address.
prev=0
for pc in $(grep -oE 'pc = 0x[0-9a-f]+' $t/log | grep -oE '0x[0-9a-f]+'); do
[ $((pc)) -ge $prev ]
prev=$((pc))
done
# --gc-sections must drop the FDEs of collected functions.
cat <<EOF | $CC -O0 -ffunction-sections -Wa,--gsframe -o $t/c.o -c -xc -
int used(int x) { return x; }
int unused(int x) { return x + 1; }
int main(void) { return used(0); }
EOF
$CC -B. -Wa,--gsframe -ffunction-sections -o $t/exe2 $t/c.o
$CC -B. -Wa,--gsframe -ffunction-sections -Wl,--gc-sections -o $t/exe3 $t/c.o
$QEMU $t/exe3
n1=$(readelf --sframe=.sframe $t/exe2 | grep -c 'func idx')
n2=$(readelf --sframe=.sframe $t/exe3 | grep -c 'func idx')
[ "$n2" -lt "$n1" ]
# A relocatable link (-r) must merge the inputs into a single .sframe and
# keep each func_start as a relocation for the final link to resolve.
./mold --relocatable -o $t/r.o $t/a.o $t/b.o
readelf --sframe=.sframe $t/r.o > $t/rlog
grep -q SFRAME_VERSION_3 $t/rlog
[ "$(readelf -SW $t/r.o | grep -cw GNU_SFRAME)" = 1 ]
readelf -SW $t/r.o | grep -q '\.rela\.sframe'
# The relocatable object must link into a working executable that still
# describes every function from the original inputs.
$CC -B. -Wa,--gsframe -o $t/exe4 $t/r.o
$QEMU $t/exe4
[ "$(grep -c 'func idx' $t/rlog)" -le "$(readelf --sframe=.sframe $t/exe4 | grep -c 'func idx')" ]
+2 -2
View File
@@ -16,13 +16,13 @@ int main() {
}
EOF
$CC -o $t/exe1 $t/a.o -pie
$CC -o $t/exe1 $t/a.o -pie -Wl,-z,notext
$QEMU $t/exe1 || skip
$CC -B. -o $t/exe2 $t/a.o -pie
$QEMU $t/exe2 | grep 'Hello world'
$CC -o $t/exe3 $t/a.o -pie -Wl,-z,pack-relative-relocs 2> /dev/null || skip
$CC -o $t/exe3 $t/a.o -pie -Wl,-z,notext -Wl,-z,pack-relative-relocs 2> /dev/null || skip
readelf -WS $t/exe3 | grep -F .relr.dyn || skip
$QEMU $t/exe3 2> /dev/null | grep 'Hello world' || skip
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -fPIC -c -xc -
#include <stdio.h>
int main() {
printf("Hello world\n");
}
EOF
# Probe whether the host toolchain can link a PIE; skip if not.
$CC -o $t/probe $t/a.o -pie 2> /dev/null || skip
# Default: standard DT_RELR tags (35/36/37).
$CC -B. -o $t/exe1 $t/a.o -pie -Wl,--pack-dyn-relocs=relr
readelf -SW $t/exe1 | grep -F .relr.dyn
readelf --dynamic $t/exe1 > $t/log1
grep -Ew 'RELR|<unknown>: 24' $t/log1
grep -Ew 'RELRSZ|<unknown>: 23' $t/log1
grep -Ew 'RELRENT|<unknown>: 25' $t/log1
# --use-android-relr-tags: legacy DT_ANDROID_RELR* tags (in the
# OS-specific range), for compatibility with pre-Android-12 loaders
# that don't recognize the standard tags. Match by raw hex value because
# older readelf doesn't know the symbolic names.
$CC -B. -o $t/exe2 $t/a.o -pie \
-Wl,--pack-dyn-relocs=relr -Wl,--use-android-relr-tags
readelf -SW $t/exe2 | grep -F .relr.dyn
readelf --dynamic $t/exe2 > $t/log2
grep -E '0x0*6fffe000\b' $t/log2
grep -E '0x0*6fffe001\b' $t/log2
grep -E '0x0*6fffe003\b' $t/log2
# Make sure the standard tags are NOT emitted in this mode.
not grep -Ew 'RELR|<unknown>: 24' $t/log2
not grep -Ew 'RELRSZ|<unknown>: 23' $t/log2
not grep -Ew 'RELRENT|<unknown>: 25' $t/log2
# --no-use-android-relr-tags reverts to the standard tags.
$CC -B. -o $t/exe3 $t/a.o -pie \
-Wl,--pack-dyn-relocs=relr \
-Wl,--use-android-relr-tags -Wl,--no-use-android-relr-tags
readelf --dynamic $t/exe3 > $t/log3
grep -Ew 'RELR|<unknown>: 24' $t/log3
+13 -4
View File
@@ -7,6 +7,7 @@ local:
*;
global:
[abc][^abc][^\]a-zABC];
[abc][!abc][!\]a-zABC]_;
};
EOF
@@ -15,12 +16,20 @@ void azZ() {}
void czZ() {}
void azC() {}
void aaZ() {}
void azZ_() {}
void czZ_() {}
void azC_() {}
void aaZ_() {}
EOF
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
readelf --dyn-syms $t/c.so > $t/log
grep ' azZ' $t/log
grep ' czZ' $t/log
not grep ' azC' $t/log
not grep ' aaZ' $t/log
grep ' azZ$' $t/log
grep ' czZ$' $t/log
not grep ' azC$' $t/log
not grep ' aaZ$' $t/log
grep ' azZ_$' $t/log
grep ' czZ_$' $t/log
not grep ' azC_$' $t/log
not grep ' aaZ_$' $t/log