Skip to content

[ELF] Support NOCROSSREFS and NOCROSSERFS_TO #98773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lld/ELF/LinkerScript.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,16 @@ struct InsertCommand {
StringRef where;
};

// A NOCROSSREFS/NOCROSSREFS_TO command that prohibits references between
// certain output sections.
struct NoCrossRefCommand {
SmallVector<StringRef, 0> outputSections;

// When true, this describes a NOCROSSREFS_TO command that probits references
// to the first output section from any of the other sections.
bool toFirst = false;
};

struct PhdrsCommand {
StringRef name;
unsigned type = llvm::ELF::PT_NULL;
Expand Down Expand Up @@ -397,6 +407,9 @@ class LinkerScript final {
// OutputSections specified by OVERWRITE_SECTIONS.
SmallVector<OutputDesc *, 0> overwriteSections;

// NOCROSSREFS(_TO) commands.
SmallVector<NoCrossRefCommand, 0> noCrossRefs;

// Sections that will be warned/errored by --orphan-handling.
SmallVector<const InputSectionBase *, 0> orphanSections;

Expand Down
58 changes: 58 additions & 0 deletions lld/ELF/Relocations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2367,7 +2367,65 @@ void elf::hexagonTLSSymbolUpdate(ArrayRef<OutputSection *> outputSections) {
});
}

static bool matchesRefTo(const NoCrossRefCommand &cmd, StringRef osec) {
if (cmd.toFirst)
return cmd.outputSections[0] == osec;
return llvm::is_contained(cmd.outputSections, osec);
}

template <class ELFT, class Rels>
static void scanCrossRefs(const NoCrossRefCommand &cmd, OutputSection *osec,
InputSection *sec, Rels rels) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a NoCrossRefCommand cmd with toFirst == true can we early exit if osec == cmd.outputSections[0]?

This would be the case where we search the relocations from tosection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an early return to elf::checkNoCrossRefs: (noxref.toFirst && noxref.outputSections[0] == osec->name)

for (const auto &r : rels) {
Symbol &sym = sec->file->getSymbol(r.getSymbol(config->isMips64EL));
// A legal cross-reference is when the destination output section is
// nullptr, osec for a self-reference, or a section that is described by the
// NOCROSSREFS/NOCROSSREFS_TO command.
auto *dstOsec = sym.getOutputSection();
if (!dstOsec || dstOsec == osec || !matchesRefTo(cmd, dstOsec->name))
continue;

std::string toSymName;
if (!sym.isSection())
toSymName = toString(sym);
else if (auto *d = dyn_cast<Defined>(&sym))
toSymName = d->section->name;
errorOrWarn(sec->getLocation(r.r_offset) +
": prohibited cross reference from '" + osec->name + "' to '" +
toSymName + "' in '" + dstOsec->name + "'");
}
}

// For each output section described by at least one NOCROSSREFS(_TO) command,
// scan relocations from its input sections for prohibited cross references.
template <class ELFT> void elf::checkNoCrossRefs() {
for (OutputSection *osec : outputSections) {
for (const NoCrossRefCommand &noxref : script->noCrossRefs) {
if (!llvm::is_contained(noxref.outputSections, osec->name) ||
(noxref.toFirst && noxref.outputSections[0] == osec->name))
continue;
for (SectionCommand *cmd : osec->commands) {
auto *isd = dyn_cast<InputSectionDescription>(cmd);
if (!isd)
continue;
parallelForEach(isd->sections, [&](InputSection *sec) {
const RelsOrRelas<ELFT> rels = sec->template relsOrRelas<ELFT>();
if (rels.areRelocsRel())
scanCrossRefs<ELFT>(noxref, osec, sec, rels.rels);
else
scanCrossRefs<ELFT>(noxref, osec, sec, rels.relas);
});
}
}
}
}

template void elf::scanRelocations<ELF32LE>();
template void elf::scanRelocations<ELF32BE>();
template void elf::scanRelocations<ELF64LE>();
template void elf::scanRelocations<ELF64BE>();

