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, false); return; case file_magic::archive: { // Handle -whole-archive. @@ -1428,9 +1428,11 @@ break; } case OPT_script: + case OPT_overwrite_script: if (Optional path = searchScript(arg->getValue())) { if (Optional mb = readFile(*path)) - readLinkerScript(*mb); + readLinkerScript(*mb, + arg->getOption().getID() == OPT_overwrite_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 @@ -343,6 +343,9 @@ // to be reordered. std::vector insertCommands; + // OutputSections specified by --overwrite-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 @@ -547,52 +547,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-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-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-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-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,7 @@ MetaVarName<"">; defm script: Eq<"script", "Read linker script">; +defm overwrite_script: EEq<"overwrite-script", "Read a script which overrides the main 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 overwrite); // 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 readOverwriteScript(); void readVersionScript(); void readDynamicList(); void readDefsym(StringRef name); @@ -271,6 +272,16 @@ } } +void ScriptParser::readOverwriteScript() { + while (!atEOF() && !errorCount()) { + expect("SECTIONS"); + expect("{"); + if (!errorCount()) + script->overwriteSections.push_back(readOutputSectionDescription(next())); + expect("}"); + } +} + void ScriptParser::readDefsym(StringRef name) { if (errorCount()) return; @@ -1646,10 +1657,13 @@ return {flags, negFlags}; } -void elf::readLinkerScript(MemoryBufferRef mb) { +void elf::readLinkerScript(MemoryBufferRef mb, bool overwrite) { llvm::TimeTraceScope timeScope("Read linker script", mb.getBufferIdentifier()); - ScriptParser(mb).readLinkerScript(); + if (overwrite) + ScriptParser(mb).readOverwriteScript(); + else + ScriptParser(mb).readLinkerScript(); } void elf::readVersionScript(MemoryBufferRef mb) { diff --git a/lld/test/ELF/linkerscript/overwrite-script-discard.test b/lld/test/ELF/linkerscript/overwrite-script-discard.test new file mode 100644 --- /dev/null +++ b/lld/test/ELF/linkerscript/overwrite-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-script can discard sections as well. + +# RUN: ld.lld --overwrite-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-script %t/overwrite.lds --overwrite-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-script.test b/lld/test/ELF/linkerscript/overwrite-script.test new file mode 100644 --- /dev/null +++ b/lld/test/ELF/linkerscript/overwrite-script.test @@ -0,0 +1,106 @@ +# 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-script defines output section +## descriptions and follows the usual orphan placement rules. + +# RUN: ld.lld --overwrite-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-script %t/overwrite1.lds --overwrite-script %t/overwrite1.lds %t/a.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=WARN1 + +# WARN1: warning: --overwrite-script specifies duplicate .foo.text + +## The output section description order (.foo.data .foo.text) does not affect +## the output order. + +# RUN: ld.lld --overwrite-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-script is processed before the main script. The main script +## specifies the output order. The alignment of .foo.text is specified by +## --overwrite-script insteaad of the main script. + +# RUN: ld.lld -T %t/main.lds --overwrite-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-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-script %t/overwrite1.lds %t/a.o -o %t3 --verbose 2>&1 | FileCheck %s --check-prefix=VERBOSE3 + +# VERBOSE3: .foo.text is overwritten by --overwrite-script to {{.*}}overwrite1.lds:[[#]] + +#--- 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 = .; } +} + +#--- err1.lds +# RUN: not ld.lld --overwrite-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+3]]: } expected, but got .foo.text +SECTIONS { + .foo.data : {} + .foo.text : {} +} + +#--- err2.lds +## TODO: Fix the diagnostic 'malformed number' +# RUN: not ld.lld --overwrite-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+2]]: malformed number: = +SECTIONS { + A = 1; +}