-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
[ELF] Support NOCROSSREFS and NOCROSSERFS_TO #98773
Conversation
Created using spr 1.3.5-bogner
@llvm/pr-subscribers-lld-elf @llvm/pr-subscribers-lld Author: Fangrui Song (MaskRay) ChangesImplement the two commands described by After To support non SHF_ALLOC sections, Some parse code is adapted from #95714. Close #41825 Full diff: https://github.com/llvm/llvm-project/pull/98773.diff 6 Files Affected:
diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index 43d0850eed718..b987026c00200 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 probits references among 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..6c1edaa70728a 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -2367,7 +2367,61 @@ 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));
+ // The destination output section can be nullptr, osec, or those 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 + "'");
+ }
+}
+
+template <class ELFT> void elf::checkNoCrossRefs() {
+ for (OutputSection *osec : outputSections) {
+ for (const NoCrossRefCommand &noxref : script->noCrossRefs) {
+ if (!llvm::is_contained(noxref.outputSections, 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 41bd9a95053f7..65fca3d0d090e 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,16 @@ void ScriptParser::readDefsym(StringRef name) {
script->sectionCommands.push_back(cmd);
}
+void ScriptParser::readNoCrossRefs(bool to) {
+ expect("(");
+ NoCrossRefCommand cmd{{}, to};
+ while (!atEOF() && !errorCount() && peek() != ")")
+ cmd.outputSections.push_back(next());
+ if (cmd.outputSections.size() >= 2)
+ script->noCrossRefs.push_back(std::move(cmd));
+ expect(")");
+}
+
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/test/ELF/linkerscript/nocrossrefs.test b/lld/test/ELF/linkerscript/nocrossrefs.test
new file mode 100644
index 0000000000000..5ec6607959494
--- /dev/null
+++ b/lld/test/ELF/linkerscript/nocrossrefs.test
@@ -0,0 +1,84 @@
+# 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
+
+# RUN: not ld.lld a.o data.o -T 1.t 2>&1 | FileCheck %s --check-prefix=ERR1 --implicit-check-not=error:
+# ERR1: 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=ERR2 --implicit-check-not=error:
+# ERR2: error: a.o:(.text.start+0x6): prohibited cross reference from '.text' to '.text1' in '.text1'
+# ERR2-NEXT: error: a.o:(.text.start+0x6): prohibited cross reference from '.text' to '.text1' in '.text1'
+# ERR2-NEXT: error: a.o:(.text.start+0xb): prohibited cross reference from '.text' to 'foo2' in '.text2'
+# ERR2-NEXT: error: a.o:(.text.start+0x11): prohibited cross reference from '.text' to 'data' in '.data'
+# ERR2-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.
+# ERR2-NEXT: error: a.o:(.text1+0x1): prohibited cross reference from '.text1' to '_edata' in '.data'
+# ERR2-NEXT: error: a.o:(.nonalloc+0x0): prohibited cross reference from '.nonalloc' to '.text' in '.text'
+# ERR2-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=ERR3 --implicit-check-not=error:
+# ERR3: 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 );
+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 .text1 .text2 .data .nonalloc);
+
+#--- 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
|
Created using spr 1.3.5-bogner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only a small number of comments, otherwise LGTM.
lld/ELF/LinkerScript.h
Outdated
@@ -256,6 +256,16 @@ struct InsertCommand { | |||
StringRef where; | |||
}; | |||
|
|||
// A NOCROSSREFS/NOCROSSREFS_TO command that probits references among certain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo probits
-> prohibits
.
I'd also say references between certain output sections
but that just reads a bit more naturally to me.
cmd.outputSections.push_back(unquote(next())); | ||
if (cmd.outputSections.size() >= 2) | ||
script->noCrossRefs.push_back(std::move(cmd)); | ||
} |
There was a problem hiding this comment.
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>)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a warning.
lld/ELF/Relocations.cpp
Outdated
InputSection *sec, Rels rels) { | ||
for (const auto &r : rels) { | ||
Symbol &sym = sec->file->getSymbol(r.getSymbol(config->isMips64EL)); | ||
// The destination output section can be nullptr, osec, or those described |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm don't fully understand what
or those described by the NOCROSSREFS/NOCROSSREFS_TO command.
means in this context.
If the comment is describing permitted cross-references then I'd expect something like:
A legal cross-reference is when the destination output section is nullptr, osec for a self-reference, or a section that is not in the set described by the NOCROSSREFS/NOCROSSREFS_TO command.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion. Improved the comment.
|
||
template <class ELFT, class Rels> | ||
static void scanCrossRefs(const NoCrossRefCommand &cmd, OutputSection *osec, | ||
InputSection *sec, Rels rels) { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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)
Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM thanks for the updates.
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: #98773
Implement the two commands described by
https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html
After
outputSections
is available, check each output section describedby at least one
NOCROSSREFS
/NOCROSSERFS_TO
command. For each checkedoutput 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