[PATCH] ld: sframe: do not generate .sframe for PLT if no .sframe is in input BFDs

Indu Bhagat indu.bhagat@oracle.com
Sun Jan 25 01:33:33 GMT 2026


  Hi,

  I am considering committing this patch soon.  Its verified that this
  fixes the kernel build problem.  Regression testing ongoing, looks OK so
  far.

GNU ld creates SFrame stack trace info for the PLT. For x86 the linker-
created .sframe section is created in setup_gnu_properties.  For s390 it
is created in create_dynamic_sections.  For both, the section data is
itself emitted a bit later in late_size_sections.  Note that for aarch64 the
linker does not create .sframe for PLT yet.

Recall that a previous patch disentangled --no-ld-generated-unwind-info
from the linker-generated .sframe sections: this means that the linker
now generates .sframe section (for .plt*) for the first input BFD
enthusiatically even when none of the input BFDs have any .sframe
section, unless --discard-sframe is also added.  The issue is that these
(unexpected) linker-generated .sframe sections (on x86_64, and s390) may
now trip the linking process, e.g., when using --orphan-handling=error
together with a linker script that treats .sframe differently than the
default linker script.
https://sourceware.org/pipermail/binutils/2026-January/147826.html

Further, with SFrame sections to be soon marked KEEP for fixing
GC/SFrame (PR ld/32769), the presence of these linker generated SFrame
sections will also cause emission of an empty .sframe (for x86_64 and
s390x), even when all input bfd's have no .sframe section.

This patch avoids creation of .sframe for .plt* if none of the input
BFDs had any .sframe section.  This then avoids creation of empty
.sframe in linked objects on x86_64 and s390x, when none of the inputs
have SFrame sections.  This also fixes PR ld/33830.

For the code changes:
Reviewed-by: Jens Remus <jremus@linux.ibm.com>

New testcases have (since the above Reviewed-by) been added.  Since
--no-ld-generated-unwind-info is not supported on aarch64, add
target-specific ld tests.  Additionally add a generic test (for all
targets that support SFrame) to ensure no output .sframe is generated if
users says no --gsframe or similar.

bfd/
	PR ld/33830
	* elf-bfd.h (_bfd_elf_sframe_present_input_bfds): New
	declaration.
	* elf-sframe.c (_bfd_elf_sframe_present_input_bfds): New
	definition.
	* elf64-s390.c (elf_s390_create_dynamic_sections): Do not
	generate .sframe for .plt unconditionally.
	* elfxx-x86.c (_bfd_x86_elf_link_setup_gnu_properties):
	Likewise.
ld/testsuite/
	PR ld/33830
	* ld-s390/no-sframe.ld: Linker script with no specification for
	SFrame sections.
	* ld-s390/s390.exp: Add new test.
	* ld-s390/sframe-command-line-2.d: New testcase that uses
	--no-ld-generated-unwind-info and a linker script that has no
	specific rules for .sframe.
	* ld-x86-64/no-sframe.ld: Likewise for x86_64.
	* ld-x86-64/sframe-command-line-2.d: Likewise for x86_64.
	* ld-x86-64/x86-64.exp: Add new test.
	* ld-sframe/no-ld-generated-sframe.d: Ensure no .sframe in
	output if no .sframe in input.
	* ld-sframe/no-sframe.ld: Linker script with no specification
	for SFrame sections.
	* ld-sframe/test.s: Add new test.
---
 bfd/elf-bfd.h                                  |  2 ++
 bfd/elf-sframe.c                               | 18 ++++++++++++++++++
 bfd/elf64-s390.c                               | 10 +++++++++-
 bfd/elfxx-x86.c                                | 11 ++++++++++-
 ld/testsuite/ld-s390/no-sframe.ld              |  6 ++++++
 ld/testsuite/ld-s390/s390.exp                  |  3 +++
 ld/testsuite/ld-s390/sframe-command-line-2.d   | 10 ++++++++++
 .../ld-sframe/no-ld-generated-sframe.d         | 10 ++++++++++
 ld/testsuite/ld-sframe/no-sframe.ld            |  6 ++++++
 ld/testsuite/ld-sframe/test.s                  | 11 +++++++++++
 ld/testsuite/ld-x86-64/no-sframe.ld            |  6 ++++++
 ld/testsuite/ld-x86-64/sframe-command-line-2.d | 10 ++++++++++
 ld/testsuite/ld-x86-64/x86-64.exp              |  3 +++
 13 files changed, 104 insertions(+), 2 deletions(-)
 create mode 100644 ld/testsuite/ld-s390/no-sframe.ld
 create mode 100644 ld/testsuite/ld-s390/sframe-command-line-2.d
 create mode 100644 ld/testsuite/ld-sframe/no-ld-generated-sframe.d
 create mode 100644 ld/testsuite/ld-sframe/no-sframe.ld
 create mode 100644 ld/testsuite/ld-sframe/test.s
 create mode 100644 ld/testsuite/ld-x86-64/no-sframe.ld
 create mode 100644 ld/testsuite/ld-x86-64/sframe-command-line-2.d

diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index f368986dd05..c7ef031979e 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -2628,6 +2628,8 @@ extern bool _bfd_elf_maybe_strip_eh_frame_hdr
 
 extern bool _bfd_elf_sframe_present
   (struct bfd_link_info *) ATTRIBUTE_HIDDEN;
+extern bool _bfd_elf_sframe_present_input_bfds
+  (struct bfd_link_info *) ATTRIBUTE_HIDDEN;
 extern bool _bfd_elf_parse_sframe
   (bfd *, struct bfd_link_info *, asection *, struct elf_reloc_cookie *)
   ATTRIBUTE_HIDDEN;
diff --git a/bfd/elf-sframe.c b/bfd/elf-sframe.c
index 33e4df5b1c7..77d9d33d602 100644
--- a/bfd/elf-sframe.c
+++ b/bfd/elf-sframe.c
@@ -171,6 +171,24 @@ sframe_read_value (bfd *abfd, bfd_byte *contents, unsigned int offset,
   return value;
 }
 
+/* Return true if any of the input BFDs contains at least one .sframe
+   section.  */
+
+bool
+_bfd_elf_sframe_present_input_bfds (struct bfd_link_info *info)
+{
+  /* Find if any input file has an .sframe section.  */
+  for (bfd *pbfd = info->input_bfds; pbfd != NULL; pbfd = pbfd->link.next)
+    if (bfd_get_flavour (pbfd) == bfd_target_elf_flavour
+	&& bfd_count_sections (pbfd) != 0)
+      {
+	asection *sec = bfd_get_section_by_name (pbfd, ".sframe");
+	if (sec != NULL)
+	  return true;
+      }
+  return false;
+}
+
 /* Return true if there is at least one non-empty .sframe section in
    input files.  Can only be called after ld has mapped input to
    output sections, and before sections are stripped.  */
diff --git a/bfd/elf64-s390.c b/bfd/elf64-s390.c
index 8ad10ce6bd1..b6a903db42a 100644
--- a/bfd/elf64-s390.c
+++ b/bfd/elf64-s390.c
@@ -4309,7 +4309,15 @@ elf_s390_create_dynamic_sections (bfd *dynobj,
         }
 
       /* Create .sframe section for .plt section.  */
