Compare commits

...
19 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
43 changed files with 4407 additions and 655 deletions

No files matched your search

+8 -75
View File
@@ -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")) {
@@ -1103,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")) {
@@ -1257,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")) {
@@ -1565,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)
@@ -1577,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;
@@ -1604,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";
@@ -1634,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) {
@@ -1652,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))
@@ -1683,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);
+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 "";
}
+9 -2
View File
@@ -12,13 +12,20 @@
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;
if constexpr (is_ppc32<E>)
if (isec.name == ".got2")
return true;
// KEEP'ed by a linker script
if (!isec.file.script_match.empty()) {
i32 rank = isec.file.script_match[isec.shndx];
if (rank != -1 && ctx.script_ranks[rank].keep)
return true;
}
return (flags & SHF_GNU_RETAIN) ||
type == SHT_NOTE ||
type == SHT_INIT_ARRAY ||
@@ -91,7 +98,7 @@ collect_root_set(Context<E> &ctx) {
continue;
}
if (should_keep(*isec))
if (should_keep(ctx, *isec))
enqueue_section(isec.get());
}
});
+12 -2
View File
@@ -837,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] =
+2593 -178
View File
File diff suppressed because it is too large. Load diff
+21 -2
View File
@@ -33,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;
@@ -72,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;
@@ -109,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:
@@ -147,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;
}
@@ -332,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;
@@ -368,6 +377,13 @@ 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);
@@ -388,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);
+305 -31
View File
@@ -690,8 +690,16 @@ public:
i64 strtab_size = 0;
i64 strtab_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 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.
@@ -783,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:
@@ -804,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;
};
@@ -1221,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);
@@ -1235,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;
@@ -1840,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;
@@ -1950,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();
@@ -1962,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
//
@@ -2123,6 +2390,7 @@ 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> &);
@@ -2341,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 {};
@@ -2397,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");
@@ -2437,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;
@@ -2513,7 +2774,6 @@ struct Context {
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;
@@ -2539,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;
@@ -2557,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;
@@ -3547,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>
+95 -54
View File
@@ -151,6 +151,10 @@ i64 to_phdr_flags(Context<E> &ctx, Chunk<E> *chunk) {
template <typename E>
static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
// A PHDRS linker script command describes program headers explicitly
if (!ctx.script_phdrs.empty())
return create_script_phdrs(ctx);
std::vector<ElfPhdr<E>> vec;
auto define = [&](u64 type, u64 flags, Chunk<E> *chunk) {
@@ -171,7 +175,13 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
}
phdr.p_vaddr = chunk->shdr.sh_addr;
phdr.p_paddr = chunk->shdr.sh_addr;
// A linker script's AT() can give a section a load address that
// differs from its virtual address
if (!ctx.script_sections.empty())
phdr.p_paddr = chunk->lma;
else
phdr.p_paddr = chunk->shdr.sh_addr;
if (chunk->shdr.sh_flags & SHF_ALLOC)
phdr.p_memsz = chunk->shdr.sh_size;
@@ -241,19 +251,28 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
if (!ctx.arg.nmagic && !ctx.arg.omagic)
vec.back().p_align = std::max<u64>(ctx.page_size, vec.back().p_align);
// Sections whose load address differs from the virtual address
// by a different amount cannot share a segment
auto same_lma_delta = [&](Chunk<E> *chunk) {
return ctx.script_sections.empty() ||
chunk->lma - chunk->shdr.sh_addr == first->lma - first->shdr.sh_addr;
};
// Add contiguous ALLOC sections as long as they have the same
// section flags and there's no on-disk gap in between.
if (!is_bss(first))
while (i < chunks.size() &&
!is_bss(chunks[i]) &&
to_phdr_flags(ctx, chunks[i]) == flags &&
same_lma_delta(chunks[i]) &&
chunks[i]->shdr.sh_offset - first->shdr.sh_offset ==
chunks[i]->shdr.sh_addr - first->shdr.sh_addr)
append(chunks[i++]);
while (i < chunks.size() &&
is_bss(chunks[i]) &&
to_phdr_flags(ctx, chunks[i]) == flags)
to_phdr_flags(ctx, chunks[i]) == flags &&
same_lma_delta(chunks[i]))
append(chunks[i++]);
}
@@ -323,52 +342,6 @@ static std::vector<ElfPhdr<E>> create_phdr(Context<E> &ctx) {
if (chunk->name == ".openbsd.randomdata")
define(PT_OPENBSD_RANDOMIZE, PF_R | PF_W, chunk);
// Set p_paddr if --physical-image-base was given. --physical-image-base
// is typically used in embedded programming to specify the base address
// of a memory-mapped ROM area. In that environment, paddr refers to a
// segment's initial location in ROM and vaddr refers the its run-time
// address.
//
// When a device is turned on, it start executing code at a fixed
// location in the ROM area. At that location is a startup routine that
// copies data or code from ROM to RAM before using them.
//
// .data must have different paddr and vaddr because ROM is not writable.
// paddr of .rodata and .text may or may be equal to vaddr. They can be
// directly read or executed from ROM, but oftentimes they are copied
// from ROM to RAM because Flash or EEPROM are usually much slower than
// DRAM.
//
// We want to keep vaddr == pvaddr for as many segments as possible so
// that they can be directly read/executed from ROM. If a gap between
// two segments is two page size or larger, we give up and pack segments
// tightly so that we don't waste too much ROM area.
if (ctx.arg.physical_image_base) {
for (i64 i = 0; i < vec.size(); i++) {
if (vec[i].p_type != PT_LOAD)
continue;
u64 addr = *ctx.arg.physical_image_base;
bool in_sync = (vec[i].p_vaddr == addr);
vec[i].p_paddr = addr;
addr += vec[i].p_memsz;
for (i++; i < vec.size() && vec[i].p_type == PT_LOAD; i++) {
ElfPhdr<E> &p = vec[i];
if (in_sync && addr <= p.p_vaddr && p.p_vaddr < addr + ctx.page_size * 2) {
p.p_paddr = p.p_vaddr;
addr = p.p_vaddr + p.p_memsz;
} else {
in_sync = false;
p.p_paddr = addr;
addr += p.p_memsz;
}
}
break;
}
}
vec.resize(vec.size() + ctx.arg.spare_program_headers);
return vec;
}
@@ -884,6 +857,11 @@ template <typename E>
void OutputSection<E>::compute_section_size(Context<E> &ctx) {
ElfShdr<E> &shdr = this->shdr;
// A section described by a linker script is laid out by the script
// engine in set_virtual_addresses_by_script()
if (this->script_cmd != -1)
return;
// Text sections must to be handled by create_range_extension_thunks()
// if they may need range extension thunks.
assert(!needs_thunk<E> || !(shdr.sh_flags & SHF_EXECINSTR) ||
@@ -1046,7 +1024,12 @@ void OutputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
memcpy(loc + i, filler, N);
};
if (this->shdr.sh_flags & SHF_EXECINSTR) {
// A fill pattern given by a linker script (e.g. `=0xcccccccc`) is
// written big-endian, repeating
if (this->fill) {
for (i64 i = 0; i < size; i++)
loc[i] = *this->fill >> (24 - (i % 4) * 8);
} else if (this->shdr.sh_flags & SHF_EXECINSTR) {
// s390x's old CRT files use NOP slides in .init and .fini.
// https://sourceware.org/bugzilla/show_bug.cgi?id=31042
if (is_s390x<E> && (this->name == ".init" || this->name == ".fini"))
@@ -1058,6 +1041,39 @@ void OutputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
}
});
// Fill the leading padding and the gaps around data commands. As
// with the gaps between members, a fill pattern starts anew at each
// gap.
auto fill_gap = [&](u64 start, u64 end) {
if (this->fill)
for (u64 i = start; i < end; i++)
buf[i] = *this->fill >> (24 - ((i - start) % 4) * 8);
else
memset(buf + start, 0, end - start);
};
if (members.empty()) {
u64 pos = 0;
for (ScriptData &d : script_data) {
fill_gap(pos, d.offset);
pos = d.offset + d.size;
}
fill_gap(pos, this->shdr.sh_size);
} else if (members[0]->offset > 0) {
fill_gap(0, members[0]->offset);
}
// Write data inserted by linker script BYTE/SHORT/LONG/QUAD commands
for (ScriptData &d : script_data) {
u8 *loc = buf + d.offset;
switch (d.size) {
case 1: *loc = d.value; break;
case 2: *(U16<E> *)loc = d.value; break;
case 4: *(U32<E> *)loc = d.value; break;
default: *(U64<E> *)loc = d.value; break;
}
}
// Emit range extension thunks.
if constexpr (needs_thunk<E>) {
tbb::parallel_for_each(thunks, [&](std::unique_ptr<Thunk<E>> &thunk) {
@@ -2180,7 +2196,8 @@ MergedSection<E>::MergedSection(std::string_view name, i64 flags, i64 type,
template <typename E>
MergedSection<E> *
MergedSection<E>::get_instance(Context<E> &ctx, std::string_view name,
const ElfShdr<E> &shdr) {
const ElfShdr<E> &shdr, i64 script_cmd,
i32 script_rank) {
if (!(shdr.sh_flags & SHF_MERGE))
return nullptr;
@@ -2199,27 +2216,38 @@ MergedSection<E>::get_instance(Context<E> &ctx, std::string_view name,
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
if (name == osec->name && flags == osec->shdr.sh_flags &&
shdr.sh_type == osec->shdr.sh_type &&
entsize == osec->shdr.sh_entsize)
entsize == osec->shdr.sh_entsize &&
script_cmd == osec->script_cmd)
return osec.get();
return nullptr;
};
// The section is placed at the position of the earliest input
// section description that matched any of its inputs
auto update_rank = [&](MergedSection *osec) {
i32 old = osec->script_rank.load();
while (script_rank < old &&
!osec->script_rank.compare_exchange_weak(old, script_rank));
return osec;
};
// Search for an exiting output section.
static std::shared_mutex mu;
{
std::shared_lock lock(mu);
if (MergedSection *osec = find())
return osec;
return update_rank(osec);
}
// Create a new output section.
std::unique_lock lock(mu);
if (MergedSection *osec = find())
return osec;
return update_rank(osec);
MergedSection *osec = new MergedSection(name, flags, shdr.sh_type, entsize);
osec->script_cmd = script_cmd;
ctx.merged_sections.emplace_back(osec);
return osec;
return update_rank(osec);
}
template <typename E>
@@ -2331,6 +2359,19 @@ void MergedSection<E>::compute_section_size(Context<E> &ctx) {
this->shdr.sh_size = shard_offsets.back();
// The padding to the section alignment must not make the size of a
// mergeable section with fixed-size elements indivisible by the
// element size. Some tools reject such sections; for example,
// libelf-based programs such as objtool fail to process them.
if (i64 entsize = this->shdr.sh_entsize;
entsize > 0 && this->shdr.sh_size % entsize) {
i64 end = 0;
for (i64 i = 0; i < sizes.size(); i++)
if (sizes[i])
end = shard_offsets[i] + sizes[i];
this->shdr.sh_size = end;
}
tbb::parallel_for((i64)1, map.NUM_SHARDS, [&](i64 i) {
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
SectionFragment<E> &frag = map.entries[j].value;
+374 -234
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>);
@@ -167,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>);
@@ -519,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 {
@@ -577,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.
//
@@ -678,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()});
@@ -724,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
@@ -731,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
@@ -783,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) {
@@ -847,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 =
@@ -900,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);
}
}
}
@@ -1673,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
@@ -1690,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.
@@ -1973,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);
@@ -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.
//
@@ -2969,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++;
@@ -2998,10 +3155,10 @@ 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;
@@ -3019,6 +3176,11 @@ i64 set_osec_offsets(Context<E> &ctx) {
// 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);
@@ -3039,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) {
@@ -3214,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;
}
}
}
@@ -3245,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>
@@ -3666,6 +3805,7 @@ 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> &);
+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'
-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'
-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'