[lld] lld: add support for NOCROSSREFS(_TO) (PR #95714)

Pavel Skripkin via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 21 12:25:59 PDT 2024


https://github.com/pskrgag updated https://github.com/llvm/llvm-project/pull/95714

>From 05e0a5563896376bc6092b5658db331fd29f87e5 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Sun, 16 Jun 2024 20:02:23 +0300
Subject: [PATCH] ldd: add support for NOCROSSREFS(_TO)

Patch introduces supprot for NOCROSSREFS_(TO) linker script commands.
These commands specify which cross-section references should be threated
as errors. See more in ld documenmtation [0]

Implementation is straightforward -- traverse all relocations in all
object files and report an error if there is prohibited one.

[0] https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html

Closes: #41825
Signed-off-by: Pavel Skripkin <paskripkin at gmail.com>
---
 lld/ELF/LinkerScript.cpp                   |  11 ++
 lld/ELF/LinkerScript.h                     |  16 ++
 lld/ELF/Relocations.cpp                    |  42 +++++
 lld/ELF/Relocations.h                      |   1 +
 lld/ELF/ScriptParser.cpp                   |  25 +++
 lld/ELF/Writer.cpp                         |   3 +
 lld/test/ELF/linkerscript/nocrossrefs.test | 179 +++++++++++++++++++++
 7 files changed, 277 insertions(+)
 create mode 100644 lld/test/ELF/linkerscript/nocrossrefs.test

diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index 3ba59c112b8a8..f57e7ae7d4bf9 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -1714,3 +1714,14 @@ bool LinkerScript::shouldAddProvideSym(StringRef symName) {
   Symbol *sym = symtab.find(symName);
   return sym && !sym->isDefined() && !sym->isCommon();
 }
+
+bool NoCrossRefList::matchesRefToSection(const OutputSection *section) const {
+  if (toSection)
+    return toSection.value() == section->name;
+
+  return llvm::is_contained(outputSections, section->name);
+}
+
+bool NoCrossRefList::matchesRefFromSection(const OutputSection *section) const {
+  return llvm::is_contained(outputSections, section->name);
+}
diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index 734d4e7498aa2..5f859a0b39eae 100644
--- a/lld/ELF/LinkerScript.h
+++ b/lld/ELF/LinkerScript.h
@@ -256,6 +256,19 @@ struct InsertCommand {
   StringRef where;
 };
 
+struct NoCrossRefList {
+  SmallVector<StringRef, 2> outputSections;
+
+  // See documentation for NOCROSSREFS and NOCROSSREFS_TO. When toSection is
+  // NONE outputSections are output section names that must not have any cross
+  // references between them. Otherwise, toSection is tosection name and
+  // outputSections are fromsections.
+  std::optional<StringRef> toSection;
+
+  bool matchesRefFromSection(const OutputSection *section) const;
+  bool matchesRefToSection(const OutputSection *section) const;
+};
+
 struct PhdrsCommand {
   StringRef name;
   unsigned type = llvm::ELF::PT_NULL;
@@ -394,6 +407,9 @@ class LinkerScript final {
   // OutputSections specified by OVERWRITE_SECTIONS.
   SmallVector<OutputDesc *, 0> overwriteSections;
 
+  // OutputSections names specified by NOCROSSREFS(_TO).
+  SmallVector<NoCrossRefList, 0> noCrossRefLists;
+
   // 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 2c02c2e572bfd..47081ad94b748 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -2358,3 +2358,45 @@ template void elf::scanRelocations<ELF32LE>();
 template void elf::scanRelocations<ELF32BE>();
 template void elf::scanRelocations<ELF64LE>();
 template void elf::scanRelocations<ELF64BE>();
+
+static void forEachAllocInputSectionDescription(
+    ArrayRef<OutputSection *> outputSections,
+    llvm::function_ref<void(OutputSection *, InputSectionDescription *)> fn) {
+  for (OutputSection *os : outputSections) {
+    if (!(os->flags & SHF_ALLOC))
+      continue;
+    for (SectionCommand *bc : os->commands)
+      if (auto *isd = dyn_cast<InputSectionDescription>(bc))
+        fn(os, isd);
+  }
+}
+
+static void checkSectionNoCrossRefs(OutputSection *outSec,
+                                    InputSectionDescription *inSecDescr) {
+  for (const auto &list : script->noCrossRefLists) {
+    if (!list.matchesRefFromSection(outSec))
+      continue;
+
+    for (const auto &inSection : inSecDescr->sections) {
+      for (const auto &relocation : inSection->relocations) {
+        auto *destOutSec = relocation.sym->getOutputSection();
+        if (!destOutSec)
+          continue;
+
+        // Relocations from section to itself are allowed.
+        if (destOutSec->name == outSec->name ||
+            !list.matchesRefToSection(destOutSec))
+          continue;
+
+        error(inSection->getLocation(relocation.offset) +
+              ": prohibited cross reference from " + inSection->name.str() +
+              " to " + relocation.sym->getName().str() + " in " +
+              destOutSec->name.str());
+      }
+    }
+  }
+}
+
+void elf::checkNoCrossRefs() {
+  forEachAllocInputSectionDescription(outputSections, checkSectionNoCrossRefs);
+}
diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h
index b7b9c09e1b892..eabae26283c24 100644
--- a/lld/ELF/Relocations.h
+++ b/lld/ELF/Relocations.h
@@ -141,6 +141,7 @@ struct JumpInstrMod {
 // the diagnostics.
 template <class ELFT> void scanRelocations();
 void reportUndefinedSymbols();
+void checkNoCrossRefs();
 void postScanRelocations();
 void addGotEntry(Symbol &sym);
 
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index f90ce6fa74075..d92ea7b191bad 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);
@@ -235,6 +236,26 @@ void ScriptParser::readVersionScriptCommand() {
   }
 }
 
+void ScriptParser::readNoCrossRefs(bool to) {
+  expect("(");
+
+  script->noCrossRefLists.push_back({});
+  auto &list = script->noCrossRefLists.back();
+
+  if (to && peek() != ")")
+    list.toSection = next();
+
+  while (!atEOF() && !errorCount() && peek() != ")")
+    list.outputSections.push_back(next());
+
+  // Discard meaningless lists
+  if ((to && list.outputSections.empty()) ||
+      (!to && list.outputSections.size() < 2))
+    script->noCrossRefLists.pop_back();
+
+  expect(")");
+}
+
 void ScriptParser::readVersion() {
   expect("{");
   readVersionScriptCommand();
@@ -279,6 +300,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 {
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index fe2e1900520a4..f8edd039c5779 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -309,6 +309,9 @@ template <class ELFT> void Writer<ELFT>::run() {
   finalizeSections();
   checkExecuteOnly();
 
+  if (script->noCrossRefLists.size())
+    checkNoCrossRefs();
+
   // If --compressed-debug-sections is specified, compress .debug_* sections.
   // Do it right now because it changes the size of output sections.
   for (OutputSection *sec : outputSections)
diff --git a/lld/test/ELF/linkerscript/nocrossrefs.test b/lld/test/ELF/linkerscript/nocrossrefs.test
new file mode 100644
index 0000000000000..cac12373c3939
--- /dev/null
+++ b/lld/test/ELF/linkerscript/nocrossrefs.test
@@ -0,0 +1,179 @@
+# REQUIRES: x86
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: not ld.lld main.o -o main --script script1.ld 2>&1 | FileCheck -check-prefix=ERR %s
+# ERR: {{.*}} error: main.o:(.text+0x6): prohibited cross reference from .text to in .text1
+
+#--- script1.ld
+NOCROSSREFS(.text .text1);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: not ld.lld main.o -o main --script script2.ld 2>&1 | FileCheck -check-prefix=ERR1 %s
+# ERR1: {{.*}} error: main.o:(.text+0x6): prohibited cross reference from .text to in .text1
+
+#--- script2.ld
+NOCROSSREFS_TO(.text1 .text);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: not ld.lld main.o -o main --script script3.ld 2>&1 | FileCheck -check-prefix=ERR2 %s
+# ERR2: {{.*}} error: main.o:(.text+0x6): prohibited cross reference from .text to in .text1
+
+#--- script3.ld
+NOCROSSREFS(.text1 .text .text2);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script4.ld 2>&1
+
+#--- script4.ld
+NOCROSSREFS_TO(.text .text1);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script5.ld 2>&1
+
+#--- script5.ld
+NOCROSSREFS_TO();
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script6.ld 2>&1
+
+#--- script6.ld
+NOCROSSREFS();
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script7.ld 2>&1
+
+#--- script7.ld
+NOCROSSREFS(.text);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script8.ld 2>&1
+
+#--- script8.ld
+NOCROSSREFS_TO(.text);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script9.ld 2>&1
+
+#--- script9.ld
+NOCROSSREFS_TO(.text2 .text);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script10.ld 2>&1
+
+#--- script10.ld
+NOCROSSREFS(.text .text2);
+SECTIONS {
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script11.ld 2>&1
+
+#--- script11.ld
+NOCROSSREFS(.text .text2);
+SECTIONS {
+	foo = ABSOLUTE(.);
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: ld.lld main.o -o main --script script12.ld 2>&1
+
+#--- script12.ld
+NOCROSSREFS(.text .text2);
+SECTIONS {
+	foo = ABSOLUTE(.);
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+	.bss   : { *(.unused) }
+}
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux -filetype=obj -o main.o main.s
+# RUN: not ld.lld main.o -o main --script script13.ld 2>&1 | FileCheck -check-prefix=ERR3 %s
+# ERR3: {{.*}} error: main.o:(.text+0x5): prohibited cross reference from .text to unused in .bss
+
+#--- script13.ld
+NOCROSSREFS(.text .bss);
+SECTIONS {
+	foo = ABSOLUTE(.);
+	.text  : { *(.text) }
+	.text1 : { *(.text1) }
+	.text2 : { *(.text2) }
+	.bss   : { *(.unused) }
+}
+
+#--- main.s
+.global _start
+_start:
+	call test
+
+	.type	unused, at object
+	.comm	unused,4,4
+
+	.section	.noalloc,"", at progbits
+	.quad	unused
+
+.section .text
+test:
+	.reloc ., R_X86_64_32, unused
+	call test1
+
+.section .text2
+test2:
+	.reloc ., R_X86_64_32, foo
+	nop
+
+.section .text1
+test1:
+	nop



More information about the llvm-commits mailing list