[lld] [lld][ELF] Allow explicit address expressions on .tbss output sections (PR #196447)
via llvm-commits
llvm-commits at lists.llvm.org
Fri May 8 15:50:49 PDT 2026
https://github.com/amosher-nvidia updated https://github.com/llvm/llvm-project/pull/196447
>From d02c21c8fe7f3c2b159499de4adb0ca97e1e562c Mon Sep 17 00:00:00 2001
From: Avery Mosher <amosher at nvidia.com>
Date: Thu, 7 May 2026 16:33:56 -0700
Subject: [PATCH 1/2] [LLD][ELF] Allow explicit address expressions on .tbss
output sections
This fixes a bug where lld would ignore explicit requests to place .tbss
sections at a user-defined address, and matches LD's behavior in this case
---
lld/ELF/LinkerScript.cpp | 2 +-
lld/test/ELF/linkerscript/tbss-addr-expr.s | 52 ++++++++++++++++++++++
2 files changed, 53 insertions(+), 1 deletion(-)
create mode 100644 lld/test/ELF/linkerscript/tbss-addr-expr.s
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index b9ed964b8c720..567bb188d17b3 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -1189,7 +1189,7 @@ bool LinkerScript::assignOffsets(OutputSection *sec) {
if (!(sec->flags & SHF_ALLOC)) {
// Non-SHF_ALLOC sections have zero addresses.
dot = 0;
- } else if (isTbss) {
+ } else if (isTbss && !sec->addrExpr) {
// 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)
diff --git a/lld/test/ELF/linkerscript/tbss-addr-expr.s b/lld/test/ELF/linkerscript/tbss-addr-expr.s
new file mode 100644
index 0000000000000..889064bebc2de
--- /dev/null
+++ b/lld/test/ELF/linkerscript/tbss-addr-expr.s
@@ -0,0 +1,52 @@
+# REQUIRES: x86
+## Test that an explicit address expression on a .tbss section is respected.
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
+
+## Test that an explicit address on a tbss section is honored.
+# RUN: echo 'SECTIONS { \
+# RUN: . = SIZEOF_HEADERS; \
+# RUN: .text : { *(.text) } \
+# RUN: .tbss 0x1000 : { *(.tbss) } \
+# RUN: .data : { *(.data) } \
+# RUN: }' > %t.lds
+# RUN: ld.lld -T %t.lds %t.o -o %t
+# RUN: llvm-readelf -S %t | FileCheck %s
+
+# CHECK: .tbss NOBITS 0000000000001000 {{[0-9a-f]+}} 000004
+
+## Test that an expression (not just a constant) works for tbss address.
+# RUN: echo 'SECTIONS { \
+# RUN: . = SIZEOF_HEADERS; \
+# RUN: .text : { *(.text) } \
+# RUN: .tbss (0x800 + 0x800) : { *(.tbss) } \
+# RUN: .data : { *(.data) } \
+# RUN: }' > %t2.lds
+# RUN: ld.lld -T %t2.lds %t.o -o %t2
+# RUN: llvm-readelf -S %t2 | FileCheck %s --check-prefix=CHECK2
+
+# CHECK2: .tbss NOBITS 0000000000001000 {{[0-9a-f]+}} 000004
+
+## Test that .data follows .text, not .tbss (since tbss is SHT_NOBITS).
+# RUN: echo 'SECTIONS { \
+# RUN: .text 0x1000 : { *(.text) } \
+# RUN: .tbss 0x2000 : { *(.tbss) } \
+# RUN: .data : { *(.data) } \
+# RUN: }' > %t3.lds
+# RUN: ld.lld -T %t3.lds %t.o -o %t3
+# RUN: llvm-readelf -S %t3 | FileCheck %s --check-prefix=CHECK3
+
+## .data should follow .text at 0x1001, not .tbss at 0x2004
+# CHECK3: .text PROGBITS 0000000000001000
+# CHECK3: .tbss NOBITS 0000000000002000
+# CHECK3: .data PROGBITS 0000000000001001
+
+.globl _start
+_start:
+ nop
+
+.section .tbss,"awT", at nobits
+ .long 0
+
+.section .data,"aw"
+ .long 0
>From f0611b74c104c33e6bc920d90bc7627798568687 Mon Sep 17 00:00:00 2001
From: Avery Mosher <amosher at nvidia.com>
Date: Fri, 8 May 2026 15:49:30 -0700
Subject: [PATCH 2/2] [lld][ELF] Clean up tbss-addr-expr test and incorporate
maintainer feedback
---
lld/test/ELF/linkerscript/tbss-addr-expr.s | 101 +++++++++++++--------
1 file changed, 62 insertions(+), 39 deletions(-)
diff --git a/lld/test/ELF/linkerscript/tbss-addr-expr.s b/lld/test/ELF/linkerscript/tbss-addr-expr.s
index 889064bebc2de..7cd00976ea136 100644
--- a/lld/test/ELF/linkerscript/tbss-addr-expr.s
+++ b/lld/test/ELF/linkerscript/tbss-addr-expr.s
@@ -1,46 +1,37 @@
# REQUIRES: x86
## Test that an explicit address expression on a .tbss section is respected.
-# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
-
-## Test that an explicit address on a tbss section is honored.
-# RUN: echo 'SECTIONS { \
-# RUN: . = SIZEOF_HEADERS; \
-# RUN: .text : { *(.text) } \
-# RUN: .tbss 0x1000 : { *(.tbss) } \
-# RUN: .data : { *(.data) } \
-# RUN: }' > %t.lds
-# RUN: ld.lld -T %t.lds %t.o -o %t
-# RUN: llvm-readelf -S %t | FileCheck %s
-
-# CHECK: .tbss NOBITS 0000000000001000 {{[0-9a-f]+}} 000004
-
-## Test that an expression (not just a constant) works for tbss address.
-# RUN: echo 'SECTIONS { \
-# RUN: . = SIZEOF_HEADERS; \
-# RUN: .text : { *(.text) } \
-# RUN: .tbss (0x800 + 0x800) : { *(.tbss) } \
-# RUN: .data : { *(.data) } \
-# RUN: }' > %t2.lds
-# RUN: ld.lld -T %t2.lds %t.o -o %t2
-# RUN: llvm-readelf -S %t2 | FileCheck %s --check-prefix=CHECK2
-
-# CHECK2: .tbss NOBITS 0000000000001000 {{[0-9a-f]+}} 000004
-
-## Test that .data follows .text, not .tbss (since tbss is SHT_NOBITS).
-# RUN: echo 'SECTIONS { \
-# RUN: .text 0x1000 : { *(.text) } \
-# RUN: .tbss 0x2000 : { *(.tbss) } \
-# RUN: .data : { *(.data) } \
-# RUN: }' > %t3.lds
-# RUN: ld.lld -T %t3.lds %t.o -o %t3
-# RUN: llvm-readelf -S %t3 | FileCheck %s --check-prefix=CHECK3
-
-## .data should follow .text at 0x1001, not .tbss at 0x2004
-# CHECK3: .text PROGBITS 0000000000001000
-# CHECK3: .tbss NOBITS 0000000000002000
-# CHECK3: .data PROGBITS 0000000000001001
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %t/a.s -o %t/a.o
+# RUN: ld.lld -T %t/explicit.t %t/a.o -o %t/explicit
+# RUN: llvm-readelf -S %t/explicit | FileCheck %s --check-prefix=EXPLICIT
+
+# RUN: ld.lld -T %t/dataafter.t %t/a.o -o %t/dataafter
+# RUN: llvm-readelf -S %t/dataafter | FileCheck %s --check-prefix=DATAAFTER
+
+# RUN: ld.lld -T %t/consec.t %t/a.o -o %t/consec
+# RUN: llvm-readelf -S %t/consec | FileCheck %s --check-prefix=CONSEC
+
+## An explicit address on a .tbss output section is honored.
+# EXPLICIT: .tbss NOBITS 0000000000001000 {{[0-9a-f]+}} 000004
+
+## .data follows .text, not .tbss — SHT_NOBITS doesn't push the location counter.
+# DATAAFTER: .text PROGBITS 0000000000001000
+# DATAAFTER: .tbss NOBITS 0000000000002000
+# DATAAFTER: .data PROGBITS 0000000000001001
+
+## When the initial tbss output section has a defined address, subsequent tbss
+## output sections without addrExprs stack consecutively from its end (per the
+## existing comment in LinkerScript::assignOffsets: "The address range starts
+## from the end address of the previous tbss section"). .data still follows
+## .text, unaffected by tbss layout.
+# CONSEC: .text PROGBITS 0000000000000100
+# CONSEC: .tbss1 NOBITS 0000000000001000
+# CONSEC: .tbss2 NOBITS 0000000000001004
+# CONSEC: .data PROGBITS 0000000000000101
+
+#--- a.s
.globl _start
_start:
nop
@@ -48,5 +39,37 @@ _start:
.section .tbss,"awT", at nobits
.long 0
+.section .tbss1,"awT", at nobits
+ .long 0
+
+.section .tbss2,"awT", at nobits
+ .long 0
+
.section .data,"aw"
.long 0
+
+#--- explicit.t
+SECTIONS {
+ . = SIZEOF_HEADERS;
+ .text : { *(.text) }
+ .tbss 0x1000 : { *(.tbss) }
+ .data : { *(.data) }
+ /DISCARD/ : { *(.tbss1) *(.tbss2) }
+}
+
+#--- dataafter.t
+SECTIONS {
+ .text 0x1000 : { *(.text) }
+ .tbss 0x2000 : { *(.tbss) }
+ .data : { *(.data) }
+ /DISCARD/ : { *(.tbss1) *(.tbss2) }
+}
+
+#--- consec.t
+SECTIONS {
+ .text 0x100 : { *(.text) }
+ .tbss1 0x1000 : { *(.tbss1) }
+ .tbss2 : { *(.tbss2) }
+ .data : { *(.data) }
+ /DISCARD/ : { *(.tbss) }
+}
More information about the llvm-commits
mailing list