diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp --- a/lld/ELF/LinkerScript.cpp +++ b/lld/ELF/LinkerScript.cpp @@ -833,6 +833,7 @@ if (!(sec->flags & SHF_ALLOC)) dot = 0; + bool prevLMARegionIsDefault = ctx->lmaRegion == nullptr; ctx->memRegion = sec->memRegion; ctx->lmaRegion = sec->lmaRegion; if (ctx->memRegion) @@ -851,19 +852,19 @@ 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; diff --git a/lld/docs/ELF/linker_script.rst b/lld/docs/ELF/linker_script.rst --- a/lld/docs/ELF/linker_script.rst +++ b/lld/docs/ELF/linker_script.rst @@ -51,3 +51,27 @@ 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. diff --git a/lld/test/ELF/linkerscript/at4.s b/lld/test/ELF/linkerscript/at4.s deleted file mode 100644 --- a/lld/test/ELF/linkerscript/at4.s +++ /dev/null @@ -1,28 +0,0 @@ -# REQUIRES: x86 -# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o -# RUN: echo "SECTIONS { \ -# RUN: . = 0x1000; \ -# RUN: .aaa : { *(.aaa) } \ -# RUN: .bbb : AT(0x2008) { *(.bbb) } \ -# RUN: .ccc : { *(.ccc) } \ -# RUN: }" > %t.script -# RUN: ld.lld %t.o --script %t.script -o %t -# RUN: llvm-readelf -l %t | FileCheck %s - -# CHECK: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align -# CHECK-NEXT: LOAD 0x001000 0x0000000000001000 0x0000000000001000 0x000008 0x000008 R 0x1000 -# CHECK-NEXT: LOAD 0x001008 0x0000000000001008 0x0000000000002008 0x000010 0x000010 R 0x1000 -# CHECK-NEXT: LOAD 0x001018 0x0000000000001018 0x0000000000001018 0x000001 0x000001 R E 0x1000 - -.global _start -_start: - nop - -.section .aaa, "a" -.quad 0 - -.section .bbb, "a" -.quad 0 - -.section .ccc, "a" -.quad 0 diff --git a/lld/test/ELF/linkerscript/lma-offset.s b/lld/test/ELF/linkerscript/lma-offset.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/linkerscript/lma-offset.s @@ -0,0 +1,37 @@ +# REQUIRES: x86 +# 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) } +} diff --git a/lld/test/ELF/linkerscript/loadaddr.s b/lld/test/ELF/linkerscript/loadaddr.s --- a/lld/test/ELF/linkerscript/loadaddr.s +++ b/lld/test/ELF/linkerscript/loadaddr.s @@ -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 diff --git a/lld/test/ELF/linkerscript/map-file2.test b/lld/test/ELF/linkerscript/map-file2.test --- a/lld/test/ELF/linkerscript/map-file2.test +++ b/lld/test/ELF/linkerscript/map-file2.test @@ -32,10 +32,10 @@ # 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 :(.comment) # CHECK-NEXT: 0 0 48 8 .symtab diff --git a/lld/test/ELF/linkerscript/overlay.test b/lld/test/ELF/linkerscript/overlay.test --- a/lld/test/ELF/linkerscript/overlay.test +++ b/lld/test/ELF/linkerscript/overlay.test @@ -28,4 +28,4 @@ # 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