Skip to content

Commit

Permalink
[ELF] Postpone evaluation of ORIGIN/LENGTH in a MEMORY command
Browse files Browse the repository at this point in the history
```
createFiles(args)
 readDefsym
 readerLinkerScript(*mb)
  ...
   readMemory
    readMemoryAssignment("ORIGIN", "org", "o") // eagerly evaluated
target = getTarget();
link(args)
 writeResult<ELFT>()
  ...
   finalizeSections()
    script->processSymbolAssignments()
     addSymbol(cmd) // with this patch, evaluated here
```

readMemoryAssignment eagerly evaluates ORIGIN/LENGTH and returns an uint64_t.
This patch postpones the evaluation to make

* --defsym and symbol assignments
* `CONSTANT(COMMONPAGESIZE)` (requires a non-null `lld::elf::target`)

work. If the expression somehow requires interaction with memory
regions, the circular dependency may cause the expression to evaluate to
a strange value. See the new test added to memory-err.s

Reviewed By: grimar

Differential Revision: https://reviews.llvm.org/D75763
  • Loading branch information
MaskRay committed Mar 9, 2020
1 parent 7b66160 commit 92b5b98
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 26 deletions.
9 changes: 5 additions & 4 deletions lld/ELF/LinkerScript.cpp
Expand Up @@ -103,10 +103,11 @@ OutputSection *LinkerScript::getOrCreateOutputSection(StringRef name) {
static void expandMemoryRegion(MemoryRegion *memRegion, uint64_t size,
StringRef regionName, StringRef secName) {
memRegion->curPos += size;
uint64_t newSize = memRegion->curPos - memRegion->origin;
if (newSize > memRegion->length)
uint64_t newSize = memRegion->curPos - (memRegion->origin)().getValue();
uint64_t length = (memRegion->length)().getValue();
if (newSize > length)
error("section '" + secName + "' will not fit in region '" + regionName +
"': overflowed by " + Twine(newSize - memRegion->length) + " bytes");
"': overflowed by " + Twine(newSize - length) + " bytes");
}

