Skip to content

Commit

Permalink
[LinkerScript] Implement MEMORY command
Browse files Browse the repository at this point in the history
As specified here:

* https://sourceware.org/binutils/docs/ld/MEMORY.html#MEMORY

There are two deviations from what is specified for GNU ld:

  1. Only integer constants and *not* constant expressions
     are allowed in `LENGTH` and `ORIGIN` initializations.

  2. The `I` and `L` attributes are *not* implemented.

With (1) there is currently no easy way to evaluate integer
only constant expressions.  This can be enhanced in the
future.

With (2) it isn't clear how these flags map to the `SHF_*`
flags or if they even make sense for an ELF linker.

Differential Revision: https://reviews.llvm.org/D28911

llvm-svn: 292875
  • Loading branch information
meadori committed Jan 24, 2017
1 parent e1a5630 commit b889744
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 1 deletion.
141 changes: 140 additions & 1 deletion lld/ELF/LinkerScript.cpp
Expand Up @@ -409,6 +409,19 @@ template <class ELFT> void LinkerScript<ELFT>::output(InputSection<ELFT> *S) {
// .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->Offset += CurOutSec->Size;
uint64_t CurSize = CurMemRegion->Offset - 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
Expand Down Expand Up @@ -508,6 +521,42 @@ findSections(StringRef Name, const std::vector<OutputSectionBase *> &Sections) {
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 <class ELFT>
MemoryRegion *LinkerScript<ELFT>::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;
}

// The memory region name is empty, thus a suitable region must be
// searched for in the region map. If the region map is empty, just
// return. Note that this check doesn't happen at the very beginning
// so that uses of undeclared regions can be caught.
if (!Opt.MemoryRegions.size())
return nullptr;

// 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)
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 <class ELFT>
Expand All @@ -518,7 +567,13 @@ void LinkerScript<ELFT>::assignOffsets(OutputSectionCommand *Cmd) {
findSections<ELFT>(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->Offset;
switchTo(Sec);

// Find the last section output location. We will output orphan sections
// there so that end symbols point to the correct location.
Expand Down Expand Up @@ -961,6 +1016,8 @@ class elf::ScriptParser final : public ScriptParserBase {
void readExtern();
void readGroup();
void readInclude();
void readMemory();
std::pair<uint32_t, uint32_t> readMemoryAttributes();
void readOutput();
void readOutputArch();
void readOutputFormat();
Expand Down Expand Up @@ -1058,6 +1115,8 @@ void ScriptParser::readLinkerScript() {
readGroup();
} else if (Tok == "INCLUDE") {
readInclude();
} else if (Tok == "MEMORY") {
readMemory();
} else if (Tok == "OUTPUT") {
readOutput();
} else if (Tok == "OUTPUT_ARCH") {
Expand Down Expand Up @@ -1453,6 +1512,10 @@ ScriptParser::readOutputSectionDescription(StringRef OutSec) {
setError("unknown command " + Tok);
}
}

if (consume(">"))
Cmd->MemoryRegionName = next();

Cmd->Phdrs = readOutputSectionPhdrs();

if (consume("="))
Expand Down Expand Up @@ -1937,6 +2000,82 @@ std::vector<SymbolVersion> ScriptParser::readVersionExtern() {
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<uint32_t, uint32_t> 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();
}
Expand Down
21 changes: 21 additions & 0 deletions lld/ELF/LinkerScript.h
Expand Up @@ -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"
Expand Down Expand Up @@ -127,6 +128,7 @@ struct OutputSectionCommand : BaseCommand {
uint32_t Filler = 0;
ConstraintKind Constraint = ConstraintKind::NoConstraint;
std::string Location;
std::string MemoryRegionName;
};

// This struct represents one section match pattern in SECTIONS() command.
Expand Down Expand Up @@ -187,6 +189,18 @@ struct PhdrsCommand {
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 Offset;
uint32_t Flags;
uint32_t NotFlags;
};

class LinkerScriptBase {
protected:
~LinkerScriptBase() = default;
Expand Down Expand Up @@ -215,6 +229,9 @@ struct ScriptConfiguration {
// List of section patterns specified with KEEP commands. They will
// be kept even if they are unused and --gc-sections is specified.
std::vector<InputSectionDescription *> KeptSections;

// A map from memory region name to a memory region descriptor.
llvm::DenseMap<llvm::StringRef, MemoryRegion> MemoryRegions;
};

extern ScriptConfiguration *ScriptConfig;
Expand Down Expand Up @@ -273,9 +290,13 @@ template <class ELFT> class LinkerScript final : public LinkerScriptBase {
std::vector<size_t> 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();
Expand Down
140 changes: 140 additions & 0 deletions lld/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

0 comments on commit b889744

Please sign in to comment.