[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()) if (errorCount())
return; 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, // We want to declare linker script's symbols early,
// so that we can version them. // so that we can version them.
// They also might be exported if referenced by DSOs. // They also might be exported if referenced by DSOs.

View File

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

View File

@ -523,13 +523,6 @@ std::vector<BaseCommand *> ScriptParser::readOverlay() {
} }
void ScriptParser::readSections() { 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("{"); expect("{");
std::vector<BaseCommand *> v; std::vector<BaseCommand *> v;
while (!errorCount() && !consume("}")) { while (!errorCount() && !consume("}")) {
@ -548,22 +541,29 @@ void ScriptParser::readSections() {
else else
v.push_back(readOutputSectionDescription(tok)); v.push_back(readOutputSectionDescription(tok));
} }
script->sectionCommands.insert(script->sectionCommands.end(), v.begin(),
v.end());
if (!atEOF() && consume("INSERT")) { if (atEOF() || !consume("INSERT")) {
std::vector<BaseCommand *> *dest = nullptr; // --no-rosegment is used to avoid placing read only non-executable sections
if (consume("AFTER")) // in their own segment. We do the same if SECTIONS command is present in
dest = &script->insertAfterCommands[next()]; // linker script. See comment for computeFlags().
else if (consume("BEFORE")) // TODO This rule will be dropped in the future.
dest = &script->insertBeforeCommands[next()]; config->singleRoRx = true;
else
setError("expected AFTER/BEFORE, but got '" + next() + "'"); script->hasSectionsCommand = true;
if (dest)
dest->insert(dest->end(), v.begin(), v.end());
return; return;
} }
script->sectionCommands.insert(script->sectionCommands.end(), v.begin(), bool isAfter = false;
v.end()); 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() { 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(script->sectionCommands, isSection),
llvm::find_if(llvm::reverse(script->sectionCommands), isSection).base(), llvm::find_if(llvm::reverse(script->sectionCommands), isSection).base(),
compareSections); compareSections);
// Process INSERT commands. From this point onwards the order of
// script->sectionCommands is fixed.
script->processInsertCommands();
return; return;
} }
script->processInsertCommands();
// Orphan sections are sections present in the input files which are // Orphan sections are sections present in the input files which are
// not explicitly placed into the output file by the linker script. // 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 ## we check that can use INSERT AFTER to insert sections .foo.data
## and .foo.text at the right places. ## 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: ld.lld %t1.o -o %t1 --script %p/Inputs/insert-after.script --script %s
# RUN: llvm-objdump -section-headers %t1 | FileCheck %s # RUN: llvm-readelf -S -l %t1 | FileCheck %s
# CHECK: Sections: # CHECK: Name Type Address Off
# CHECK-NEXT: Idx Name Size VMA Type # CHECK-NEXT: NULL 0000000000000000 000000
# CHECK-NEXT: 0 00000000 0000000000000000 # CHECK-NEXT: .text PROGBITS 0000000000000000 001000
# CHECK-NEXT: 1 .text 00000008 0000000000000000 TEXT # CHECK-NEXT: .foo.text PROGBITS 0000000000000008 001008
# CHECK-NEXT: 2 .foo.text 00000008 0000000000000008 TEXT # CHECK-NEXT: .data PROGBITS 0000000000000010 001010
# CHECK-NEXT: 3 .data 00000008 0000000000000010 DATA # CHECK-NEXT: .foo.data PROGBITS 0000000000000018 001018
# CHECK-NEXT: 4 .foo.data 00000008 0000000000000018 DATA # 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 \ ## There is no main linker script. INSERT AFTER just reorders output sections,
# RUN: | FileCheck %s --check-prefix=ERR ## without making more layout changes. Address/offset assignments are different
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .text: section not defined ## with a main linker script.
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .data: section not defined
# 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 ## we check that can use INSERT BEFORE to insert sections .foo.data
## and .foo.text at the right places. ## 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: ld.lld %t1.o -o %t1 --script %p/Inputs/insert-after.script --script %s
# RUN: llvm-objdump -section-headers %t1 | FileCheck %s # RUN: llvm-readelf -S -l %t1 | FileCheck %s
# CHECK: Sections: # CHECK: Name Type Address Off
# CHECK-NEXT: Idx Name Size VMA Type # CHECK-NEXT: NULL 0000000000000000 000000
# CHECK-NEXT: 0 00000000 0000000000000000 # CHECK-NEXT: .foo.text PROGBITS 0000000000000000 001000
# CHECK-NEXT: 1 .foo.text 00000008 0000000000000000 TEXT # CHECK-NEXT: .text PROGBITS 0000000000000008 001008
# CHECK-NEXT: 2 .text 00000008 0000000000000008 TEXT # CHECK-NEXT: .foo.data PROGBITS 0000000000000010 001010
# CHECK-NEXT: 3 .foo.data 00000008 0000000000000010 DATA # CHECK-NEXT: .data PROGBITS 0000000000000018 001018
# CHECK-NEXT: 4 .data 00000008 0000000000000018 DATA # 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 \ ## There is no main linker script. INSERT BEFORE just reorders output sections,
# RUN: | FileCheck %s --check-prefix=ERR ## without making more layout changes. Address/offset assignments are different
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .text: section not defined ## with a main linker script.
# ERR-DAG: error: unable to INSERT AFTER/BEFORE .data: section not defined
# 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;