Skip to content

lld: add support for NOCROSSREFS(_TO) #95714

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lld/ELF/LinkerScript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
16 changes: 16 additions & 0 deletions lld/ELF/LinkerScript.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
42 changes: 42 additions & 0 deletions lld/ELF/Relocations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2358,3 +2358,45 @@ template void elf::scanRelocations<ELF32LE>();
template void elf::scanRelocations<ELF32BE>();
template void elf::scanRelocations<ELF64LE>();
template void elf::scanRelocations<ELF64BE>();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't add code after explicit template instantiations. I would place the checks to LinkerScript.cpp.
Relocations.cpp code should minimize uses of outputSections.

static void forEachAllocInputSectionDescription(
ArrayRef<OutputSection *> outputSections,
llvm::function_ref<void(OutputSection *, InputSectionDescription *)> fn) {
for (OutputSection *os : outputSections) {
if (!(os->flags & SHF_ALLOC))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GNU ld reports violations due to non-SHF_ALLOC sections. I'll fix it in my branch.

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) +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my branch I'll use this:

+      std::string toSymName;
+      if (!r.sym->isSection())
+        toSymName = toString(*r.sym);
+      else if (auto *d = dyn_cast<Defined>(r.sym))
+        toSymName = d->section->name;
+      errorOrWarn(sec->getLocation(r.offset) +
+                  ": prohibited cross reference to '" + toSymName + "' in " +
+                  dstOsec->name.str());

I think from section can be omitted since getLocation carries the location information.

": prohibited cross reference from " + inSection->name.str() +
" to " + relocation.sym->getName().str() + " in " +
destOutSec->name.str());
}
}
}
}

void elf::checkNoCrossRefs() {
forEachAllocInputSectionDescription(outputSections, checkSectionNoCrossRefs);
}
1 change: 1 addition & 0 deletions lld/ELF/Relocations.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ struct JumpInstrMod {
// the diagnostics.
template <class ELFT> void scanRelocations();
void reportUndefinedSymbols();
void checkNoCrossRefs();
void postScanRelocations();
void addGotEntry(Symbol &sym);

Expand Down
25 changes: 25 additions & 0 deletions lld/ELF/ScriptParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -235,6 +236,26 @@ void ScriptParser::readVersionScriptCommand() {
}
}

void ScriptParser::readNoCrossRefs(bool to) {
expect("(");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our code style is quite terse. We only blank lines when there is a significant "pause" need.

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();
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions lld/ELF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
179 changes: 179 additions & 0 deletions lld/test/ELF/linkerscript/nocrossrefs.test
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove -unknown-linux. make it clear this doesn't use Linux specific features.

# RUN: not ld.lld main.o -o main --script script1.ld 2>&1 | FileCheck -check-prefix=ERR %s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-o is unneeded when using not ld.lld
(Since we have done cd %t, the unused output file will not clutter the current directory)

# ERR: {{.*}} error: main.o:(.text+0x6): prohibited cross reference from .text to in .text1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something is omitted after to


#--- script1.ld
NOCROSSREFS(.text .text1);
SECTIONS {
.text : { *(.text) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll clean up the tests. We actually don't need SECTIONS since the default orphan section placement does the right thing. We need to make these sections SHF_ALLOC, though ("a").

.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,@object
.comm unused,4,4

.section .noalloc,"",@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
Loading