void LinkerScript::expandMemoryRegions(uint64_t size) {
Expand Down Expand Up @@ -1101,7 +1102,7 @@ void LinkerScript::allocateHeaders(std::vector<PhdrEntry *> &phdrs) {
LinkerScript::AddressState::AddressState() {
for (auto &mri : script->memoryRegions) {
MemoryRegion *mr = mri.second;
mr->curPos = mr->origin;
mr->curPos = (mr->origin)().getValue();
}
}

Expand Down
6 changes: 3 additions & 3 deletions lld/ELF/LinkerScript.h
Expand Up @@ -126,14 +126,14 @@ enum class ConstraintKind { NoConstraint, ReadOnly, ReadWrite };
// target memory. Instances of the struct are created by parsing the
// MEMORY command.
struct MemoryRegion {
MemoryRegion(StringRef name, uint64_t origin, uint64_t length, uint32_t flags,
MemoryRegion(StringRef name, Expr origin, Expr length, uint32_t flags,
uint32_t negFlags)
: name(std::string(name)), origin(origin), length(length), flags(flags),
negFlags(negFlags) {}

std::string name;
uint64_t origin;
uint64_t length;
Expr origin;
Expr length;
uint32_t flags;
uint32_t negFlags;
uint64_t curPos = 0;
Expand Down
18 changes: 9 additions & 9 deletions lld/ELF/ScriptParser.cpp
Expand Up @@ -108,7 +108,7 @@ class ScriptParser final : ScriptLexer {
Expr readConstant();
Expr getPageSize();

uint64_t readMemoryAssignment(StringRef, StringRef, StringRef);
Expr readMemoryAssignment(StringRef, StringRef, StringRef);
std::pair<uint32_t, uint32_t> readMemoryAttributes();

Expr combine(StringRef op, Expr l, Expr r);
Expand Down Expand Up @@ -1302,7 +1302,7 @@ Expr ScriptParser::readPrimary() {
setError("memory region not defined: " + name);
return [] { return 0; };
}
return [=] { return script->memoryRegions[name]->length; };
return script->memoryRegions[name]->length;
}
if (tok == "LOADADDR") {
StringRef name = readParenLiteral();
Expand All @@ -1329,7 +1329,7 @@ Expr ScriptParser::readPrimary() {
setError("memory region not defined: " + name);
return [] { return 0; };
}
return [=] { return script->memoryRegions[name]->origin; };
return script->memoryRegions[name]->origin;
}
if (tok == "SEGMENT_START") {
expect("(");
Expand Down Expand Up @@ -1519,14 +1519,14 @@ std::vector<SymbolVersion> ScriptParser::readVersionExtern() {
return ret;
}

uint64_t ScriptParser::readMemoryAssignment(StringRef s1, StringRef s2,
StringRef s3) {
Expr ScriptParser::readMemoryAssignment(StringRef s1, StringRef s2,
StringRef s3) {
if (!consume(s1) && !consume(s2) && !consume(s3)) {
setError("expected one of: " + s1 + ", " + s2 + ", or " + s3);
return 0;
return [] { return 0; };
}
expect("=");
return readExpr()().getValue();
return readExpr();
}

// Parse the MEMORY command as specified in:
Expand All @@ -1550,9 +1550,9 @@ void ScriptParser::readMemory() {
}
expect(":");

uint64_t origin = readMemoryAssignment("ORIGIN", "org", "o");
Expr origin = readMemoryAssignment("ORIGIN", "org", "o");
expect(",");
uint64_t length = readMemoryAssignment("LENGTH", "len", "l");
Expr length = readMemoryAssignment("LENGTH", "len", "l");

// Add the memory region to the region map.
MemoryRegion *mr = make<MemoryRegion>(tok, origin, length, flags, negFlags);
Expand Down
23 changes: 13 additions & 10 deletions lld/test/ELF/linkerscript/memory-err.s
Expand Up @@ -52,19 +52,22 @@
# ERR7: {{.*}}.script:1: invalid memory region attribute

# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t
# RUN: echo "MEMORY { name : ORIGIN = DATA_SEGMENT_RELRO_END; }" > %t.script
# RUN: not ld.lld -shared -o /dev/null -T %t.script %t.o 2>&1 | FileCheck %s
# CHECK: error: {{.*}}.script:1: unable to calculate page size

# RUN: echo 'MEMORY { name : ORIGIN = CONSTANT(COMMONPAGESIZE); }' > %t.script
# RUN: not ld.lld -shared -o /dev/null -T %t.script %t.o 2>&1 | FileCheck --check-prefix=ERR_PAGESIZE %s
# ERR_PAGESIZE: error: {{.*}}.script:1: unable to calculate page size

# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t
# RUN: echo 'MEMORY { name : ORIGIN = .; }' > %t.script
# RUN: echo 'MEMORY { name : ORIGIN = ., LENGTH = 1 }' > %t.script
# RUN: not ld.lld -shared -o /dev/null -T %t.script %t.o 2>&1 | FileCheck --check-prefix=ERR_DOT %s
# ERR_DOT: error: {{.*}}.script:1: unable to get location counter value

## ORIGIN/LENGTH can be simple symbolic expressions. If the expression
## requires interaction with memory regions, it may fail.

# RUN: echo 'MEMORY { ram : ORIGIN = symbol, LENGTH = 4097 } \
# RUN: SECTIONS { \
# RUN: .text : { *(.text) } > ram \
# RUN: symbol = .; \
# RUN: .data : { *(.data) } > ram \
# RUN: }' > %t.script
# RUN: not ld.lld -T %t.script %t.o -o /dev/null 2>&1 | FileCheck --check-prefix=ERR_OVERFLOW %s
# ERR_OVERFLOW: error: section '.text' will not fit in region 'ram': overflowed by 18446744073709547518 bytes

nop

.data
Expand Down
26 changes: 26 additions & 0 deletions lld/test/ELF/linkerscript/memory.s
Expand Up @@ -46,6 +46,32 @@
# ATTRS: [ 1] .text PROGBITS 0000000080000000 001000 000001
# ATTRS-NEXT: [ 2] .data PROGBITS 0000000000000000 002000 001000

## ORIGIN/LENGTH support expressions with symbol assignments.
# RUN: echo 'MEMORY { ram : ORIGIN = symbol, LENGTH = 4097 } \
# RUN: SECTIONS { \
# RUN: .text : { *(.text) } > ram \
# RUN: .data : { *(.data) } > ram \
# RUN: }' > %t.script
# RUN: ld.lld -T %t.script %t --defsym symbol=0x5000 -o %t.relro
# RUN: llvm-readelf -S %t.relro | FileCheck --check-prefix=RELRO %s
# RUN: echo 'symbol = 0x5000;' > %t1.script
# RUN: ld.lld -T %t.script -T %t1.script %t -o %t.relro2
# RUN: llvm-readelf -S %t.relro2 | FileCheck --check-prefix=RELRO %s

# RELRO: [ 1] .text PROGBITS 0000000000005000 001000 000001
# RELRO-NEXT: [ 2] .data PROGBITS 0000000000005001 001001 001000

# RUN: echo 'MEMORY { ram : ORIGIN = CONSTANT(COMMONPAGESIZE), LENGTH = CONSTANT(COMMONPAGESIZE)+1 } \
# RUN: SECTIONS { \
# RUN: .text : { *(.text) } > ram \
# RUN: .data : { *(.data) } > ram \
# RUN: }' > %t.script
# RUN: ld.lld -T %t.script %t -o %t.pagesize
# RUN: llvm-readelf -S %t.pagesize | FileCheck --check-prefix=PAGESIZE %s

# PAGESIZE: [ 1] .text PROGBITS 0000000000001000 001000 000001
# PAGESIZE-NEXT: [ 2] .data PROGBITS 0000000000001001 001001 001000

.text
.global _start
_start:
Expand Down

0 comments on commit 92b5b98

Please sign in to comment.