template void elf::checkNoCrossRefs<ELF32LE>();
template void elf::checkNoCrossRefs<ELF32BE>();
template void elf::checkNoCrossRefs<ELF64LE>();
template void elf::checkNoCrossRefs<ELF64BE>();
1 change: 1 addition & 0 deletions lld/ELF/Relocations.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ struct JumpInstrMod {
// Call reportUndefinedSymbols() after calling scanRelocations() to emit
// the diagnostics.
template <class ELFT> void scanRelocations();
template <class ELFT> void checkNoCrossRefs();
void reportUndefinedSymbols();
void postScanRelocations();
void addGotEntry(Symbol &sym);
Expand Down
16 changes: 16 additions & 0 deletions lld/ELF/ScriptParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class ScriptParser final : ScriptLexer {
void readTarget();
void readVersion();
void readVersionScriptCommand();
void readNoCrossRefs(bool to);

SymbolAssignment *readSymbolAssignment(StringRef name);
ByteCommand *readByteCommand(StringRef tok);
Expand Down Expand Up @@ -280,6 +281,10 @@ void ScriptParser::readLinkerScript() {
readTarget();
} else if (tok == "VERSION") {
readVersion();
} else if (tok == "NOCROSSREFS") {
readNoCrossRefs(/*to=*/false);
} else if (tok == "NOCROSSREFS_TO") {
readNoCrossRefs(/*to=*/true);
} else if (SymbolAssignment *cmd = readAssignment(tok)) {
script->sectionCommands.push_back(cmd);
} else {
Expand All @@ -299,6 +304,17 @@ void ScriptParser::readDefsym(StringRef name) {
script->sectionCommands.push_back(cmd);
}

void ScriptParser::readNoCrossRefs(bool to) {
expect("(");
NoCrossRefCommand cmd{{}, to};
while (!errorCount() && !consume(")"))
cmd.outputSections.push_back(unquote(next()));
if (cmd.outputSections.size() < 2)
warn(getCurrentLocation() + ": ignored with fewer than 2 output sections");
else
script->noCrossRefs.push_back(std::move(cmd));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth a warning if the outputSections.size() < 2 I don't think there could be a legal use case for NOCROSSREFS() or NOCROSSREFS(<section>).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a warning.


void ScriptParser::addFile(StringRef s) {
if (isUnderSysroot && s.starts_with("/")) {
SmallString<128> pathData;
Expand Down
5 changes: 5 additions & 0 deletions lld/ELF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1943,6 +1943,11 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
// have the headers, we can find out which sections they point to.
setReservedSymbolSections();

if (script->noCrossRefs.size()) {
llvm::TimeTraceScope timeScope("Check NOCROSSREFS");
checkNoCrossRefs<ELFT>();
}

{
llvm::TimeTraceScope timeScope("Finalize synthetic sections");

Expand Down
3 changes: 3 additions & 0 deletions lld/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ ELF Improvements
(`#87530 <https://github.com/llvm/llvm-project/pull/87530>`_)
* ``OUTPUT_FORMAT(binary)`` is now supported.
(`#98837 <https://github.com/llvm/llvm-project/pull/98837>`_)
* ``NOCROSSREFS`` and ``NOCRFOSSREFS_TO`` commands now supported to prohibit
cross references between certain output sections.
(`#98773 <https://github.com/llvm/llvm-project/pull/98773>`_)
* Orphan placement is refined to prefer the last similar section when its rank <= orphan's rank.
(`#94099 <https://github.com/llvm/llvm-project/pull/94099>`_)
Non-alloc orphan sections are now placed at the end.
Expand Down
99 changes: 99 additions & 0 deletions lld/test/ELF/linkerscript/nocrossrefs.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# REQUIRES: x86
# RUN: rm -rf %t && split-file %s %t && cd %t

# RUN: llvm-mc --triple=x86_64 -filetype=obj a.s -o a.o
# RUN: llvm-mc --triple=x86_64 -filetype=obj data.s -o data.o
# RUN: ld.lld a.o data.o -T 0.t 2>&1 | FileCheck %s --check-prefix=CHECK0 --implicit-check-not=warning:

# CHECK0: warning: 0.t:3: ignored with fewer than 2 output sections
# CHECK0-NEXT: warning: 0.t:4: ignored with fewer than 2 output sections

# RUN: not ld.lld a.o data.o -T 1.t 2>&1 | FileCheck %s --check-prefix=CHECK1 --implicit-check-not=error:
# CHECK1: error: a.o:(.text.start+0x11): prohibited cross reference from '.text' to 'data' in '.data'

## .text and .text1 are in two NOCROSSREFS commands. Violations are reported twice.
# RUN: not ld.lld --threads=1 a.o data.o -T 2.t 2>&1 | FileCheck %s --check-prefix=CHECK2 --implicit-check-not=error:
# CHECK2: error: a.o:(.text.start+0x6): prohibited cross reference from '.text' to '.text1' in '.text1'
# CHECK2-NEXT: error: a.o:(.text.start+0x6): prohibited cross reference from '.text' to '.text1' in '.text1'
# CHECK2-NEXT: error: a.o:(.text.start+0xb): prohibited cross reference from '.text' to 'foo2' in '.text2'
# CHECK2-NEXT: error: a.o:(.text.start+0x11): prohibited cross reference from '.text' to 'data' in '.data'
# CHECK2-NEXT: error: a.o:(.text.start+0x17): prohibited cross reference from '.text' to 'str1' in '.rodata'
## .data occurs twice in the command, but the violation is only reported once.
# CHECK2-NEXT: error: a.o:(.text1+0x1): prohibited cross reference from '.text1' to '_edata' in '.data'
# CHECK2-NEXT: error: a.o:(.nonalloc+0x0): prohibited cross reference from '.nonalloc' to '.text' in '.text'
# CHECK2-NEXT: error: a.o:(.nonalloc+0x10): prohibited cross reference from '.nonalloc' to 'data' in '.data'

# RUN: not ld.lld a.o data.o -T 3.t 2>&1 | FileCheck %s --check-prefix=CHECK3 --implicit-check-not=error:
# CHECK3: error: a.o:(.nonalloc+0x0): prohibited cross reference from '.nonalloc' to '.text' in '.text'

#--- 0.t
## Some cases that do not cause errors.
abs = 42;
NOCROSSREFS()
NOCROSSREFS (.text)
NOCROSSREFS( .text .text3 ); ## ; is ignored
NOCROSSREFS_TO(.text .text2 .text3 .data );
NOCROSSREFS_TO (.data .text2 .text3)

#--- 1.t
abs = 42;
NOCROSSREFS(.text ".data")

#--- 2.t
abs = 42;
NOCROSSREFS(.text ".text1" .text ".text1" )
NOCROSSREFS(.text .text1 .text2 .data .rodata .data .nonalloc)

#--- 3.t
abs = 42;
NOCROSSREFS_TO(.text .text .text1 .text2 .data .nonalloc)

#--- err1.t
NOCROSSREFS )

# RUN: not ld.lld a.o data.o -T err1.t 2>&1 | FileCheck %s --check-prefix=ERR1 --implicit-check-not=error:
# ERR1: error: err1.t:1: ( expected, but got )

#--- err2.t
NOCROSSREFS(.text

# RUN: not ld.lld a.o data.o -T err2.t 2>&1 | FileCheck %s --check-prefix=ERR2 --implicit-check-not=error:
# ERR2: error: err2.t:1: unexpected EOF

#--- a.s
.global _start, foo1, foo2, foo3
.section .text.start,"ax"
_start:
call _start
call .text1
call foo2
movl data(%rip), %eax
movl str1(%rip), %eax

.section .text1,"ax"
foo1:
call _edata

.section .text2,"ax"
foo2:
call foo3

.section .text3,"ax"
foo3:
call foo2

.section .rodata.str1.1,"aMS",@progbits,1
str1:
.asciz "abc"

.section .nonalloc,""
.quad .text
.quad .text3
.quad data

#--- data.s
.section .data,"aw"
.globl data
data:
.byte 0
.quad abs
Loading