Skip to content

Commit

Permalink
[ELF] Propagate LMA offset to sections with neither AT() nor AT>
Browse files Browse the repository at this point in the history
Fixes https://bugs.llvm.org/show_bug.cgi?id=45313
Also fixes linkerscript/{at4.s,overlay.test} LMA address issues exposed by
011b785.
Related: D74297

This patch improves emulation of GNU ld's heuristics on the difference
between the LMA and the VMA:
https://sourceware.org/binutils/docs/ld/Output-Section-LMA.html#Output-Section-LMA

New test linkerscript/lma-offset.s (based on at4.s) demonstrates some behaviors.

Reviewed By: psmith

Differential Revision: https://reviews.llvm.org/D76995
  • Loading branch information
MaskRay committed Apr 1, 2020
1 parent 3500cc8 commit bb4a36e
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 43 deletions.
19 changes: 10 additions & 9 deletions lld/ELF/LinkerScript.cpp
Expand Up @@ -833,6 +833,7 @@ void LinkerScript::assignOffsets(OutputSection *sec) {
if (!(sec->flags & SHF_ALLOC))
dot = 0;

bool prevLMARegionIsDefault = ctx->lmaRegion == nullptr;
ctx->memRegion = sec->memRegion;
ctx->lmaRegion = sec->lmaRegion;
if (ctx->memRegion)
Expand All @@ -851,19 +852,19 @@ void LinkerScript::assignOffsets(OutputSection *sec) {

switchTo(sec);

ctx->lmaOffset = 0;

// ctx->lmaOffset is LMA minus VMA. If LMA is explicitly specified via AT() or
// AT>, recompute ctx->lmaOffset; otherwise, if both previous/current LMA
// region is the default, reuse previous lmaOffset; otherwise, reset lmaOffset
// to 0. This emulates heuristics described in
// https://sourceware.org/binutils/docs/ld/Output-Section-LMA.html
if (sec->lmaExpr)
ctx->lmaOffset = sec->lmaExpr().getValue() - dot;
if (MemoryRegion *mr = sec->lmaRegion)
else if (MemoryRegion *mr = sec->lmaRegion)
ctx->lmaOffset = alignTo(mr->curPos, sec->alignment) - dot;
else if (!prevLMARegionIsDefault)
ctx->lmaOffset = 0;

// If neither AT nor AT> is specified for an allocatable section, the linker
// will set the LMA such that the difference between VMA and LMA for the
// section is the same as the preceding output section in the same region
// https://sourceware.org/binutils/docs-2.20/ld/Output-Section-LMA.html
// This, however, should only be done by the first "non-header" section
// in the segment.
// Propagate ctx->lmaOffset to the first "non-header" section.
if (PhdrEntry *l = ctx->outSec->ptLoad)
if (sec == findFirstSection(l))
l->lmaOffset = ctx->lmaOffset;
Expand Down
24 changes: 24 additions & 0 deletions lld/docs/ELF/linker_script.rst
Expand Up @@ -51,3 +51,27 @@ sh_addralign of an *OutputSection* *S* is the maximum of

When an *OutputSection* *S* has both ``address`` and ``ALIGN(section_align)``,
GNU ld will set sh_addralign to ``ALIGN(section_align)``.

Output section LMA
------------------

A load address (LMA) can be specified by ``AT(lma)`` or ``AT>lma_region``.

- ``AT(lma)`` specifies the exact load address. If the linker script does not
have a PHDRS command, then a new loadable segment will be generated.
- ``AT>lma_region`` specifies the LMA region. The lack of ``AT>lma_region``
means the default region is used. Note, GNU ld propagates the previous LMA
memory region when ``address`` is not specified. The LMA is set to the
current location of the memory region aligned to the section alignment.
If the linker script does not have a PHDRS command, then if
``lma_region`` is different from the ``lma_region`` for
the previous OutputSection a new loadable segment will be generated.

The two keywords cannot be specified at the same time.

If neither ``AT(lma)`` nor ``AT>lma_region`` is specified:

- If the previous section is also in the default LMA region, the difference
between the LMA and the VMA is computed to be the same as the previous
difference.
- Otherwise, the LMA is set to the VMA.
28 changes: 0 additions & 28 deletions lld/test/ELF/linkerscript/at4.s

This file was deleted.

39 changes: 39 additions & 0 deletions lld/test/ELF/linkerscript/lma-offset.s
@@ -0,0 +1,39 @@
# REQUIRES: x86
## Test the difference between the VMA and the LMA for sections with AT().

# RUN: echo '.globl _start; _start: ret; \
# RUN: .section .a,"a"; .byte 0; \
# RUN: .section .b,"a"; .byte 0; \
# RUN: .section .c,"a"; .byte 0; \
# RUN: .section .d,"a"; .byte 0; \
# RUN: .data; .byte 0' | \
# RUN: llvm-mc -filetype=obj -triple=x86_64 - -o %t.o
# RUN: ld.lld -T %s %t.o -o %t
# RUN: llvm-readelf -l %t | FileCheck %s

# CHECK: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
# CHECK-NEXT: LOAD 0x001000 0x0000000000001000 0x0000000000001000 0x000001 0x000001 R 0x1000

## .b has AT(). It starts a PT_LOAD segment which also includes .c
# CHECK-NEXT: LOAD 0x001001 0x0000000000001001 0x0000000000002005 0x000002 0x000002 R 0x1000

## .d has AT(). It starts a PT_LOAD segment, even if the difference between
## LMA and VMA (0x2007-0x1003) is the same as the previous one.
# CHECK-NEXT: LOAD 0x001003 0x0000000000001003 0x0000000000002007 0x000001 0x000001 R 0x1000

## The orphan section .text starts a PT_LOAD segment. The difference between
## LMA and VMA (0x2008-0x1004) remains the same
# CHECK-NEXT: LOAD 0x001004 0x0000000000001004 0x0000000000002008 0x000001 0x000001 R E 0x1000

## .data starts a PT_LOAD segment. The difference remains the same.
# CHECK-NEXT: LOAD 0x001005 0x0000000000001005 0x0000000000002009 0x000001 0x000001 RW 0x1000

SECTIONS {
. = 0x1000;
.a : { *(.a) }
.b : AT(0x2005) { *(.b) }
.c : { *(.c) }
.d : AT(0x2007) { *(.d) }
## Orphan section .text will be inserted here.
.data : { *(.data) }
}
2 changes: 1 addition & 1 deletion lld/test/ELF/linkerscript/loadaddr.s
Expand Up @@ -22,7 +22,7 @@
# CHECK-NEXT: 0000000000002008 g *ABS* 0000000000000000 bbb_lma
# CHECK-NEXT: 0000000000003000 g *ABS* 0000000000000000 ccc_lma
# CHECK-NEXT: 0000000000004000 g *ABS* 0000000000000000 ddd_lma
# CHECK-NEXT: 0000000000001020 g *ABS* 0000000000000000 txt_lma
# CHECK-NEXT: 0000000000004008 g *ABS* 0000000000000000 txt_lma
# ERROR: {{.*}}.script:1: undefined section .zzz

.global _start
Expand Down
8 changes: 4 additions & 4 deletions lld/test/ELF/linkerscript/map-file2.test
Expand Up @@ -32,10 +32,10 @@ SECTIONS {
# CHECK-NEXT: 1219 3209 8 1 {{.*}}{{/|\\}}map-file2.test.tmp.o:(.ddd)
# CHECK-NEXT: 1228 3218 34 8 .eh_frame
# CHECK-NEXT: 1228 3218 30 1 {{.*}}{{/|\\}}map-file2.test.tmp.o:(.eh_frame+0x0)
# CHECK-NEXT: 125c 125c 1 4 .text
# CHECK-NEXT: 125c 125c 1 4 {{.*}}{{/|\\}}map-file2.test.tmp.o:(.text)
# CHECK-NEXT: 125c 125c 0 1 f(int)
# CHECK-NEXT: 125c 125c 0 1 _start
# CHECK-NEXT: 125c 324c 1 4 .text
# CHECK-NEXT: 125c 324c 1 4 {{.*}}{{/|\\}}map-file2.test.tmp.o:(.text)
# CHECK-NEXT: 125c 324c 0 1 f(int)
# CHECK-NEXT: 125c 324c 0 1 _start
# CHECK-NEXT: 0 0 8 1 .comment
# CHECK-NEXT: 0 0 8 1 <internal>:(.comment)
# CHECK-NEXT: 0 0 48 8 .symtab
Expand Down
2 changes: 1 addition & 1 deletion lld/test/ELF/linkerscript/overlay.test
Expand Up @@ -28,4 +28,4 @@ SECTIONS {
# CHECK: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
# CHECK-NEXT: LOAD 0x001000 0x0000000000001000 0x0000000000004000 0x000008 0x000008 R 0x1000
# CHECK-NEXT: LOAD 0x002000 0x0000000000001000 0x0000000000004008 0x000004 0x000004 R 0x1000
# CHECK-NEXT: LOAD 0x002008 0x0000000000001008 0x0000000000001008 0x000001 0x000001 R E 0x1000
# CHECK-NEXT: LOAD 0x002008 0x0000000000001008 0x0000000000004010 0x000001 0x000001 R E 0x1000

0 comments on commit bb4a36e

Please sign in to comment.