diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -219,7 +219,7 @@ switch (identify_magic(mbref.getBuffer())) { case file_magic::unknown: - readLinkerScript(mbref); + readLinkerScript(mbref, /*overwriteSection=*/false); return; case file_magic::archive: { // Handle -whole-archive. @@ -1428,9 +1428,11 @@ break; } case OPT_script: + case OPT_overwrite_section_script: if (Optional path = searchScript(arg->getValue())) { if (Optional mb = readFile(*path)) - readLinkerScript(*mb); + readLinkerScript(*mb, arg->getOption().getID() == + OPT_overwrite_section_script); break; } error(Twine("cannot find linker script ") + arg->getValue()); diff --git a/lld/ELF/LinkerScript.h b/lld/ELF/LinkerScript.h --- a/lld/ELF/LinkerScript.h +++ b/lld/ELF/LinkerScript.h @@ -227,7 +227,7 @@ }; struct InsertCommand { - OutputSection *os; + StringRef name; bool isAfter; StringRef where; }; @@ -343,6 +343,9 @@ // to be reordered. std::vector insertCommands; + // OutputSections specified by --overwrite-section-script. + std::vector overwriteSections; + // Sections that will be warned/errored by --orphan-handling. std::vector orphanSections; }; diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp --- a/lld/ELF/LinkerScript.cpp +++ b/lld/ELF/LinkerScript.cpp @@ -254,9 +254,13 @@ for (const InsertCommand &cmd : insertCommands) { // If cmd.os is empty, it may have been discarded by // adjustSectionsBeforeSorting(). We do not handle such output sections. - auto from = llvm::find(sectionCommands, cmd.os); + auto from = llvm::find_if(sectionCommands, [&](BaseCommand *base) { + return isa(base) && + cast(base)->name == cmd.name; + }); if (from == sectionCommands.end()) continue; + OutputSection *osec = cast(*from); sectionCommands.erase(from); auto insertPos = llvm::find_if(sectionCommands, [&cmd](BaseCommand *base) { @@ -264,12 +268,12 @@ return to != nullptr && to->name == cmd.where; }); if (insertPos == sectionCommands.end()) { - error("unable to insert " + cmd.os->name + + error("unable to insert " + osec->name + (cmd.isAfter ? " after " : " before ") + cmd.where); } else { if (cmd.isAfter) ++insertPos; - sectionCommands.insert(insertPos, cmd.os); + sectionCommands.insert(insertPos, osec); } } } @@ -547,52 +551,74 @@ // Create output sections described by SECTIONS commands. void LinkerScript::processSectionCommands() { + auto process = [this](OutputSection *osec) { + std::vector v = createInputSectionList(*osec); + + // The output section name `/DISCARD/' is special. + // Any input section assigned to it is discarded. + if (osec->name == "/DISCARD/") { + for (InputSectionBase *s : v) + discard(s); + discardSynthetic(*osec); + osec->sectionCommands.clear(); + return false; + } + + // This is for ONLY_IF_RO and ONLY_IF_RW. An output section directive + // ".foo : ONLY_IF_R[OW] { ... }" is handled only if all member input + // sections satisfy a given constraint. If not, a directive is handled + // as if it wasn't present from the beginning. + // + // Because we'll iterate over SectionCommands many more times, the easy + // way to "make it as if it wasn't present" is to make it empty. + if (!matchConstraints(v, osec->constraint)) { + for (InputSectionBase *s : v) + s->parent = nullptr; + osec->sectionCommands.clear(); + return false; + } + + // Handle subalign (e.g. ".foo : SUBALIGN(32) { ... }"). If subalign + // is given, input sections are aligned to that value, whether the + // given value is larger or smaller than the original section alignment. + if (osec->subalignExpr) { + uint32_t subalign = osec->subalignExpr().getValue(); + for (InputSectionBase *s : v) + s->alignment = subalign; + } + + // Set the partition field the same way OutputSection::recordSection() + // does. Partitions cannot be used with the SECTIONS command, so this is + // always 1. + osec->partition = 1; + return true; + }; + + // Process --overwrite-section-script first so that it can overwrite the main + // script or orphans. + DenseMap map; size_t i = 0; - for (BaseCommand *base : sectionCommands) { - if (auto *sec = dyn_cast(base)) { - std::vector v = createInputSectionList(*sec); - - // The output section name `/DISCARD/' is special. - // Any input section assigned to it is discarded. - if (sec->name == "/DISCARD/") { - for (InputSectionBase *s : v) - discard(s); - discardSynthetic(*sec); - sec->sectionCommands.clear(); - continue; + for (OutputSection *osec : overwriteSections) + if (process(osec) && !map.try_emplace(osec->name, osec).second) + warn("--overwrite-section-script specifies duplicate " + osec->name); + for (BaseCommand *&base : sectionCommands) + if (auto *osec = dyn_cast(base)) { + if (OutputSection *overwrite = map.lookup(osec->name)) { + log(osec->name + " is overwritten by --overwrite-section-script to " + + overwrite->location); + overwrite->sectionIndex = i++; + base = overwrite; + } else if (process(osec)) { + osec->sectionIndex = i++; } - - // This is for ONLY_IF_RO and ONLY_IF_RW. An output section directive - // ".foo : ONLY_IF_R[OW] { ... }" is handled only if all member input - // sections satisfy a given constraint. If not, a directive is handled - // as if it wasn't present from the beginning. - // - // Because we'll iterate over SectionCommands many more times, the easy - // way to "make it as if it wasn't present" is to make it empty. - if (!matchConstraints(v, sec->constraint)) { - for (InputSectionBase *s : v) - s->parent = nullptr; - sec->sectionCommands.clear(); - continue; - } - - // Handle subalign (e.g. ".foo : SUBALIGN(32) { ... }"). If subalign - // is given, input sections are aligned to that value, whether the - // given value is larger or smaller than the original section alignment. - if (sec->subalignExpr) { - uint32_t subalign = sec->subalignExpr().getValue(); - for (InputSectionBase *s : v) - s->alignment = subalign; - } - - // Set the partition field the same way OutputSection::recordSection() - // does. Partitions cannot be used with the SECTIONS command, so this is - // always 1. - sec->partition = 1; - - sec->sectionIndex = i++; } - } + + // If an --overwrite-section-script specified output section is not in + // sectionCommands, append it to the end. The section will be inserted by + // orphan placement. + for (OutputSection *osec : overwriteSections) + if (osec->partition == 1 && osec->sectionIndex == UINT32_MAX) + sectionCommands.push_back(osec); } void LinkerScript::processSymbolAssignments() { diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -366,6 +366,8 @@ MetaVarName<"">; defm script: Eq<"script", "Read linker script">; +defm overwrite_section_script : EEq<"overwrite-section-script", + "Read a script containing SECTIONS commands that replace output section descriptions in the main or default linker script">; defm section_start: Eq<"section-start", "Set address of section">, MetaVarName<"
">; diff --git a/lld/ELF/ScriptParser.h b/lld/ELF/ScriptParser.h --- a/lld/ELF/ScriptParser.h +++ b/lld/ELF/ScriptParser.h @@ -17,7 +17,7 @@ // Parses a linker script. Calling this function updates // lld::elf::config and lld::elf::script. -void readLinkerScript(MemoryBufferRef mb); +void readLinkerScript(MemoryBufferRef mb, bool overwriteSection); // Parses a version script. void readVersionScript(MemoryBufferRef mb); diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp --- a/lld/ELF/ScriptParser.cpp +++ b/lld/ELF/ScriptParser.cpp @@ -60,6 +60,7 @@ } void readLinkerScript(); + void readOverwriteSectionScript(); void readVersionScript(); void readDynamicList(); void readDefsym(StringRef name); @@ -271,6 +272,20 @@ } } +void ScriptParser::readOverwriteSectionScript() { + const StringRef msg = + "overwrite section script can only contain SECTIONS commands " + "with one output section description"; + while (!atEOF() && !errorCount()) { + if (next() != "SECTIONS") + setError(msg); + expect("{"); + script->overwriteSections.push_back(readOutputSectionDescription(next())); + if (next() != "}") + setError(msg); + } +} + void ScriptParser::readDefsym(StringRef name) { if (errorCount()) return; @@ -588,7 +603,7 @@ StringRef where = next(); for (BaseCommand *cmd : v) if (auto *os = dyn_cast(cmd)) - script->insertCommands.push_back({os, isAfter, where}); + script->insertCommands.push_back({os->name, isAfter, where}); } void ScriptParser::readTarget() { @@ -1646,10 +1661,13 @@ return {flags, negFlags}; } -void elf::readLinkerScript(MemoryBufferRef mb) { +void elf::readLinkerScript(MemoryBufferRef mb, bool overwriteSection) { llvm::TimeTraceScope timeScope("Read linker script", mb.getBufferIdentifier()); - ScriptParser(mb).readLinkerScript(); + if (overwriteSection) + ScriptParser(mb).readOverwriteSectionScript(); + else + ScriptParser(mb).readLinkerScript(); } void elf::readVersionScript(MemoryBufferRef mb) { diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst --- a/lld/docs/ReleaseNotes.rst +++ b/lld/docs/ReleaseNotes.rst @@ -28,6 +28,8 @@ (`D102461 `_) * ``-Bno-symbolic`` has been added. (`D102461 `_) +* ``--overwrite-section-script`` has been added. + (`D103303 `_) Breaking changes ---------------- diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1 --- a/lld/docs/ld.lld.1 +++ b/lld/docs/ld.lld.1 @@ -417,6 +417,8 @@ .Pp .Cm place is the default. +.It Fl -overwrite-section-script Ns = Ns Ar file +Read a script containing SECTIONS commands that replace output section descriptions in the main or default linker script. .It Fl -pack-dyn-relocs Ns = Ns Ar format Pack dynamic relocations in the given format. .Ar format diff --git a/lld/test/ELF/linkerscript/insert-duplicate.test b/lld/test/ELF/linkerscript/insert-duplicate.test --- a/lld/test/ELF/linkerscript/insert-duplicate.test +++ b/lld/test/ELF/linkerscript/insert-duplicate.test @@ -1,7 +1,6 @@ # REQUIRES: x86 -## Test that we can handle cases where an output section is specified by multiple -## INSERT commands. Each output section description creates a new instance. -## A redundant description matches no input sections and thus is a no-op. +## Document the behavior when an output section is specified by multiple +## INSERT commands. It is discouraged in the real world. # RUN: llvm-mc -filetype=obj -triple=x86_64 %p/Inputs/insert-after.s -o %t.o # RUN: ld.lld -T %s %t.o -o %t @@ -9,16 +8,14 @@ # CHECK: Name Type Address Off # CHECK-NEXT: NULL 0000000000000000 000000 -# CHECK-NEXT: .text PROGBITS 00000000002011c8 0001c8 -# CHECK-NEXT: .foo.data PROGBITS 00000000002021d0 0001d0 -# CHECK-NEXT: .foo.text PROGBITS 00000000002031d8 0001d8 +# CHECK-NEXT: .text PROGBITS 0000000000201158 000158 +# CHECK-NEXT: .foo.text PROGBITS 0000000000201160 000160 +# CHECK-NEXT: .foo.data PROGBITS 0000000000202168 000168 # CHECK: Type # CHECK-NEXT: PHDR {{.*}} R # CHECK-NEXT: LOAD {{.*}} R # CHECK-NEXT: LOAD {{.*}} R E # CHECK-NEXT: LOAD {{.*}} RW -# CHECK-NEXT: LOAD {{.*}} R E -# CHECK-NEXT: LOAD {{.*}} RW # CHECK-NEXT: GNU_STACK {{.*}} RW ## First, move .foo.data after .foo.text @@ -27,6 +24,5 @@ ## Next, move .foo.text after .foo.data SECTIONS { .foo.text : { *(.foo.text) } } INSERT AFTER .foo.data; -## No-op. The .foo.data output section is a different instance and matches no -## input sections. +## Then, move .foo.data after .foo.text again. SECTIONS { .foo.data : { *(.foo.data) } } INSERT AFTER .foo.text; diff --git a/lld/test/ELF/linkerscript/overwrite-section-script-discard.test b/lld/test/ELF/linkerscript/overwrite-section-script-discard.test new file mode 100644 --- /dev/null +++ b/lld/test/ELF/linkerscript/overwrite-section-script-discard.test @@ -0,0 +1,30 @@ +# REQUIRES: x86 +# RUN: split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/a.s -o %t/a.o + +## /DISCARD/ specified by --overwrite-section-script can discard sections as well. + +# RUN: ld.lld --overwrite-section-script %t/overwrite.lds -T %t/main.lds %t/a.o -o %t1 2>&1 | count 0 +# RUN: llvm-readelf -S -l %t1 | FileCheck %s +# RUN: ld.lld --overwrite-section-script %t/overwrite.lds --overwrite-section-script %t/overwrite.lds -T %t/main.lds %t/a.o -o %t1 2>&1 | count 0 +# RUN: llvm-readelf -S -l %t1 | FileCheck %s + +# CHECK: Name +# CHECK-NOT: .data + +#--- a.s +.globl _start +_start: + +.section .data.1,"aw"; .byte 1 +.section .data.2,"aw"; .byte 2 + +#--- main.lds +SECTIONS { + /DISCARD/ : { *(.data.1) } +} + +#--- overwrite.lds +SECTIONS { + /DISCARD/ : { *(.data.2) } +} diff --git a/lld/test/ELF/linkerscript/overwrite-section-script.test b/lld/test/ELF/linkerscript/overwrite-section-script.test new file mode 100644 --- /dev/null +++ b/lld/test/ELF/linkerscript/overwrite-section-script.test @@ -0,0 +1,133 @@ +# REQUIRES: x86 +# RUN: rm -rf %t && split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/a.s -o %t/a.o + +## There is no main linker script. --overwrite-section-script defines output section +## descriptions and follows the usual orphan placement rules. + +# RUN: ld.lld --overwrite-section-script %t/overwrite1.lds %t/a.o -o %t1 +# RUN: llvm-readelf -S %t1 | FileCheck %s --check-prefix=CHECK1 + +# CHECK1: Name Type Address Off Size ES Flg Lk Inf Al +# CHECK1-NEXT: NULL [[#%x,]] [[#%x,]] 000000 +# CHECK1-NEXT: .text PROGBITS [[#%x,]] [[#%x,]] 000001 00 AX 0 0 4 +# CHECK1-NEXT: .foo.text PROGBITS [[#%x,]] [[#%x,]] 000002 00 WAX 0 0 8 +# CHECK1-NEXT: .data PROGBITS [[#%x,]] [[#%x,]] 000001 00 WA 0 0 1 +# CHECK1-NEXT: .comment PROGBITS {{.*}} + +# RUN: ld.lld --overwrite-section-script %t/overwrite1.lds --overwrite-section-script %t/overwrite1.lds %t/a.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=WARN1 + +# WARN1: warning: --overwrite-section-script specifies duplicate .foo.text + +## The output section description order (.foo.data .foo.text) does not affect +## the output order. + +# RUN: ld.lld --overwrite-section-script %t/overwrite2.lds %t/a.o -o %t2 +# RUN: llvm-readelf -S -s %t2 | FileCheck %s --check-prefix=CHECK2 + +# CHECK2: [Nr] Name Type Address Off Size ES Flg Lk Inf Al +# CHECK2-NEXT: [ 0] NULL [[#%x,]] [[#%x,]] 000000 +# CHECK2-NEXT: [ 1] .text PROGBITS [[#%x,]] [[#%x,]] 000001 00 AX 0 0 4 +# CHECK2-NEXT: [ 2] .foo.text PROGBITS [[#%x,]] [[#%x,]] 000001 00 AX 0 0 1 +# CHECK2-NEXT: [ 3] .data PROGBITS [[#%x,]] [[#%x,]] 000001 00 WA 0 0 1 +# CHECK2-NEXT: [ 4] .foo.data PROGBITS [[#%x,]] [[#%x,]] 000001 00 WA 0 0 1 +# CHECK2-NEXT: [ 5] .comment PROGBITS {{.*}} + +# CHECK2: Num: Value Size Type Bind Vis Ndx Name +# CHECK2: [[#]]: [[#%x,ADDR:]] 0 NOTYPE GLOBAL DEFAULT 4 FOO_DATA_START +# CHECK2-NEXT: [[#]]: {{0*}}[[#ADDR+1]] 0 NOTYPE GLOBAL DEFAULT 4 FOO_DATA_END +# CHECK2-NEXT: [[#]]: [[#%x,ADDR:]] 0 NOTYPE GLOBAL DEFAULT 2 FOO_TEXT_START +# CHECK2-NEXT: [[#]]: {{0*}}[[#ADDR+1]] 0 NOTYPE GLOBAL DEFAULT 2 FOO_TEXT_END + +## --overwrite-section-script is processed before the main script. The main script +## specifies the output order. The alignment of .foo.text is specified by +## --overwrite-section-script insteaad of the main script. + +# RUN: ld.lld -T %t/main.lds --overwrite-section-script %t/overwrite1.lds %t/a.o -o %t3 2>&1 | count 0 +# RUN: llvm-readelf -S %t3 | FileCheck %s --check-prefix=CHECK3 +# RUN: ld.lld --overwrite-section-script %t/overwrite1.lds -T %t/main.lds %t/a.o -o %t3 2>&1 | count 0 +# RUN: llvm-readelf -S %t3 | FileCheck %s --check-prefix=CHECK3 + +# CHECK3: Name Type Address Off Size ES Flg Lk Inf Al +# CHECK3-NEXT: NULL [[#%x,]] [[#%x,]] 000000 +# CHECK3-NEXT: .data PROGBITS [[#%x,]] [[#%x,]] 000001 00 WA 0 0 1 +# CHECK3-NEXT: .text PROGBITS [[#%x,]] [[#%x,]] 000001 00 AX 0 0 4 +# CHECK3-NEXT: .foo.text PROGBITS [[#%x,]] [[#%x,]] 000002 00 WAX 0 0 8 +# CHECK3-NEXT: .comment PROGBITS {{.*}} + +# RUN: ld.lld -T %t/main.lds --overwrite-section-script %t/overwrite1.lds %t/a.o -o %t3 --verbose 2>&1 | FileCheck %s --check-prefix=VERBOSE3 + +# VERBOSE3: .foo.text is overwritten by --overwrite-section-script to {{.*}}overwrite1.lds:[[#]] + +## If INSERT commands are specified, INSERT commands are processed after overwrite sections. +# RUN: ld.lld %t/insert.lds --overwrite-section-script %t/overwrite2.lds %t/a.o -o %t4 +# RUN: llvm-readelf -S -s %t4 | FileCheck %s --check-prefix=CHECK4 + +# CHECK4: [Nr] Name Type Address Off Size ES Flg Lk Inf Al +# CHECK4-NEXT: [ 0] NULL [[#%x,]] [[#%x,]] 000000 +# CHECK4-NEXT: [ 1] .text PROGBITS [[#%x,]] [[#%x,]] 000001 00 AX 0 0 4 +# CHECK4-NEXT: [ 2] .data PROGBITS [[#%x,]] [[#%x,]] 000001 00 WA 0 0 1 +# CHECK4-NEXT: [ 3] .foo.data PROGBITS [[#%x,]] [[#%x,]] 000001 00 WA 0 0 1 +# CHECK4-NEXT: [ 4] .foo.text PROGBITS [[#%x,]] [[#%x,]] 000001 00 AX 0 0 1 +# CHECK4-NEXT: [ 5] .comment PROGBITS {{.*}} + +# CHECK4: Num: Value Size Type Bind Vis Ndx Name +# CHECK4: [[#]]: [[#%x,ADDR:]] 0 NOTYPE GLOBAL DEFAULT 4 FOO_TEXT_START +# CHECK4-NEXT: [[#]]: {{0*}}[[#ADDR+1]] 0 NOTYPE GLOBAL DEFAULT 4 FOO_TEXT_END +# CHECK4-NEXT: [[#]]: [[#%x,ADDR:]] 0 NOTYPE GLOBAL DEFAULT 3 FOO_DATA_START +# CHECK4-NEXT: [[#]]: {{0*}}[[#ADDR+1]] 0 NOTYPE GLOBAL DEFAULT 3 FOO_DATA_END + + +#--- a.s +.globl _start +_start: + +.section .foo.text,"ax"; .byte 1 +.section .foo.data,"aw"; .byte 2 +.section .text.1,"ax"; .byte 3 +.section .data.1,"aw"; .byte 4 + +#--- main.lds +SECTIONS { + .data : { *(.data*) } + .foo.data : { *(.foo.data) } + .text : { *(.text*) } + .foo.text : ALIGN(16) { *(.foo.text) } +} + +#--- overwrite1.lds +SECTIONS { + .foo.text : ALIGN(8) { *(.foo.data .foo.text) } +} + +#--- overwrite2.lds +SECTIONS { + .foo.data : { FOO_DATA_START = .; *(.foo.data) FOO_DATA_END = .; } +} +SECTIONS { + .foo.text : { FOO_TEXT_START = .; *(.foo.text) FOO_TEXT_END = .; } +} + +#--- insert.lds +SECTIONS { .foo.text : {} } INSERT AFTER .foo.data; + +#--- err1.lds +# RUN: not ld.lld --overwrite-section-script %t/err1.lds %t/a.o -o /dev/null 2>&1 | FileCheck %s -DFILE=%t/err1.lds --check-prefix=ERR1 +# ERR1: error: [[FILE]]:[[#@LINE+1]]: overwrite section script can only contain SECTIONS commands with one output section description +A = 1; + +#--- err2.lds +# RUN: not ld.lld --overwrite-section-script %t/err2.lds %t/a.o -o /dev/null 2>&1 | FileCheck %s -DFILE=%t/err2.lds --check-prefix=ERR2 +# ERR2: error: [[FILE]]:[[#@LINE+3]]: overwrite section script can only contain SECTIONS commands with one output section description +SECTIONS { + .foo.data : {} + .foo.text : {} +} + +#--- err3.lds +## TODO Fix the diagnostic 'malformed number' +# RUN: not ld.lld --overwrite-section-script %t/err3.lds %t/a.o -o /dev/null 2>&1 | FileCheck %s -DFILE=%t/err3.lds --check-prefix=ERR3 +# ERR3: error: [[FILE]]:[[#@LINE+2]]: malformed number: = +SECTIONS { + A = 1; +}