[lld] [ELF] Set sh_entsize of .{init, fini, preinit}_array to the word size (PR #226697)

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 26 06:06:00 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lld-elf

Author: dong jianqiang (dongjianqiang2)

<details>
<summary>Changes</summary>

.init_array, .fini_array, .preinit_array are tables of function pointers, so sh_entsize should be the pointer size. lld currently only propagates sh_entsize from input sections, and emits 0 when none provides one:

  - the integrated assembler writes sh_entsize 0 for @<!-- -->init_array (GNU as writes the word size), so nearly every clang-compiled link with static initializers gets sh_entsize 0;
  - sections declared in a linker script have no input section at all, e.g. .init_array (TYPE=SHT_INIT_ARRAY) : { ... } on bare metal.

GNU ld emits the word size in all of these cases (verified against GNU ld 2.42 for script-declared sections, -r output, and llvm-mc-produced inputs). This patch falls back to the word size when sh_size is a multiple of it, which keeps sh_size % sh_entsize == 0 — a section header never claims an entry size that doesn't divide its contents. Unlike GNU ld, keep 0 when sh_size is not a multiple of the word size: such a section is not a table of fixed-size entries, and per the generic ABI sh_entsize 0 is the correct encoding for "no uniform entry size".

An assembler-side fix (emit the word size for @<!-- -->init_array like GNU as) would be complementary but can't cover linker-script sections, which have no input section to propagate from.

Only the section header field changes; section contents, addresses and sizes are unchanged.

---
Full diff: https://github.com/llvm/llvm-project/pull/226697.diff


4 Files Affected:

- (modified) lld/ELF/OutputSections.cpp (+11-1) 
- (modified) lld/test/ELF/linkerscript/custom-section-type.s (+3-3) 
- (added) lld/test/ELF/linkerscript/init-array-entsize.s (+108) 
- (modified) lld/test/ELF/pre_init_fini_array.s (+1-1) 


``````````diff
diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp
index 86c5f66c8c0ae..2f282c855967a 100644
--- a/lld/ELF/OutputSections.cpp
+++ b/lld/ELF/OutputSections.cpp
@@ -56,7 +56,17 @@ uint32_t OutputSection::getPhdrFlags() const {
 
 template <class ELFT>
 void OutputSection::writeHeaderTo(typename ELFT::Shdr *shdr) {
-  shdr->sh_entsize = entsize;
+  // .init_array, .fini_array, and .preinit_array hold tables of pointers, so
+  // use the word size if no entry size is available (the integrated assembler
+  // and linker script sections leave sh_entsize 0). Keep 0 if sh_size is not
+  // a multiple of the word size: such a section is not a table of fixed-size
+  // entries. GNU ld uses the word size even then.
+  uint64_t es = entsize;
+  if (es == 0 && (type == SHT_INIT_ARRAY || type == SHT_FINI_ARRAY ||
+                  type == SHT_PREINIT_ARRAY) &&
+      size % ctx.arg.wordsize == 0)
+    es = ctx.arg.wordsize;
+  shdr->sh_entsize = es;
   shdr->sh_addralign = addralign;
   shdr->sh_type = type;
   shdr->sh_offset = offset;
diff --git a/lld/test/ELF/linkerscript/custom-section-type.s b/lld/test/ELF/linkerscript/custom-section-type.s
index 2add3a52f8117..2bed8ee493051 100644
--- a/lld/test/ELF/linkerscript/custom-section-type.s
+++ b/lld/test/ELF/linkerscript/custom-section-type.s
@@ -15,9 +15,9 @@
 # CHECK-NEXT:  [ 1] progbits          PROGBITS        [[#%x,]]         [[#%x,]] 000001 00   A  0   0  1
 # CHECK-NEXT:  [ 2] note              NOTE            [[#%x,]]         [[#%x,]] 000002 00   A  0   0  1
 # CHECK-NEXT:  [ 3] nobits            NOBITS          [[#%x,]]         [[#%x,]] 000001 00   A  0   0  1
-# CHECK-NEXT:  [ 4] init_array        INIT_ARRAY      [[#%x,]]         [[#%x,]] 000008 00   A  0   0  1
-# CHECK-NEXT:  [ 5] fini_array        FINI_ARRAY      [[#%x,]]         [[#%x,]] 000008 00   A  0   0  1
-# CHECK-NEXT:  [ 6] preinit_array     PREINIT_ARRAY   [[#%x,]]         [[#%x,]] 000008 00   A  0   0  1
+# CHECK-NEXT:  [ 4] init_array        INIT_ARRAY      [[#%x,]]         [[#%x,]] 000008 08   A  0   0  1
+# CHECK-NEXT:  [ 5] fini_array        FINI_ARRAY      [[#%x,]]         [[#%x,]] 000008 08   A  0   0  1
+# CHECK-NEXT:  [ 6] preinit_array     PREINIT_ARRAY   [[#%x,]]         [[#%x,]] 000008 08   A  0   0  1
 # CHECK-NEXT:  [ 7] group             GROUP           [[#%x,]]         [[#%x,]] 000004 00   A [[#SYMTAB:]] 0  1
 # CHECK-NEXT:  [ 8] expr              0x42: <unknown> [[#%x,]]         [[#%x,]] 000001 00   A  0   0  1
 # CHECK:       [[[#SYMTAB]]] .symtab  SYMTAB
diff --git a/lld/test/ELF/linkerscript/init-array-entsize.s b/lld/test/ELF/linkerscript/init-array-entsize.s
new file mode 100644
index 0000000000000..e82d38b100950
--- /dev/null
+++ b/lld/test/ELF/linkerscript/init-array-entsize.s
@@ -0,0 +1,108 @@
+# REQUIRES: x86
+# RUN: split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/t64.s -o %t64.o
+# RUN: llvm-mc -filetype=obj -triple=i386 %t/t32.s -o %t32.o
+# RUN: yaml2obj %t/entsize16.yaml -o %t16.o
+# RUN: yaml2obj %t/conflict.yaml -o %tconflict.o
+
+## init_array/fini_array/preinit_array sections hold tables of pointers, so
+## sh_entsize should be the pointer size even when no input section provided
+## one (e.g. sections declared in a linker script). This matches GNU ld.
+# RUN: ld.lld -T %t/script.lds %t64.o -o %t64
+# RUN: llvm-readelf -S %t64 | FileCheck %s --check-prefix=CHECK64
+
+# RUN: ld.lld -m elf_i386 -T %t/script.lds %t32.o -o %t32
+# RUN: llvm-readelf -S %t32 | FileCheck %s --check-prefix=CHECK32
+
+## An explicit input entry size is kept as is.
+# RUN: ld.lld -T %t/propagate.lds %t16.o -o %t16
+# RUN: llvm-readelf -S %t16 | FileCheck %s --check-prefix=PROP
+
+## Conflicting input entry sizes record no entry size (0). sh_size 24 is a
+## multiple of 8, so fall back to 8, matching GNU ld.
+# RUN: ld.lld -T %t/propagate.lds %tconflict.o -o %tconflict
+# RUN: llvm-readelf -S %tconflict | FileCheck %s --check-prefix=CONFLICT
+
+## A 0-byte section trivially has a multiple of the word size as sh_size, so
+## set sh_entsize to the word size. (GNU ld removes empty script sections.)
+# CHECK64:      .init_array INIT_ARRAY [[#%x,]] [[#%x,]] 000000 08
+# CHECK64:      .fini_array FINI_ARRAY [[#%x,]] [[#%x,]] 000000 08
+## 9-byte contents are not a table of pointers: keep sh_entsize 0.
+# CHECK64:      .preinit_array PREINIT_ARRAY [[#%x,]] [[#%x,]] 000009 00
+
+# CHECK32:      .init_array INIT_ARRAY [[#%x,]] [[#%x,]] 000000 04
+# CHECK32:      .fini_array FINI_ARRAY [[#%x,]] [[#%x,]] 000000 04
+# CHECK32:      .preinit_array PREINIT_ARRAY [[#%x,]] [[#%x,]] 000009 00
+
+# PROP:         .init_array INIT_ARRAY [[#%x,]] [[#%x,]] 000010 10
+
+# CONFLICT:     .init_array INIT_ARRAY [[#%x,]] [[#%x,]] 000018 08
+
+#--- t64.s
+.globl _start
+_start:
+  movq __init_array_start at GOTPCREL(%rip), %rax
+  movq __fini_array_start at GOTPCREL(%rip), %rax
+  ret
+
+#--- t32.s
+.globl _start
+_start:
+  movl $__init_array_start, %eax
+  movl $__fini_array_start, %eax
+  movl $__preinit_array_start, %eax
+  ret
+
+#--- script.lds
+SECTIONS {
+  .text : { *(.text) }
+  .init_array (TYPE=SHT_INIT_ARRAY) : { *(.init_array) }
+  .fini_array (TYPE=SHT_FINI_ARRAY) : { *(.fini_array) }
+  .preinit_array (TYPE=SHT_PREINIT_ARRAY) : { QUAD(1) BYTE(0) }
+}
+
+#--- propagate.lds
+SECTIONS {
+  .init_array : { *(.init_array .init_array.*) }
+}
+
+#--- entsize16.yaml
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_REL
+  Machine: EM_X86_64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+    Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+    Content: C3
+  - Name: .init_array
+    Type: SHT_INIT_ARRAY
+    Flags: [ SHF_ALLOC, SHF_WRITE ]
+    EntSize: 16
+    Content: "00000000000000000000000000000000"
+
+#--- conflict.yaml
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_REL
+  Machine: EM_X86_64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+    Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+    Content: C3
+  - Name: .init_array
+    Type: SHT_INIT_ARRAY
+    Flags: [ SHF_ALLOC, SHF_WRITE ]
+    EntSize: 8
+    Content: "0000000000000000"
+  - Name: .init_array.2
+    Type: SHT_INIT_ARRAY
+    Flags: [ SHF_ALLOC, SHF_WRITE ]
+    EntSize: 16
+    Content: "00000000000000000000000000000000"
diff --git a/lld/test/ELF/pre_init_fini_array.s b/lld/test/ELF/pre_init_fini_array.s
index 7ebc6ad40bdd4..a5d3c6d50e402 100644
--- a/lld/test/ELF/pre_init_fini_array.s
+++ b/lld/test/ELF/pre_init_fini_array.s
@@ -29,7 +29,7 @@ _start:
 
 // CHECK-LABEL: Section Headers:
 // CHECK:      Name           Type          Address                 Off       Size                    ES Flg
-// CHECK:      .init_array    INIT_ARRAY    [[# %x, INIT_ADDR:]]    [[# %x,]] [[# %x, INIT_SIZE:]]    00 WA
+// CHECK:      .init_array    INIT_ARRAY    [[# %x, INIT_ADDR:]]    [[# %x,]] [[# %x, INIT_SIZE:]]    08 WA
 // CHECK-NEXT: .preinit_array PREINIT_ARRAY [[# %x, PREINIT_ADDR:]] [[# %x,]] [[# %x, PREINIT_SIZE:]] 00 WA
 // CHECK-NEXT: .fini_array    FINI_ARRAY    [[# %x, FINI_ADDR:]]    [[# %x,]] [[# %x, FINI_SIZE:]]    00 WA
 

``````````

</details>


https://github.com/llvm/llvm-project/pull/226697


More information about the llvm-commits mailing list