[lld] [ELF] Set sh_entsize of .{init, fini, preinit}_array to the word size (PR #226697)
dong jianqiang via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 26 06:08:02 PDT 2026
https://github.com/dongjianqiang2 updated https://github.com/llvm/llvm-project/pull/226697
>From a49bb51cfeaf22218f646123f616272ee0659f7f Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Sat, 26 Sep 2026 20:53:45 +0800
Subject: [PATCH] [ELF] Set sh_entsize of .{init,fini,preinit}_array to the
word size
These sections are tables of function pointers. lld only propagates
sh_entsize from input sections today, but the integrated assembler emits
sh_entsize 0 for @init_array (GNU as emits the word size) and sections
created by a linker script have no input section to propagate from. As a
result lld emits 0 where GNU ld emits the word size, e.g. for
`.init_array (TYPE=SHT_INIT_ARRAY) : { ... }` (common on bare metal).
Fall back to the word size when sh_size is a multiple of it. Unlike GNU
ld, keep 0 when sh_size is not a multiple of the word size: the generic
ABI says sh_entsize is 0 if the section does not hold a table of
fixed-size entries.
This changes the section header of almost every link that has a
.init_array (clang emits sh_entsize 0 for .init_array); section contents,
addresses and sizes are unchanged.
---
lld/ELF/OutputSections.cpp | 13 ++-
.../ELF/linkerscript/custom-section-type.s | 6 +-
.../ELF/linkerscript/init-array-entsize.s | 108 ++++++++++++++++++
lld/test/ELF/pre_init_fini_array.s | 2 +-
4 files changed, 124 insertions(+), 5 deletions(-)
create mode 100644 lld/test/ELF/linkerscript/init-array-entsize.s
diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp
index 86c5f66c8c0ae..b42c1fbe49cde 100644
--- a/lld/ELF/OutputSections.cpp
+++ b/lld/ELF/OutputSections.cpp
@@ -56,7 +56,18 @@ 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
More information about the llvm-commits
mailing list