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

Pavel Skripkin via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 16 10:53:52 PDT 2024


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

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

>From d7baadb71a0590e1950cd4eb744103ece8690eb3 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.h         |   8 +++
 lld/ELF/Relocations.cpp        |  59 ++++++++++++++++++
 lld/ELF/Relocations.h          |   1 +
 lld/ELF/ScriptParser.cpp       |  20 ++++++
 lld/ELF/Writer.cpp             |   3 +
 lld/test/ELF/gnu-nocrossrefs.s | 110 +++++++++++++++++++++++++++++++++
 6 files changed, 201 insertions(+)
 create mode 100644 lld/test/ELF/gnu-nocrossrefs.s

diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h
index 734d4e7498aa2..5821b0fc587aa 100644
--- a/lld/ELF/LinkerScript.h
+++ b/lld/ELF/LinkerScript.h
@@ -256,6 +256,11 @@ struct InsertCommand {
   StringRef where;
 };
 
+struct CrossRefList {
+  SmallVector<StringRef, 2> refs;
+  bool firstOnly;
+};
+
 struct PhdrsCommand {
   StringRef name;
   unsigned type = llvm::ELF::PT_NULL;
@@ -394,6 +399,9 @@ class LinkerScript final {
   // OutputSections specified by OVERWRITE_SECTIONS.
   SmallVector<OutputDesc *, 0> overwriteSections;
 
+  // Nocrossrefs sections.
+  SmallVector<CrossRefList, 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 2c02c2e572bfd..400013adf53dd 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -2358,3 +2358,62 @@ template void elf::scanRelocations<ELF32LE>();
 template void elf::scanRelocations<ELF32BE>();
 template void elf::scanRelocations<ELF64LE>();
 template void elf::scanRelocations<ELF64BE>();
+
+static bool isNoCrefFromSection(const CrossRefList &list,
+                                const OutputSection *section) {
+  const auto *begin =
+      list.firstOnly ? list.refs.begin() + 1 : list.refs.begin();
+
+  return std::find(begin, list.refs.end(), section->name) != list.refs.end();
+}
+
+static bool isNoCrefToSection(const CrossRefList &list,
+                              const OutputSection *section) {
+  if (list.firstOnly)
+    return list.refs[0] == section->name;
+
+  return std::find(list.refs.begin(), list.refs.end(), section->name) !=
+         list.refs.end();
+}
+
+void elf::checkNoCrossRefs() {
+  // Basic brute-force algorithm, since in reality NOCROSSRES lists are quite
+  // small.
+  //
+  // Idea is to traverse all relocations in all sections and report if
+  // prohibited reference was found. Note that NOCROSSREFS works with output section
+  // names.
+  for (ELFFileBase *file : ctx.objectFiles) {
+    auto sections = file->getSections();
+    std::string message = "";
+
+    for (size_t i = 0; i < sections.size(); ++i) {
+      if (sections[i]) {
+        StringRef sectionName = sections[i]->name;
+
+        if (const auto *outSec = dyn_cast_or_null<OutputSection>(sections[i]->parent)) {
+          for (const auto &refs : script->nocrossrefs) {
+            if (isNoCrefFromSection(refs, outSec)) {
+
+              for (const auto &j : sections[i]->relocations) {
+                if (auto *def = dyn_cast<Defined>(j.sym)) {
+                  const auto *outSecSym =
+                      cast<InputSection>(def->section)->getParent();
+
+                  if (isNoCrefToSection(refs, outSecSym)) {
+                    message +=
+                        (sections[i]->getLocation(j.offset) +
+                         ": prohibited cross reference from " +
+                         sectionName.str() + " to " + def->getName().str() +
+                         " in " + outSecSym->name.str());
+                    error(message);
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
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..0c14f43fc5f05 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 = false);
 
   SymbolAssignment *readSymbolAssignment(StringRef name);
   ByteCommand *readByteCommand(StringRef tok);
@@ -235,6 +236,21 @@ void ScriptParser::readVersionScriptCommand() {
   }
 }
 
+void ScriptParser::readNoCrossRefs(bool to) {
+  expect("(");
+
+  script->nocrossrefs.push_back({});
+
+  script->nocrossrefs.back().firstOnly = to;
+
+  while (!atEOF() && !errorCount() && peek() != ")") {
+    StringRef section = next();
+    script->nocrossrefs.back().refs.push_back(section);
+  }
+
+  expect(")");
+}
+
 void ScriptParser::readVersion() {
   expect("{");
   readVersionScriptCommand();
@@ -279,6 +295,10 @@ void ScriptParser::readLinkerScript() {
       readTarget();
     } else if (tok == "VERSION") {
       readVersion();
+    } else if (tok == "NOCROSSREFS") {
+      readNoCrossRefs();
+    } else if (tok == "NOCROSSREFS_TO") {
+      readNoCrossRefs(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..9466d383607c4 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->nocrossrefs.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/gnu-nocrossrefs.s b/lld/test/ELF/gnu-nocrossrefs.s
new file mode 100644
index 0000000000000..67d98482cad78
--- /dev/null
+++ b/lld/test/ELF/gnu-nocrossrefs.s
@@ -0,0 +1,110 @@
+// REQUIRES: x86
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS(.text .text1); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: not ld.lld %t.o -o %t --script %t.script 2>&1 | FileCheck -check-prefix=ERR %s
+// ERR: ld.lld: error: {{.*}}.o:(.text+{{.*}}): prohibited cross reference from .text to in .text1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS_TO(.text1 .text); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: not ld.lld %t.o -o %t --script %t.script 2>&1 | FileCheck -check-prefix=ERR1 %s
+// ERR1: ld.lld: error: {{.*}}.o:(.text+{{.*}}): prohibited cross reference from .text to in .text1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS(.text1 .text .text2); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: not ld.lld %t.o -o %t --script %t.script 2>&1 | FileCheck -check-prefix=ERR2 %s
+// ERR2: ld.lld: error: {{.*}}.o:(.text+{{.*}}): prohibited cross reference from .text to in .text1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS_TO(.text .text1); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: ld.lld %t.o -o %t --script %t.script 2>&1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS_TO(); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: ld.lld %t.o -o %t --script %t.script 2>&1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS(); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: ld.lld %t.o -o %t --script %t.script 2>&1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS(.text); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: ld.lld %t.o -o %t --script %t.script 2>&1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS_TO(.text); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: ld.lld %t.o -o %t --script %t.script 2>&1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS_TO(.text2 .text); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: ld.lld %t.o -o %t --script %t.script 2>&1
+
+// RUN: llvm-mc -filetype=obj -o %t.o %s
+// RUN: echo "NOCROSSREFS(.text .text2); \
+// RUN:       SECTIONS { \
+// RUN:         .text  : { *(.text) } \
+// RUN:         .text1 : { *(.text1) } \
+// RUN:         .text2 : { *(.text2) } \
+// RUN: } " > %t.script
+// RUN: ld.lld %t.o -o %t --script %t.script 2>&1
+
+.global _start
+_start:
+	nop
+
+.section .text
+test:
+	call test1
+
+.section .text2
+test2:
+	nop
+
+.section .text1
+test1:
+	nop



More information about the llvm-commits mailing list