-      if (!info->discard_sframe)
+      bool gen_plt_sframe_p = !info->discard_sframe
+	&& _bfd_elf_sframe_present_input_bfds (info);
+
+      /* Do not make SFrame sections for dynobj unconditionally.  If there
+	 are no SFrame sections for any input files, skip creating the linker
+	 created SFrame sections too.  Since SFrame sections are marked KEEP,
+	 prohibiting these linker-created SFrame sections when unnecessary,
+	 helps avoid creating of empty SFrame sections in the output.  */
+      if (gen_plt_sframe_p)
 	{
 	  flagword flags = (SEC_ALLOC | SEC_LOAD | SEC_READONLY
 			    | SEC_HAS_CONTENTS | SEC_IN_MEMORY
diff --git a/bfd/elfxx-x86.c b/bfd/elfxx-x86.c
index 901b858fb34..b450aed4038 100644
--- a/bfd/elfxx-x86.c
+++ b/bfd/elfxx-x86.c
@@ -4827,7 +4827,16 @@ _bfd_x86_elf_link_setup_gnu_properties
 	}
 
       /* .sframe sections are emitted for AMD64 ABI only.  */
-      if (ABI_64_P (info->output_bfd) && !info->discard_sframe)
+      bool gen_plt_sframe_p = ABI_64_P (info->output_bfd)
+	&& !info->discard_sframe
+	&& _bfd_elf_sframe_present_input_bfds (info);
+
+      /* Do not make SFrame sections for dynobj unconditionally.  If there
+	 are no SFrame sections for any input files, skip creating the linker
+	 created SFrame sections too.  Since SFrame sections are marked KEEP,
+	 prohibiting these linker-created SFrame sections, when unnecessary,
+	 helps avoid creation of empty SFrame sections in the output.  */
+      if (gen_plt_sframe_p)
 	{
 	  flagword flags = (SEC_ALLOC | SEC_LOAD | SEC_READONLY
 			    | SEC_HAS_CONTENTS | SEC_IN_MEMORY
diff --git a/ld/testsuite/ld-s390/no-sframe.ld b/ld/testsuite/ld-s390/no-sframe.ld
new file mode 100644
index 00000000000..13224d84573
--- /dev/null
+++ b/ld/testsuite/ld-s390/no-sframe.ld
@@ -0,0 +1,6 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = SIZEOF_HEADERS;
+  .text : { *(.text) }
+}
diff --git a/ld/testsuite/ld-s390/s390.exp b/ld/testsuite/ld-s390/s390.exp
index 0fdfb39c5aa..80536e2e39a 100644
--- a/ld/testsuite/ld-s390/s390.exp
+++ b/ld/testsuite/ld-s390/s390.exp
@@ -192,6 +192,9 @@ if [istarget "s390x-*-*"] {
     if { ![skip_sframe_tests] } {
 	run_dump_test "sframe-simple-1"
 	run_dump_test "sframe-plt-1"
+	# Test --discard-sframe
 	run_dump_test "sframe-command-line-1"
+	# Test --no-ld-generated-unwind-info
+	run_dump_test "sframe-command-line-2"
     }
 }
diff --git a/ld/testsuite/ld-s390/sframe-command-line-2.d b/ld/testsuite/ld-s390/sframe-command-line-2.d
new file mode 100644
index 00000000000..941e845139d
--- /dev/null
+++ b/ld/testsuite/ld-s390/sframe-command-line-2.d
@@ -0,0 +1,10 @@
+#as:
+#source: sframe-foo.s
+#ld: --no-ld-generated-unwind-info -T no-sframe.ld -e foo
+#objdump: -hw
+#name: No interaction between --no-ld-generated-unwind-info and SFrame
+
+#failif
+#...
+  [0-9] .sframe .*
+#...
diff --git a/ld/testsuite/ld-sframe/no-ld-generated-sframe.d b/ld/testsuite/ld-sframe/no-ld-generated-sframe.d
new file mode 100644
index 00000000000..bc9396e6ff4
--- /dev/null
+++ b/ld/testsuite/ld-sframe/no-ld-generated-sframe.d
@@ -0,0 +1,10 @@
+#as:
+#source: test.s
+#ld: -T no-sframe.ld
+#objdump: -hw
+#name: No SFrame section in output with no --gsframe
+
+#failif
+#...
+  [0-9] .sframe .*
+#...
diff --git a/ld/testsuite/ld-sframe/no-sframe.ld b/ld/testsuite/ld-sframe/no-sframe.ld
new file mode 100644
index 00000000000..13224d84573
--- /dev/null
+++ b/ld/testsuite/ld-sframe/no-sframe.ld
@@ -0,0 +1,6 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = SIZEOF_HEADERS;
+  .text : { *(.text) }
+}
diff --git a/ld/testsuite/ld-sframe/test.s b/ld/testsuite/ld-sframe/test.s
new file mode 100644
index 00000000000..31e8ea8e7b6
--- /dev/null
+++ b/ld/testsuite/ld-sframe/test.s
@@ -0,0 +1,11 @@
+	.text
+	.globl	foo
+	.type	foo, @function
+foo:
+	.cfi_startproc
+	.cfi_def_cfa_offset 16
+	.cfi_endproc
+
+	.globl _start
+_start:
+	.long   foo
diff --git a/ld/testsuite/ld-x86-64/no-sframe.ld b/ld/testsuite/ld-x86-64/no-sframe.ld
new file mode 100644
index 00000000000..13224d84573
--- /dev/null
+++ b/ld/testsuite/ld-x86-64/no-sframe.ld
@@ -0,0 +1,6 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = SIZEOF_HEADERS;
+  .text : { *(.text) }
+}
diff --git a/ld/testsuite/ld-x86-64/sframe-command-line-2.d b/ld/testsuite/ld-x86-64/sframe-command-line-2.d
new file mode 100644
index 00000000000..941e845139d
--- /dev/null
+++ b/ld/testsuite/ld-x86-64/sframe-command-line-2.d
@@ -0,0 +1,10 @@
+#as:
+#source: sframe-foo.s
+#ld: --no-ld-generated-unwind-info -T no-sframe.ld -e foo
+#objdump: -hw
+#name: No interaction between --no-ld-generated-unwind-info and SFrame
+
+#failif
+#...
+  [0-9] .sframe .*
+#...
diff --git a/ld/testsuite/ld-x86-64/x86-64.exp b/ld/testsuite/ld-x86-64/x86-64.exp
index 8bed9b566bc..5608c42a0db 100644
--- a/ld/testsuite/ld-x86-64/x86-64.exp
+++ b/ld/testsuite/ld-x86-64/x86-64.exp
@@ -588,7 +588,10 @@ run_dump_test "tls-le-pic-3-x32"
 if { ![skip_sframe_tests] } {
     run_dump_test "sframe-simple-1"
     run_dump_test "sframe-link-1"
+    # Test --discard-sframe
     run_dump_test "sframe-command-line-1"
+    # Test --no-ld-generated-unwind-info
+    run_dump_test "sframe-command-line-2"
     run_dump_test "sframe-reloc-1"
     run_dump_test "sframe-plt-1"
     run_dump_test "sframe-ibt-plt-1"
-- 
2.43.0



More information about the Binutils mailing list