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 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.
This commit is contained in:
Indu Bhagat 2026-01-22 10:00:36 +02:00
parent 1d734bf4b9
commit 9d8fa7ffc0
13 changed files with 104 additions and 2 deletions

View file

@ -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;

View file

@ -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. */

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,6 @@
ENTRY(_start)
SECTIONS
{
. = SIZEOF_HEADERS;
.text : { *(.text) }
}

View file

@ -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"
}
}

View file

@ -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 .*
#...

View file

@ -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 .*
#...

View file

@ -0,0 +1,6 @@
ENTRY(_start)
SECTIONS
{
. = SIZEOF_HEADERS;
.text : { *(.text) }
}

View file

@ -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

View file

@ -0,0 +1,6 @@
ENTRY(_start)
SECTIONS
{
. = SIZEOF_HEADERS;
.text : { *(.text) }
}

View file

@ -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 .*
#...

View file

@ -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"