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

Indu Bhagat ibhagat@sourceware.org
Sun Jan 25 07:00:18 GMT 2026


https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=11e0dbc08927f9d84ebbd98346d7016140049f5b

commit 11e0dbc08927f9d84ebbd98346d7016140049f5b
Author: Indu Bhagat <indu.bhagat@oracle.com>
Date:   Thu Jan 22 10:00:36 2026 +0200

    ld: sframe: do not generate .sframe for PLT if no .sframe is in input BFDs
    
    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 832ca9ef670 uncoupled
    --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.

Diff:
---
 bfd/elf-bfd.h                                   |  2 ++
 bfd/elf-sframe.c                                | 18 ++++++++++++++++++
 bfd/elf64-s390.c                                | 11 +++++++++--
 bfd/elfxx-x86.c                                 | 13 +++++++++++--
 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/testsuite/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, 105 insertions(+), 4 deletions(-)

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..b109ae3154e 100644
--- a/bfd/elf64-s390.c
+++ b/bfd/elf64-s390.c
@@ -4308,8 +4308,15 @@ elf_s390_create_dynamic_sections (bfd *dynobj,
             }
         }
 
-      /* Create .sframe section for .plt section.  */
-      if (!info->discard_sframe)
+      /* Create .sframe section for .plt section.
+	 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.  */
+      bool gen_plt_sframe_p = (_bfd_elf_sframe_present_input_bfds (info)
+			       && !info->discard_sframe);
+      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..f476c04c888 100644
--- a/bfd/elfxx-x86.c
+++ b/bfd/elfxx-x86.c
@@ -4826,8 +4826,17 @@ _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)
+      /* Create .sframe section for .plt section.  SFrame sections are
+	 supported for AMD64 ABI only.  Further, 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.  */
+      bool gen_plt_sframe_p = (_bfd_elf_sframe_present_input_bfds (info)
+			       && !info->discard_sframe
+			       && ABI_64_P (info->output_bfd));
+      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"


More information about the Binutils-cvs mailing list