[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