[lld] 0778f5c - [ELF] Support NOCROSSREFS and NOCROSSERFS_TO
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 17 10:46:03 PDT 2024
Author: Fangrui Song
Date: 2024-07-17T10:45:59-07:00
New Revision: 0778f5c1f11da599b71d6c9f5990fd880ff7cb46
URL: https://github.com/llvm/llvm-project/commit/0778f5c1f11da599b71d6c9f5990fd880ff7cb46
DIFF: https://github.com/llvm/llvm-project/commit/0778f5c1f11da599b71d6c9f5990fd880ff7cb46.diff
LOG: [ELF] Support NOCROSSREFS and NOCROSSERFS_TO
Implement the two commands described by
https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html
After `outputSections` is available, check each output section described
by at least one `NOCROSSREFS`/`NOCROSSERFS_TO` command. For each checked
output section, scan relocations from its input sections.
This step is slow, therefore utilize `parallelForEach(isd->sections, ...)`.
To support non SHF_ALLOC sections, `InputSectionBase::relocations`
(empty) cannot be used. In addition, we may explore eliminating this
member to speed up relocation scanning.
Some parse code is adapted from #95714.
Close #41825
Pull Request: https://github.com/llvm/llvm-project/pull/98773
Added:
lld/test/ELF/linkerscript/nocrossrefs.test
Modified:
lld/ELF/LinkerScript.h
lld/ELF/Relocations.cpp
lld/ELF/Relocations.h
lld/ELF/ScriptParser.cpp
lld/ELF/Writer.cpp
lld/docs/ReleaseNotes.rst
Removed:
################################################################################
diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index 43d0850eed718..b86521a429f04 100644
--- a/lld/ELF/LinkerScript.h
+++ b/lld/ELF/LinkerScript.h
@@ -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;
@@ -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;
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 9ad180306bcd8..36857d72c647e 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -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) {
+ 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>();
diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h
index e299d23dd7db3..1bee0dedf8587 100644
--- a/lld/ELF/Relocations.h
+++ b/lld/ELF/Relocations.h
@@ -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);
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 92ef9330141fc..47a94c29ea496 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -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);
@@ -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 {
@@ -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));
+}
+
void ScriptParser::addFile(StringRef s) {
if (isUnderSysroot && s.starts_with("/")) {
SmallString<128> pathData;
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 8940a1c5d5113..5cffdb771a738 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -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");
diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst
index 05179bfdcb536..ba8543732bb8e 100644
--- a/lld/docs/ReleaseNotes.rst
+++ b/lld/docs/ReleaseNotes.rst
@@ -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.
diff --git a/lld/test/ELF/linkerscript/nocrossrefs.test b/lld/test/ELF/linkerscript/nocrossrefs.test
new file mode 100644
index 0000000000000..f13d50a03be87
--- /dev/null
+++ b/lld/test/ELF/linkerscript/nocrossrefs.test
@@ -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", at 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
More information about the llvm-commits
mailing list