Index: ELF/LinkerScript.h =================================================================== --- ELF/LinkerScript.h +++ ELF/LinkerScript.h @@ -15,6 +15,7 @@ #include "Writer.h" #include "lld/Core/LLVM.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" @@ -127,6 +128,7 @@ uint32_t Filler = 0; ConstraintKind Constraint = ConstraintKind::NoConstraint; std::string Location; + std::string MemoryRegionName; }; // This struct represents one section match pattern in SECTIONS() command. @@ -187,6 +189,18 @@ Expr LMAExpr; }; +// This struct is used to represent the location and size of regions of +// target memory. Instances of the struct are created by parsing the +// MEMORY command. +struct MemoryRegion { + std::string Name; + uint64_t Origin; + uint64_t Length; + uint64_t Index; + uint32_t Flags; + uint32_t NotFlags; +}; + class LinkerScriptBase { protected: ~LinkerScriptBase() = default; @@ -215,6 +229,9 @@ // List of section patterns specified with KEEP commands. They will // be kept even if they are unused and --gc-sections is specified. std::vector KeptSections; + + // A map from memory region name to a memory region descriptor. + llvm::DenseMap MemoryRegions; }; extern ScriptConfiguration *ScriptConfig; @@ -273,9 +290,13 @@ std::vector getPhdrIndices(StringRef SectionName); size_t getPhdrIndex(const Twine &Loc, StringRef PhdrName); + MemoryRegion *findMemoryRegion(OutputSectionCommand *Cmd, + OutputSectionBase *Sec); + uintX_t Dot; uintX_t LMAOffset = 0; OutputSectionBase *CurOutSec = nullptr; + MemoryRegion *CurMemRegion = nullptr; uintX_t ThreadBssOffset = 0; void switchTo(OutputSectionBase *Sec); void flush(); Index: ELF/LinkerScript.cpp =================================================================== --- ELF/LinkerScript.cpp +++ ELF/LinkerScript.cpp @@ -409,6 +409,19 @@ // .foo { *(.aaa) a = SIZEOF(.foo); *(.bbb) } CurOutSec->Size = Pos - CurOutSec->Addr; + // If there is a memory region associated with this input section, then + // place the section in that region and update the region index. + if (CurMemRegion) { + CurMemRegion->Index += CurOutSec->Size; + uint64_t CurSize = CurMemRegion->Index - CurMemRegion->Origin; + if (CurSize > CurMemRegion->Length) { + uint64_t OverflowAmt = CurSize - CurMemRegion->Length; + error("section '" + CurOutSec->Name + "' will not fit in region '" + + CurMemRegion->Name + "': overflowed by " + Twine(OverflowAmt) + + " bytes"); + } + } + if (IsTbss) ThreadBssOffset = Pos - Dot; else @@ -508,6 +521,38 @@ return Ret; } +// This function searches for a memory region to place the given output +// section in. If found, a pointer to the appropriate memory region is +// returned. Otherwise, a nullptr is returned. +template +MemoryRegion *LinkerScript::findMemoryRegion(OutputSectionCommand *Cmd, + OutputSectionBase *Sec) { + // If a memory region name was specified in the output section command, + // then try to find that region first. + if (!Cmd->MemoryRegionName.empty()) { + auto It = Opt.MemoryRegions.find(Cmd->MemoryRegionName); + if (It != Opt.MemoryRegions.end()) + return &It->second; + error("memory region '" + Cmd->MemoryRegionName + "' not declared"); + return nullptr; + } + + if (!Opt.MemoryRegions.size()) + return nullptr; + + // Next, see if a region can be found by matching section flags. + for (auto &MRI : Opt.MemoryRegions) { + MemoryRegion &MR = MRI.second; + if ((MR.Flags & Sec->Flags) != 0 && (MR.NotFlags & Sec->Flags) == 0) + return &MR; + } + + // Otherwise, no suitable region was found. + if ((Sec->Flags & SHF_ALLOC) != 0) + error("no memory region specified for section '" + Sec->Name + "'"); + return nullptr; +} + // This function assigns offsets to input sections and an output section // for a single sections command (e.g. ".text { *(.text); }"). template @@ -518,7 +563,13 @@ findSections(Cmd->Name, *OutputSections); if (Sections.empty()) return; - switchTo(Sections[0]); + + OutputSectionBase *Sec = Sections[0]; + // Try and find an appropriate memory region to assign offsets in. + CurMemRegion = findMemoryRegion(Cmd, Sec); + if (CurMemRegion) + Dot = CurMemRegion->Index; + switchTo(Sec); // Find the last section output location. We will output orphan sections // there so that end symbols point to the correct location. @@ -961,6 +1012,8 @@ void readExtern(); void readGroup(); void readInclude(); + void readMemory(); + std::pair readMemoryAttributes(); void readOutput(); void readOutputArch(); void readOutputFormat(); @@ -1058,6 +1111,8 @@ readGroup(); } else if (Tok == "INCLUDE") { readInclude(); + } else if (Tok == "MEMORY") { + readMemory(); } else if (Tok == "OUTPUT") { readOutput(); } else if (Tok == "OUTPUT_ARCH") { @@ -1453,6 +1508,10 @@ setError("unknown command " + Tok); } } + + if (consume(">")) + Cmd->MemoryRegionName = next(); + Cmd->Phdrs = readOutputSectionPhdrs(); if (consume("=")) @@ -1937,6 +1996,82 @@ return Ret; } +// Parse the MEMORY command As specified in: +// https://sourceware.org/binutils/docs/ld/MEMORY.html#MEMORY +void ScriptParser::readMemory() { + expect("{"); + while (!Error && !consume("}")) { + StringRef Name = next(); + uint32_t Flags = 0; + uint32_t NotFlags = 0; + if (consume("(")) { + std::tie(Flags, NotFlags) = readMemoryAttributes(); + expect(")"); + } + expect(":"); + + // Parse the ORIGIN. + if (!(consume("ORIGIN") || consume("org") || consume("o"))) { + setError("expected one of: ORIGIN, org, or o"); + return; + } + expect("="); + uint64_t Origin; + // TODO: Fully support constant expressions. + if (!readInteger(next(), Origin)) { + setError("nonconstant expression for origin"); + return; + } + expect(","); + + // Parse the LENGTH. + if (!(consume("LENGTH") || consume("len") || consume("l"))) { + setError("expected one of: LENGTH, len, or l"); + return; + } + expect("="); + uint64_t Length; + // TODO: Fully support constant expressions. + if (!readInteger(next(), Length)) { + setError("nonconstant expression for length"); + return; + } + // Add the memory region to the region map (if it doesn't already exist). + auto It = Opt.MemoryRegions.find(Name); + if (It != Opt.MemoryRegions.end()) + setError("region '" + Name + "' already defined"); + else + Opt.MemoryRegions[Name] = {Name, Origin, Length, Origin, Flags, NotFlags}; + } +} + +// This function parses the attributes used to match against section +// flags when placing output sections in a memory region. These flags +// are only used when an explicit memory region name is not used. +std::pair ScriptParser::readMemoryAttributes() { + uint32_t Flags = 0; + uint32_t NotFlags = 0; + bool Invert = false; + for (char C : next()) { + uint32_t Flag = 0; + if (C == '!') + Invert = !Invert; + else if (tolower(C) == 'w') + Flag = SHF_WRITE; + else if (tolower(C) == 'x') + Flag = SHF_EXECINSTR; + else if (tolower(C) == 'a') + Flag = SHF_ALLOC; + else if (tolower(C) != 'r') + setError("invalid memory region attribute"); + if (Invert) + NotFlags |= Flag; + else + Flags |= Flag; + } + return {Flags, NotFlags}; +} + void elf::readLinkerScript(MemoryBufferRef MB) { ScriptParser(MB).readLinkerScript(); } Index: test/ELF/linkerscript/memory.s =================================================================== --- /dev/null +++ test/ELF/linkerscript/memory.s @@ -0,0 +1,140 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t + +## Check simple RAM-only memory region. + +# RUN: echo "MEMORY { \ +# RUN: ram (rwx) : ORIGIN = 0x8000, LENGTH = 256K \ +# RUN: } \ +# RUN: SECTIONS { \ +# RUN: .text : { \ +# RUN: *(.text) \ +# RUN: } > ram \ +# RUN: .data : { \ +# RUN: *(.data) \ +# RUN: } > ram \ +# RUN: }" > %t.script +# RUN: ld.lld -o %t1 --script %t.script %t +# RUN: llvm-objdump -section-headers %t1 | FileCheck -check-prefix=RAM %s + +# RAM: 1 .text 00000001 0000000000008000 TEXT DATA +# RAM-NEXT: 2 .data 00001000 0000000000008001 DATA + +## Check RAM and ROM memory regions. + +# RUN: echo "MEMORY { \ +# RUN: ram (rwx) : ORIGIN = 0x0, LENGTH = 1024M \ +# RUN: rom (rx) : org = 0x80000000, len = 64M \ +# RUN: } \ +# RUN: SECTIONS { \ +# RUN: .text : { \ +# RUN: *(.text) \ +# RUN: } > rom \ +# RUN: .data : { \ +# RUN: *(.data) \ +# RUN: } > ram \ +# RUN: }" > %t.script +# RUN: ld.lld -o %t1 --script %t.script %t +# RUN: llvm-objdump -section-headers %t1 | FileCheck -check-prefix=RAMROM %s + +# RAMROM: 1 .text 00000001 0000000080000000 TEXT DATA +# RAMROM-NEXT: 2 .data 00001000 0000000000000000 DATA + +## Check memory region placement by attributes. + +# RUN: echo "MEMORY { \ +# RUN: ram (!rx) : ORIGIN = 0x0, LENGTH = 1024M \ +# RUN: rom (rx) : o = 0x80000000, l = 64M \ +# RUN: } \ +# RUN: SECTIONS { \ +# RUN: .text : { \ +# RUN: *(.text) \ +# RUN: } \ +# RUN: .data : { \ +# RUN: *(.data) \ +# RUN: } > ram \ +# RUN: }" > %t.script +# RUN: ld.lld -o %t1 --script %t.script %t +# RUN: llvm-objdump -section-headers %t1 | FileCheck -check-prefix=ATTRS %s + +# ATTRS: 1 .text 00000001 0000000080000000 TEXT DATA +# ATTRS: 2 .data 00001000 0000000000000000 DATA + +## Check bad `ORIGIN`. + +# RUN: echo "MEMORY { ram (rwx) : ORIGI = 0x8000, LENGTH = 256K } }" > %t.script +# RUN: not ld.lld -o %t2 --script %t.script %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR1 %s +# ERR1: {{.*}}.script:1: expected one of: ORIGIN, org, or o + +## Check bad `LENGTH`. + +# RUN: echo "MEMORY { ram (rwx) : ORIGIN = 0x8000, LENTH = 256K } }" > %t.script +# RUN: not ld.lld -o %t2 --script %t.script %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR2 %s +# ERR2: {{.*}}.script:1: expected one of: LENGTH, len, or l + +## Check duplicate regions. + +# RUN: echo "MEMORY { ram (rwx) : o = 0x8, l = 256K ram (rx) : o = 0x0, l = 256K }" > %t.script +# RUN: not ld.lld -o %t2 --script %t.script %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR3 %s +# ERR3: {{.*}}.script:1: region 'ram' already defined + +## Check no region available. + +# RUN: echo "MEMORY { \ +# RUN: ram (!rx) : ORIGIN = 0x8000, LENGTH = 256K \ +# RUN: } \ +# RUN: SECTIONS { \ +# RUN: .text : { \ +# RUN: *(.text) \ +# RUN: } \ +# RUN: .data : { \ +# RUN: *(.data) \ +# RUN: } > ram \ +# RUN: }" > %t.script +# RUN: not ld.lld -o %t2 --script %t.script %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR4 %s +# ERR4: {{.*}}: no memory region specified for section '.text' + +## Check undeclared region. + +# RUN: echo "SECTIONS { .text : { *(.text) } > ram }" > %t.script +# RUN: not ld.lld -o %t2 --script %t.script %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR5 %s +# ERR5: {{.*}}: memory region 'ram' not declared + +## Check region overflow. + +# RUN: echo "MEMORY { \ +# RUN: ram (rwx) : ORIGIN = 0x0, LENGTH = 2K \ +# RUN: } \ +# RUN: SECTIONS { \ +# RUN: .text : { \ +# RUN: *(.text) \ +# RUN: } > ram \ +# RUN: .data : { \ +# RUN: *(.data) \ +# RUN: } > ram \ +# RUN: }" > %t.script +# RUN: not ld.lld -o %t2 --script %t.script %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR6 %s +# ERR6: {{.*}}: section '.data' will not fit in region 'ram': overflowed by 2049 bytes + +## Check invalid region attributes. + +# RUN: echo "MEMORY { ram (abc) : ORIGIN = 0x8000, LENGTH = 256K } }" > %t.script +# RUN: not ld.lld -o %t2 --script %t.script %t 2>&1 \ +# RUN: | FileCheck -check-prefix=ERR7 %s +# ERR7: {{.*}}.script:1: invalid memory region attribute + +.text +.global _start +_start: + nop + +.data +b: + .long 1 + .zero 4092