mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
19
Commits
v3.0.0
...
linker-script
No files matched your search
+8
-75
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+21
-2
@@ -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
@@ -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
@@ -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
@@ -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> &);
|
||||
|
||||
Executable
+12
@@ -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'
|
||||
Executable
+31
@@ -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'
|
||||
Executable
+23
@@ -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
|
||||
Executable
+30
@@ -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
|
||||
Executable
+25
@@ -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
|
||||
Executable
+33
@@ -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
|
||||
Executable
+17
@@ -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
|
||||
Executable
+43
@@ -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'
|
||||
Executable
+38
@@ -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
|
||||
Executable
+39
@@ -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"
|
||||
Executable
+30
@@ -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
|
||||
Executable
+19
@@ -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"
|
||||
Executable
+22
@@ -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
|
||||
Executable
+28
@@ -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
|
||||
Executable
+27
@@ -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
|
||||
Executable
+19
@@ -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:'
|
||||
Executable
+28
@@ -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
|
||||
Executable
+23
@@ -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
|
||||
Executable
+84
@@ -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
|
||||
Executable
+32
@@ -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
|
||||
Executable
+23
@@ -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
|
||||
Executable
+21
@@ -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'
|
||||
Executable
+33
@@ -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'
|
||||
Executable
+40
@@ -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
|
||||
Executable
+37
@@ -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
|
||||
Executable
+25
@@ -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
|
||||
Executable
+30
@@ -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'
|
||||
Executable
+26
@@ -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
|
||||
Executable
+34
@@ -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
|
||||
Executable
+25
@@ -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
|
||||
Executable
+53
@@ -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
|
||||
Executable
+39
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
Reference in new issue
Block a user