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

Conversation

MaskRay
Copy link
Member

@MaskRay MaskRay commented Jul 13, 2024

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

Created using spr 1.3.5-bogner
@llvmbot
Copy link
Member

llvmbot commented Jul 13, 2024

@llvm/pr-subscribers-lld-elf

@llvm/pr-subscribers-lld

Author: Fangrui Song (MaskRay)

Changes

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


Full diff: https://github.com/llvm/llvm-project/pull/98773.diff

6 Files Affected:

  • (modified) lld/ELF/LinkerScript.h (+13)
  • (modified) lld/ELF/Relocations.cpp (+54)
  • (modified) lld/ELF/Relocations.h (+1)
  • (modified) lld/ELF/ScriptParser.cpp (+15)
  • (modified) lld/ELF/Writer.cpp (+5)
  • (added) lld/test/ELF/linkerscript/nocrossrefs.test (+84)
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
Copy link
Collaborator

@smithp35 smithp35 left a 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.

@@ -256,6 +256,16 @@ struct InsertCommand {
StringRef where;
};

// A NOCROSSREFS/NOCROSSREFS_TO command that probits references among certain
Copy link
Collaborator

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));
}
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.

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
Copy link
Collaborator

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.

Copy link
Member Author

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) {
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)

MaskRay added 3 commits July 16, 2024 09:39
Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner
Copy link
Collaborator

@smithp35 smithp35 left a 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.

@MaskRay MaskRay merged commit 0778f5c into main Jul 17, 2024
8 checks passed
@MaskRay MaskRay deleted the users/MaskRay/spr/elf-support-nocrossrefs-and-nocrosserfs_to branch July 17, 2024 17:46
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"NOCROSSREFS_TO" doesn't supported by LLVM's ld.lld
3 participants