[ELF] Support INSERT [AFTER|BEFORE] for orphan sections

D43468+D44380 added INSERT [AFTER|BEFORE] for non-orphan sections. This patch
makes INSERT work for orphan sections as well.

`SECTIONS {...} INSERT [AFTER|BEFORE] .foo` does not set `hasSectionCommands`, so the result
will be similar to a regular link without a linker script. The differences when `hasSectionCommands` is set include:

* image base is different
* -z noseparate-code/-z noseparate-loadable-segments are unavailable
* some special symbols such as `_end _etext _edata` are not defined

The behavior is similar to GNU ld:
INSERT is not considered an external linker script.

This feature makes the section layout more flexible. It can be used to:

* Place .nv_fatbin before other readonly SHT_PROGBITS sections to mitigate relocation overflows.
* Disturb the layout to expose address sensitive application bugs.

Reviewed By: grimar

Differential Revision: https://reviews.llvm.org/D74375
This commit is contained in:
Fangrui Song 2020-02-10 15:58:29 -08:00
parent b498d99338
commit 7c426fb1a6
9 changed files with 162 additions and 89 deletions

View File

@ -1865,10 +1865,6 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
if (errorCount())
return;
// Now when we read all script files, we want to finalize order of linker
// script commands, which can be not yet final because of INSERT commands.
script->processInsertCommands();
// We want to declare linker script's symbols early,
// so that we can version them.
// They also might be exported if referenced by DSOs.

View File

