[lld] [LLD] Allow linker scripts to override non-SHF_ALLOC->VMA=0 rule (resolve #202266) (PR #203788)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Jun 14 13:31:10 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lld-elf
Author: Sam Edwards (CFSworks)
<details>
<summary>Changes</summary>
As discussed in #<!-- -->202266, there are linker scripts that use non-SHF_ALLOC sections -- generally (ab)using `(OVERLAY)` -- to perform pointer arithmetic to plan the runtime memory layout via marker symbols. In those cases, forcibly putting the section at address zero causes miscalculations.
I can also imagine some hypothetical user might want to use `(OVERLAY)` to create overlays that should exist informationally in the file (for extraction by a later tool, or use at runtime by a self-modifying program inspecting the ELF) without the overlays being covered by PT_LOAD as an `OVERLAY { ... }` declaration would do. Those users would probably want the overlay to have a section VMA but not a LMA.
Therefore, this PR adds two exceptions to the non-SHF_ALLOC->VMA=0 rule:
- A section with an explicit start address is always at that explicit start address; the zero-address rule does not take precedence over the linker script.
- Perhaps more controversially, a non-SHF_ALLOC section that contains some symbol assignments (real symbols, not `. += ...;` expressions) is interpreted as evidence of the "planning memory layout" kind of nonalloc, and therefore intent that the section have a "real" address.
As @<!-- -->smithp35 pointed out, `(OVERLAY)` is kind of an obscure/obsolete way of doing overlays. And especially if #<!-- -->203524 is merged, an acceptable resolution might very well be "tell affected users to use `OVERLAY { ... }`" instead of merging this PR. However, the change is minor, so I'm sending it anyway. :)
The first two patches are small/nonfunctional cleanups I did while hacking on this, included as a courtesy. The third patch corrects a test that inadvertently would trigger this new behavior. The fourth patch is the actual implementation.
---
Full diff: https://github.com/llvm/llvm-project/pull/203788.diff
4 Files Affected:
- (modified) lld/ELF/LinkerScript.cpp (+24-9)
- (modified) lld/test/ELF/linkerscript/compress-sections.s (+2-4)
- (modified) lld/test/ELF/linkerscript/memory-nonalloc-no-warn.test (+3-3)
- (modified) lld/test/ELF/linkerscript/symbols-non-alloc.test (+11-3)
``````````diff
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index 0c1652b7e3a45..1ac01f3946ad6 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -1177,15 +1177,12 @@ bool LinkerScript::assignOffsets(OutputSection *sec) {
const bool isTbss = (sec->flags & SHF_TLS) && sec->type == SHT_NOBITS;
const bool sameMemRegion = state->memRegion == sec->memRegion;
const bool prevLMARegionIsDefault = state->lmaRegion == nullptr;
+ const uint64_t oldAddress = sec->addr;
const uint64_t savedDot = dot;
- bool addressChanged = false;
state->memRegion = sec->memRegion;
state->lmaRegion = sec->lmaRegion;
- if (!(sec->flags & SHF_ALLOC)) {
- // Non-SHF_ALLOC sections have zero addresses.
- dot = 0;
- } else if (isTbss) {
+ if (isTbss) {
// Allow consecutive SHF_TLS SHT_NOBITS output sections. The address range
// starts from the end address of the previous tbss section.
if (state->tbssAddr == 0)
@@ -1209,6 +1206,25 @@ bool LinkerScript::assignOffsets(OutputSection *sec) {
sec->name);
}
+ if (!(sec->flags & SHF_ALLOC)) {
+ // Non-SHF_ALLOC sections usually have zero addresses, with two exceptions:
+ // 1) Explicit address expressions are still respected.
+ // 2) The presence of symbol assignments (not to `.`) is considered user
+ // intent for the section to have an address.
+ bool hasAssign = false;
+ for (SectionCommand *cmd : sec->commands) {
+ if (auto *assign = dyn_cast<SymbolAssignment>(cmd)) {
+ if (assign->name != ".") {
+ hasAssign = true;
+ break;
+ }
+ }
+ }
+
+ if (!(hasSectionsCommand && sec->addrExpr) && !hasAssign)
+ dot = 0;
+ }
+
state->outSec = sec;
if (!(sec->addrExpr && hasSectionsCommand)) {
// ALIGN is respected. sec->alignment is the max of ALIGN and the maximum of
@@ -1217,7 +1233,6 @@ bool LinkerScript::assignOffsets(OutputSection *sec) {
dot = alignToPowerOf2(dot, sec->addralign);
expandMemoryRegions(dot - pos);
}
- addressChanged = sec->addr != dot;
sec->addr = dot;
// state->lmaOffset is LMA minus VMA. If LMA is explicitly specified via AT()
@@ -1227,12 +1242,12 @@ bool LinkerScript::assignOffsets(OutputSection *sec) {
// heuristics described in
// https://sourceware.org/binutils/docs/ld/Output-Section-LMA.html
if (sec->lmaExpr) {
- state->lmaOffset = sec->lmaExpr().getValue() - dot;
+ state->lmaOffset = sec->lmaExpr().getValue() - sec->addr;
} else if (MemoryRegion *mr = sec->lmaRegion) {
uint64_t lmaStart = alignToPowerOf2(mr->curPos, sec->addralign);
if (mr->curPos < lmaStart)
expandMemoryRegion(mr, lmaStart - mr->curPos, sec->name);
- state->lmaOffset = lmaStart - dot;
+ state->lmaOffset = lmaStart - sec->addr;
} else if (!sameMemRegion || !prevLMARegionIsDefault) {
state->lmaOffset = 0;
}
@@ -1315,7 +1330,7 @@ bool LinkerScript::assignOffsets(OutputSection *sec) {
state->tbssAddr = dot;
dot = savedDot;
}
- return addressChanged;
+ return sec->addr != oldAddress;
}
static bool isDiscardable(const OutputSection &sec) {
diff --git a/lld/test/ELF/linkerscript/compress-sections.s b/lld/test/ELF/linkerscript/compress-sections.s
index 5131fa7542243..d44825956c1f3 100644
--- a/lld/test/ELF/linkerscript/compress-sections.s
+++ b/lld/test/ELF/linkerscript/compress-sections.s
@@ -9,8 +9,7 @@
# CHECK: nonalloc PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 00 C 0 0 1
# CHECK-NEXT: str PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 01 MSC 0 0 1
-# CHECK: 0000000000000000 0 NOTYPE GLOBAL DEFAULT [[#]] (nonalloc) nonalloc_start
-# CHECK: 0000000000000063 0 NOTYPE GLOBAL DEFAULT [[#]] (nonalloc) nonalloc_end
+# CHECK: 0000000000000063 0 NOTYPE GLOBAL DEFAULT ABS nonalloc_size
# CHECK: String dump of section 'str':
# CHECK-NEXT: [ 0] AAA
# CHECK-NEXT: [ 4] {{a+}}
@@ -47,12 +46,11 @@ SECTIONS {
b = c+1;
a = b+1;
nonalloc : {
- nonalloc_start = .;
## In general, using data commands is error-prone. This case is correct, though.
*(nonalloc*) QUAD(SIZEOF(.text))
. += a;
- nonalloc_end = .;
}
+ nonalloc_size = SIZEOF(nonalloc);
str : { *(str) }
}
diff --git a/lld/test/ELF/linkerscript/memory-nonalloc-no-warn.test b/lld/test/ELF/linkerscript/memory-nonalloc-no-warn.test
index eabdf75fcf935..81a2f8085c17f 100644
--- a/lld/test/ELF/linkerscript/memory-nonalloc-no-warn.test
+++ b/lld/test/ELF/linkerscript/memory-nonalloc-no-warn.test
@@ -21,9 +21,9 @@
# CHECK-NEXT: [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
# CHECK-NEXT: [ 1] .nonalloc PROGBITS 0000000000000000 001064 001000 00 W 0 0 1
# CHECK-NEXT: [ 2] .dat PROGBITS 0000000000000000 002064 000004 00 W 0 0 1
-# CHECK-NEXT: [ 3] .intvec0_out PROGBITS 0000000000000000 002068 000000 00 W 0 0 1
-# CHECK-NEXT: [ 4] .intvec1_out PROGBITS 0000000000000000 002068 000000 00 W 0 0 1
-# CHECK-NEXT: [ 5] .intvec2_out PROGBITS 0000000000000000 002068 000000 00 W 0 0 1
+# CHECK-NEXT: [ 3] .intvec0_out PROGBITS 00000000803fe000 002068 000000 00 W 0 0 1
+# CHECK-NEXT: [ 4] .intvec1_out PROGBITS 00000000803fe020 002068 000000 00 W 0 0 1
+# CHECK-NEXT: [ 5] .intvec2_out PROGBITS 00000000803fe040 002068 000000 00 W 0 0 1
# CHECK-NEXT: [ 6] .intvec3_out PROGBITS 00000000803fe060 001060 000004 00 AX 0 0 1
# CHECK-NEXT: [ 7] .text PROGBITS 00000000803fe064 001064 000000 00 AX 0 0 4
# CHECK-NEXT: [ 8] .comment PROGBITS 0000000000000000 {{.*}} {{.*}} 01 MS 0 0 1
diff --git a/lld/test/ELF/linkerscript/symbols-non-alloc.test b/lld/test/ELF/linkerscript/symbols-non-alloc.test
index ca47b2bfbcac6..b52bc1ec4e6be 100644
--- a/lld/test/ELF/linkerscript/symbols-non-alloc.test
+++ b/lld/test/ELF/linkerscript/symbols-non-alloc.test
@@ -1,6 +1,8 @@
# REQUIRES: x86
## The address of a symbol assignment after a non-SHF_ALLOC section equals the
-## end address of the last SHF_ALLOC section.
+## end address of the last SHF_ALLOC section. However, script-defined symbols
+## appearing within the non-SHF_ALLOC section still behave as if the section
+## were SHF_ALLOC.
# RUN: echo '.section .nonalloc,""; .quad 0' \
# RUN: | llvm-mc -filetype=obj -triple=x86_64-unknown-linux - -o %t
@@ -9,14 +11,20 @@
# CHECK: Sections:
# CHECK: .text 00000000 0000000000000120
-# CHECK: .nonalloc 00000008 0000000000000000
+# CHECK: .nonalloc 00000008 0000000000000120
# CHECK: SYMBOL TABLE:
+# CHECK: 0000000000000120 g .nonalloc 0000000000000000 start_nonalloc
+# CHECK: 0000000000000128 g .nonalloc 0000000000000000 end_nonalloc
# CHECK: 0000000000000120 g .nonalloc 0000000000000000 Sym
SECTIONS {
. = SIZEOF_HEADERS;
.text : { *(.text) }
- .nonalloc : { *(.nonalloc) }
+ .nonalloc : {
+ start_nonalloc = .;
+ *(.nonalloc);
+ end_nonalloc = .;
+ }
Sym = .;
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/203788
More information about the llvm-commits
mailing list