[lld] [ELF] Support NOCROSSREFS and NOCROSSERFS_TO (PR #98773)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 16 10:30:41 PDT 2024


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/98773

>From 7e01b3f97be7623305f1e3d94a66e97ebc6ff902 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 13 Jul 2024 13:57:40 -0700
Subject: [PATCH 1/3] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
 =?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 1.3.5-bogner
---
 lld/ELF/LinkerScript.h                     | 13 ++++
 lld/ELF/Relocations.cpp                    | 54 ++++++++++++++
 lld/ELF/Relocations.h                      |  1 +
 lld/ELF/ScriptParser.cpp                   | 15 ++++
 lld/ELF/Writer.cpp                         |  5 ++
 lld/test/ELF/linkerscript/nocrossrefs.test | 84 ++++++++++++++++++++++
 6 files changed, 172 insertions(+)
 create mode 100644 lld/test/ELF/linkerscript/nocrossrefs.test

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", 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

>From f59719f3d533ba288822a8c5357065b94bf7af98 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 13 Jul 2024 14:13:37 -0700
Subject: [PATCH 2/3] support quote

Created using spr 1.3.5-bogner
---
 lld/ELF/Relocations.cpp                    |  2 +
 lld/ELF/ScriptParser.cpp                   |  5 +-
 lld/test/ELF/linkerscript/nocrossrefs.test | 54 +++++++++++++---------
 3 files changed, 37 insertions(+), 24 deletions(-)

diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 6c1edaa70728a..8f27d5e5b262b 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -2395,6 +2395,8 @@ static void scanCrossRefs(const NoCrossRefCommand &cmd, OutputSection *osec,
   }
 }
 
+// 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) {
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 65fca3d0d090e..21c2f2c29a0aa 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -307,11 +307,10 @@ void ScriptParser::readDefsym(StringRef name) {
 void ScriptParser::readNoCrossRefs(bool to) {
   expect("(");
   NoCrossRefCommand cmd{{}, to};
-  while (!atEOF() && !errorCount() && peek() != ")")
-    cmd.outputSections.push_back(next());
+  while (!errorCount() && !consume(")"))
+    cmd.outputSections.push_back(unquote(next()));
   if (cmd.outputSections.size() >= 2)
     script->noCrossRefs.push_back(std::move(cmd));
-  expect(")");
 }
 
 void ScriptParser::addFile(StringRef s) {
diff --git a/lld/test/ELF/linkerscript/nocrossrefs.test b/lld/test/ELF/linkerscript/nocrossrefs.test
index 5ec6607959494..8fbd5ef628fec 100644
--- a/lld/test/ELF/linkerscript/nocrossrefs.test
+++ b/lld/test/ELF/linkerscript/nocrossrefs.test
@@ -5,45 +5,57 @@
 # 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'
+# 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=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'
+# 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.
-# 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'
+# 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=ERR3 --implicit-check-not=error:
-# ERR3:      error: a.o:(.nonalloc+0x0): prohibited cross reference from '.nonalloc' to '.text' in '.text'
+# 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 );
+NOCROSSREFS()
+NOCROSSREFS (.text)
+NOCROSSREFS( .text .text3 );  ## ; is ignored
 NOCROSSREFS_TO(.text .text2 .text3 .data );
-NOCROSSREFS_TO(.data .text2 .text3);
+NOCROSSREFS_TO (.data .text2 .text3)
 
 #--- 1.t
 abs = 42;
-NOCROSSREFS(.text .data);
+NOCROSSREFS(.text ".data")
 
 #--- 2.t
 abs = 42;
-NOCROSSREFS(.text .text1 .text .text1);
-NOCROSSREFS(.text .text1 .text2 .data .rodata .data .nonalloc);
+NOCROSSREFS(.text ".text1" .text ".text1" )
+NOCROSSREFS(.text .text1 .text2 .data .rodata .data .nonalloc)
 
 #--- 3.t
 abs = 42;
-NOCROSSREFS_TO(.text .text1 .text2 .data .nonalloc);
+NOCROSSREFS_TO(.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

>From d73bea7bf70e2538f67ae018b7821a810558d4ee Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 16 Jul 2024 10:24:16 -0700
Subject: [PATCH 3/3] improve comment

Created using spr 1.3.5-bogner
---
 lld/ELF/Relocations.cpp                    | 5 +++--
 lld/ELF/ScriptParser.cpp                   | 4 +++-
 lld/test/ELF/linkerscript/nocrossrefs.test | 5 ++++-
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 4b94ca443467e..36857d72c647e 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -2378,8 +2378,9 @@ 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.
+    // 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;
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 21c2f2c29a0aa..fb5321bc8e817 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -309,7 +309,9 @@ void ScriptParser::readNoCrossRefs(bool to) {
   NoCrossRefCommand cmd{{}, to};
   while (!errorCount() && !consume(")"))
     cmd.outputSections.push_back(unquote(next()));
-  if (cmd.outputSections.size() >= 2)
+  if (cmd.outputSections.size() < 2)
+    warn(getCurrentLocation() + ": ignored with fewer than 2 output sections");
+  else
     script->noCrossRefs.push_back(std::move(cmd));
 }
 
diff --git a/lld/test/ELF/linkerscript/nocrossrefs.test b/lld/test/ELF/linkerscript/nocrossrefs.test
index bf189517ed56c..f13d50a03be87 100644
--- a/lld/test/ELF/linkerscript/nocrossrefs.test
+++ b/lld/test/ELF/linkerscript/nocrossrefs.test
@@ -3,7 +3,10 @@
 
 # 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: 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'



More information about the llvm-commits mailing list