@ -246,32 +246,30 @@ getChangedSymbolAssignment(const SymbolAssignmentMap &oldValues) {
return changed;
}
// This method is used to handle INSERT AFTER statement. Here we rebuild
// the list of script commands to mix sections inserted into.
// Process INSERT [AFTER|BEFORE] commands. For each command, we move the
// specified output section to the designated place.
void LinkerScript::processInsertCommands() {
std::vector<BaseCommand *> v;
auto insert = [&](std::vector<BaseCommand *> &from) {
v.insert(v.end(), from.begin(), from.end());
from.clear();
};
for (BaseCommand *base : sectionCommands) {
if (auto *os = dyn_cast<OutputSection>(base)) {
insert(insertBeforeCommands[os->name]);
v.push_back(base);
insert(insertAfterCommands[os->name]);
for (const InsertCommand &cmd : insertCommands) {
// If cmd.os is empty, it may have been discarded by
// adjustSectionsBeforeSorting(). We do not handle such output sections.
auto from = llvm::find(sectionCommands, cmd.os);
if (from == sectionCommands.end())
continue;
sectionCommands.erase(from);
auto insertPos = llvm::find_if(sectionCommands, [&cmd](BaseCommand *base) {
auto *to = dyn_cast<OutputSection>(base);
return to != nullptr && to->name == cmd.where;
});
if (insertPos == sectionCommands.end()) {
error("unable to insert " + cmd.os->name +
(cmd.isAfter ? " after " : " before ") + cmd.where);
} else {
if (cmd.isAfter)
++insertPos;
sectionCommands.insert(insertPos, cmd.os);
}
v.push_back(base);
}
for (auto &cmds : {insertBeforeCommands, insertAfterCommands})
for (const std::pair<StringRef, std::vector<BaseCommand *>> &p : cmds)
if (!p.second.empty())
error("unable to INSERT AFTER/BEFORE " + p.first +
": section not defined");
sectionCommands = std::move(v);
}
// Symbols defined in script should not be inlined by LTO. At the same time

View File

@ -208,6 +208,12 @@ struct ByteCommand : BaseCommand {
unsigned size;
};
struct InsertCommand {
OutputSection *os;
bool isAfter;
StringRef where;
};
struct PhdrsCommand {
StringRef name;
unsigned type = llvm::ELF::PT_NULL;
@ -311,10 +317,9 @@ public:
// A list of symbols referenced by the script.
std::vector<llvm::StringRef> referencedSymbols;
// Used to implement INSERT [AFTER|BEFORE]. Contains commands that need
// to be inserted into SECTIONS commands list.
llvm::DenseMap<StringRef, std::vector<BaseCommand *>> insertAfterCommands;
llvm::DenseMap<StringRef, std::vector<BaseCommand *>> insertBeforeCommands;
// Used to implement INSERT [AFTER|BEFORE]. Contains output sections that need
// to be reordered.
std::vector<InsertCommand> insertCommands;
};
extern LinkerScript *script;

View File

@ -523,13 +523,6 @@ std::vector<BaseCommand *> ScriptParser::readOverlay() {
}
void ScriptParser::readSections() {
script->hasSectionsCommand = true;
// -no-rosegment is used to avoid placing read only non-executable sections in
// their own segment. We do the same if SECTIONS command is present in linker
// script. See comment for computeFlags().
config->singleRoRx = true;
expect("{");
std::vector<BaseCommand *> v;
while (!errorCount() && !consume("}")) {
@ -548,22 +541,29 @@ void ScriptParser::readSections() {
else
v.push_back(readOutputSectionDescription(tok));
}
script->sectionCommands.insert(script->sectionCommands.end(), v.begin(),
v.end());
if (!atEOF() && consume("INSERT")) {
std::vector<BaseCommand *> *dest = nullptr;
if (consume("AFTER"))
dest = &script->insertAfterCommands[next()];
else if (consume("BEFORE"))
dest = &script->insertBeforeCommands[next()];
else
setError("expected AFTER/BEFORE, but got '" + next() + "'");
if (dest)
dest->insert(dest->end(), v.begin(), v.end());
if (atEOF() || !consume("INSERT")) {
// --no-rosegment is used to avoid placing read only non-executable sections
// in their own segment. We do the same if SECTIONS command is present in
// linker script. See comment for computeFlags().
// TODO This rule will be dropped in the future.
config->singleRoRx = true;
script->hasSectionsCommand = true;
return;
}
script->sectionCommands.insert(script->sectionCommands.end(), v.begin(),
v.end());
bool isAfter = false;
if (consume("AFTER"))
isAfter = true;
else if (!consume("BEFORE"))
setError("expected AFTER/BEFORE, but got '" + next() + "'");
StringRef where = next();
for (BaseCommand *cmd : v)
if (auto *os = dyn_cast<OutputSection>(cmd))
script->insertCommands.push_back({os, isAfter, where});
}
void ScriptParser::readTarget() {

View File

@ -1420,9 +1420,15 @@ template <class ELFT> void Writer<ELFT>::sortSections() {
llvm::find_if(script->sectionCommands, isSection),
llvm::find_if(llvm::reverse(script->sectionCommands), isSection).base(),
compareSections);
// Process INSERT commands. From this point onwards the order of
// script->sectionCommands is fixed.
script->processInsertCommands();
return;
}
script->processInsertCommands();
// Orphan sections are sections present in the input files which are
// not explicitly placed into the output file by the linker script.
//

View File

@ -5,25 +5,39 @@
## we check that can use INSERT AFTER to insert sections .foo.data
## and .foo.text at the right places.
SECTIONS {
.foo.data : { *(.foo.data) }
} INSERT AFTER .data;
SECTIONS {
.foo.text : { *(.foo.text) }
} INSERT AFTER .text;
# RUN: ld.lld %t1.o -o %t1 --script %p/Inputs/insert-after.script --script %s
# RUN: llvm-objdump -section-headers %t1 | FileCheck %s
# CHECK: Sections:
# CHECK-NEXT: Idx Name Size VMA Type
# CHECK-NEXT: 0 00000000 0000000000000000
# CHECK-NEXT: 1 .text 00000008 0000000000000000 TEXT
# CHECK-NEXT: 2 .foo.text 00000008 0000000000000008 TEXT
# CHECK-NEXT: 3 .data 00000008 0000000000000010 DATA
# CHECK-NEXT: 4 .foo.data 00000008 0000000000000018 DATA
# RUN: llvm-readelf -S -l %t1 | FileCheck %s
# CHECK: Name Type Address Off
# CHECK-NEXT: NULL 0000000000000000 000000
# CHECK-NEXT: .text PROGBITS 0000000000000000 001000
# CHECK-NEXT: .foo.text PROGBITS 0000000000000008 001008
# CHECK-NEXT: .data PROGBITS 0000000000000010 001010
# CHECK-NEXT: .foo.data PROGBITS 0000000000000018 001018
# CHECK: Type
# CHECK-NEXT: LOAD {{.*}} R E
# CHECK-NEXT: LOAD {{.*}} RW
# CHECK-NEXT: GNU_STACK {{.*}} RW
# RUN: not ld.lld %t1.o -o %t1 --script %s 2>&1 \
# RUN: | FileCheck %s --check-prefix=ERR
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .text: section not defined
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .data: section not defined
## There is no main linker script. INSERT AFTER just reorders output sections,
## without making more layout changes. Address/offset assignments are different
## with a main linker script.
# RUN: ld.lld --script %s %t1.o -o %t2
# RUN: llvm-readelf -S -l %t2 | FileCheck --check-prefix=CHECK2 %s
# CHECK2: Name Type Address Off
# CHECK2-NEXT: NULL 0000000000000000 000000
# CHECK2-NEXT: .text PROGBITS 0000000000201158 000158
# CHECK2-NEXT: .foo.text PROGBITS 0000000000201160 000160
# CHECK2-NEXT: .data PROGBITS 0000000000202168 000168
# CHECK2-NEXT: .foo.data PROGBITS 0000000000202170 000170
# CHECK2: Type
# CHECK2-NEXT: PHDR {{.*}} R
# CHECK2-NEXT: LOAD {{.*}} R
# CHECK2-NEXT: LOAD {{.*}} R E
# CHECK2-NEXT: LOAD {{.*}} RW
# CHECK2-NEXT: GNU_STACK {{.*}} RW
SECTIONS { .foo.data : { *(.foo.data) } } INSERT AFTER .data;
## The input section .foo.text is an orphan. It will be placed in .foo.text
SECTIONS { .foo.text : {} } INSERT AFTER .text;

View File

@ -5,25 +5,38 @@
## we check that can use INSERT BEFORE to insert sections .foo.data
## and .foo.text at the right places.
SECTIONS {
.foo.data : { *(.foo.data) }
} INSERT BEFORE .data;
SECTIONS {
.foo.text : { *(.foo.text) }
} INSERT BEFORE .text;
# RUN: ld.lld %t1.o -o %t1 --script %p/Inputs/insert-after.script --script %s
# RUN: llvm-objdump -section-headers %t1 | FileCheck %s
# CHECK: Sections:
# CHECK-NEXT: Idx Name Size VMA Type
# CHECK-NEXT: 0 00000000 0000000000000000
# CHECK-NEXT: 1 .foo.text 00000008 0000000000000000 TEXT
# CHECK-NEXT: 2 .text 00000008 0000000000000008 TEXT
# CHECK-NEXT: 3 .foo.data 00000008 0000000000000010 DATA
# CHECK-NEXT: 4 .data 00000008 0000000000000018 DATA
# RUN: llvm-readelf -S -l %t1 | FileCheck %s
# CHECK: Name Type Address Off
# CHECK-NEXT: NULL 0000000000000000 000000
# CHECK-NEXT: .foo.text PROGBITS 0000000000000000 001000
# CHECK-NEXT: .text PROGBITS 0000000000000008 001008
# CHECK-NEXT: .foo.data PROGBITS 0000000000000010 001010
# CHECK-NEXT: .data PROGBITS 0000000000000018 001018
# CHECK: Type
# CHECK-NEXT: LOAD {{.*}} R E
# CHECK-NEXT: LOAD {{.*}} RW
# CHECK-NEXT: GNU_STACK {{.*}} RW
# RUN: not ld.lld %t1.o -o %t1 --script %s 2>&1 \
# RUN: | FileCheck %s --check-prefix=ERR
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .text: section not defined
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .data: section not defined
## There is no main linker script. INSERT BEFORE just reorders output sections,
## without making more layout changes. Address/offset assignments are different
## with a main linker script.
# RUN: ld.lld --script %s %t1.o -o %t2
# RUN: llvm-readelf -S -l %t2 | FileCheck --check-prefix=CHECK2 %s
# CHECK2: Name Type Address Off
# CHECK2-NEXT: NULL 0000000000000000 000000
# CHECK2-NEXT: .foo.text PROGBITS 0000000000201158 000158
# CHECK2-NEXT: .text PROGBITS 0000000000201160 000160
# CHECK2-NEXT: .foo.data PROGBITS 0000000000202168 000168
# CHECK2-NEXT: .data PROGBITS 0000000000202170 000170
# CHECK2: Type
# CHECK2-NEXT: PHDR {{.*}} R
# CHECK2-NEXT: LOAD {{.*}} R
# CHECK2-NEXT: LOAD {{.*}} R E
# CHECK2-NEXT: LOAD {{.*}} RW
SECTIONS { .foo.data : { *(.foo.data) } } INSERT BEFORE .data;
## The input section .foo.text is an orphan. It will be placed in .foo.text
SECTIONS { .foo.text : {} } INSERT BEFORE .text;

View File

@ -0,0 +1,32 @@
# REQUIRES: x86
## Test that we can handle cases where an output section is specified by multiple
## INSERT commands. Each output section description creates a new instance.
## A redundant description matches no input sections and thus is a no-op.
# RUN: llvm-mc -filetype=obj -triple=x86_64 %p/Inputs/insert-after.s -o %t.o
# RUN: ld.lld -T %s %t.o -o %t
# RUN: llvm-readelf -S -l %t | FileCheck %s
# CHECK: Name Type Address Off
# CHECK-NEXT: NULL 0000000000000000 000000
# CHECK-NEXT: .text PROGBITS 00000000002011c8 0001c8
# CHECK-NEXT: .foo.data PROGBITS 00000000002021d0 0001d0
# CHECK-NEXT: .foo.text PROGBITS 00000000002031d8 0001d8
# CHECK: Type
# CHECK-NEXT: PHDR {{.*}} R
# CHECK-NEXT: LOAD {{.*}} R
# CHECK-NEXT: LOAD {{.*}} R E
# CHECK-NEXT: LOAD {{.*}} RW
# CHECK-NEXT: LOAD {{.*}} R E
# CHECK-NEXT: LOAD {{.*}} RW
# CHECK-NEXT: GNU_STACK {{.*}} RW
## First, move .foo.data after .foo.text
SECTIONS { .foo.data : { *(.foo.data) } } INSERT AFTER .foo.text;
## Next, move .foo.text after .foo.data
SECTIONS { .foo.text : { *(.foo.text) } } INSERT AFTER .foo.data;
## No-op. The .foo.data output section is a different instance and matches no
## input sections.
SECTIONS { .foo.data : { *(.foo.data) } } INSERT AFTER .foo.text;

View File

@ -0,0 +1,9 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64 %p/Inputs/insert-after.s -o %t.o
# RUN: not ld.lld -T %s %t.o 2>&1 | FileCheck %s
# CHECK: error: unable to insert .foo.data after .not_exist
# CHECK: error: unable to insert .foo.text before .not_exist
SECTIONS { .foo.data : {} } INSERT AFTER .not_exist;
SECTIONS { .foo.text : {} } INSERT BEFORE .not_exist;