[PATCH v3 06/11] s390: Add SFrame stack trace information for .plt section
Indu Bhagat
indu.bhagat@oracle.com
Sat Jul 5 23:23:43 GMT 2025
On 6/27/25 4:08 AM, Jens Remus wrote:
> Enable SFrame stack tracing through PLT entries. Based on x86-64.
>
> On s390x both PLT0 and PLTn entries are 32-bytes in size. Their code
> neither alters the stack pointer (SP), frame pointer (FP), nor return
> address (RA) registers. Therefore the PLT0 can be represented using
> a SFrame FDE of type PCINC with a single SFrame FRE and the PLTn can
> be represented using a SFrame FDE of type PCMASK, with a repetition
> block size of 32 (PLTn size), and a single SFrame FRE.
>
> Note that as both the PLT0 entry and the PLTn entries have equal size
> and could both be represented using the identical SFrame FRE, the whole
> .plt section on s390x could be represented using a single SFrame FDE of
> type PCMASK, with a repetition block size of 32 (PLT0 and PLTn size),
> and a single SFrame FRE. Keep the x86-64 logic with separate SFrame
> FDEs for PLT0 and PLTn, to ease potential generalization of the .sframe
> for .plt generation logic among architectures.
>
> bfd/
> * elf64-s390.c: Include sframe.h and sframe-api.h.
> (PLT_SFRAME_FDE_START_OFFSET, SFRAME_PLT0_MAX_NUM_FRES,
> SFRAME_PLTN_MAX_NUM_FRES, elf_s390x_sframe_plt_fre,
> elf_s390x_sframe_plt): New .sframe template for .plt section.
> (elf_s390_link_hash_table): Add plt_cfe_ctx, plt_sframe, and
> sframe_plt fields.
> (_bfd_s390_elf_create_sframe_plt): New function. Fill in
> .sframe section for .plt section.
> (_bfd_s390_elf_write_sframe_plt): New function. Write .sframe
> section.
> (elf_s390_create_dynamic_sections): Create .sframe section for
> .plt section.
> (elf_s390_late_size_sections): Call
> _bfd_s390_elf_create_sframe_plt and
> _bfd_s390_elf_write_sframe_plt.
> (elf_s390_finish_dynamic_sections): Write .plt section start
> into .sframe FDE covering .plt section. Call
> _bfd_elf_merge_section_sframe on htab->plt_sframe.
>
> ld/
> * NEWS: Add news entry.
>
> ld/testsuite/
> * ld-s390/s390.exp: Add new test.
> * ld-s390/sframe-plt-1.d: New linker-generated .sframe for .plt
> test.
> * ld-s390/sframe-simple-1.d: Adjust expected test output due to
> linker-generated .sframe for .plt.
>
> Signed-off-by: Jens Remus <jremus@linux.ibm.com>
Reviewed-by: Indu Bhagat <indu.bhagat@oracle.com>
> ---
>
> Notes (jremus):
> Changes in V3:
> - Reword commit message, as use of term "NULL" FRE was wrong. (Indu)
>
> Changes in V2:
> - Return bool from elf_s390_finish_dynamic_sections. (Indu)
> - Use "s390" instead of "s390x" in helper functions to align to
> existing code in elf64-s390.c.
> - Rename s390x-specific macros from *_S390_* to *_S390X_*. (Indu)
> - Reword NEWS ("s390 64-bit (s390x)").
>
> bfd/elf64-s390.c | 259 +++++++++++++++++++++++++
> ld/NEWS | 4 +
> ld/testsuite/ld-s390/s390.exp | 1 +
> ld/testsuite/ld-s390/sframe-plt-1.d | 28 +++
> ld/testsuite/ld-s390/sframe-simple-1.d | 9 +-
> 5 files changed, 297 insertions(+), 4 deletions(-)
> create mode 100644 ld/testsuite/ld-s390/sframe-plt-1.d
>
> diff --git a/bfd/elf64-s390.c b/bfd/elf64-s390.c
> index e758d087a61b..bd4ffcc9d13a 100644
> --- a/bfd/elf64-s390.c
> +++ b/bfd/elf64-s390.c
> @@ -27,6 +27,8 @@
> #include "elf/s390.h"
> #include "elf-s390.h"
> #include "dwarf2.h"
> +#include "sframe.h"
> +#include "sframe-api.h"
> #include <stdarg.h>
>
> /* In case we're on a 32-bit machine, construct a 64-bit "-1" value
> @@ -594,6 +596,49 @@ static const bfd_byte elf_s390x_eh_frame_plt[] =
> DW_CFA_nop, DW_CFA_nop, DW_CFA_nop
> };
>
> +/* .sframe covering the .plt section. */
> +
> +/* This must be the same as sframe_get_hdr_size (sfh). For s390x, this value
> + is the same as sizeof (sframe_header) because there is no SFrame auxilliary
> + header. */
> +#define PLT_SFRAME_FDE_START_OFFSET sizeof (sframe_header)
> +
> +#define SFRAME_PLT0_MAX_NUM_FRES 1
> +#define SFRAME_PLTN_MAX_NUM_FRES 1
> +
> +struct elf_s390x_sframe_plt
> +{
> + unsigned int plt0_entry_size;
> + unsigned int plt0_num_fres;
> + const sframe_frame_row_entry *plt0_fres[SFRAME_PLT0_MAX_NUM_FRES];
> +
> + unsigned int pltn_entry_size;
> + unsigned int pltn_num_fres;
> + const sframe_frame_row_entry *pltn_fres[SFRAME_PLTN_MAX_NUM_FRES];
> +};
> +
> +/* .sframe FRE covering the PLT0/PLTn .plt section entry. */
> +static const sframe_frame_row_entry elf_s390x_sframe_plt_fre =
> +{
> + 0, /* SFrame FRE start address. */
> + {0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 12 bytes. */
> + SFRAME_V1_FRE_INFO (SFRAME_BASE_REG_SP, 1, SFRAME_FRE_OFFSET_2B) /* FRE info. */
> +};
> +
> +/* SFrame helper object for PLT. */
> +static const struct elf_s390x_sframe_plt elf_s390x_sframe_plt =
> +{
> + PLT_FIRST_ENTRY_SIZE,
> + 1, /* Number of FREs for PLT0. */
> + /* Array of SFrame FREs for PLT0. */
> + { &elf_s390x_sframe_plt_fre },
> +
> + PLT_ENTRY_SIZE,
> + 1, /* Number of FREs for PLTn. */
> + /* Array of SFrame FREs for PLTn. */
> + { &elf_s390x_sframe_plt_fre },
> +};
> +
>
> /* s390 ELF linker hash entry. */
>
> @@ -688,6 +733,11 @@ struct elf_s390_link_hash_table
> asection *irelifunc;
> asection *plt_eh_frame;
>
> + sframe_encoder_ctx *plt_cfe_ctx;
> + asection *plt_sframe;
> + /* The .sframe helper object for .plt section. */
> + const struct elf_s390x_sframe_plt *sframe_plt;
> +
> union {
> bfd_signed_vma refcount;
> bfd_vma offset;
> @@ -1513,6 +1563,137 @@ elf_s390_adjust_dynamic_symbol (struct bfd_link_info *info,
> return _bfd_elf_adjust_dynamic_copy (info, h, s);
> }
>
> +/* Create SFrame stack trace info for the PLT entries in the .plt section. */
> +
> +static bool
> +_bfd_s390_elf_create_sframe_plt (struct bfd_link_info *info)
> +{
> + struct elf_s390_link_hash_table *htab;
> +
> + unsigned int plt0_entry_size;
> + unsigned char func_info;
> + uint32_t fre_type;
> + /* The dynamic plt section for which .sframe stack trace information is being
> + created. */
> + asection *dpltsec;
> +
> + int err = 0;
> +
> + sframe_encoder_ctx **ectx = NULL;
> + unsigned plt_entry_size = 0;
> + unsigned int num_pltn_fres = 0;
> + unsigned int num_pltn_entries = 0;
> + const sframe_frame_row_entry * const *pltn_fres;
> +
> + htab = elf_s390_hash_table (info);
> + ectx = &htab->plt_cfe_ctx;
> + dpltsec = htab->elf.splt;
> +
> + plt0_entry_size = htab->sframe_plt->plt0_entry_size;
> + plt_entry_size = htab->sframe_plt->pltn_entry_size;
> + pltn_fres = htab->sframe_plt->pltn_fres;
> + num_pltn_fres = htab->sframe_plt->pltn_num_fres;
> + num_pltn_entries = (dpltsec->size - plt0_entry_size) / plt_entry_size;
> +
> + *ectx = sframe_encode (SFRAME_VERSION_2,
> + 0,
> + SFRAME_ABI_S390X_ENDIAN_BIG,
> + SFRAME_CFA_FIXED_FP_INVALID,
> + SFRAME_CFA_FIXED_RA_INVALID,
> + &err);
> +
> + /* FRE type is dependent on the size of the function. */
> + fre_type = sframe_calc_fre_type (dpltsec->size);
> + func_info = sframe_fde_create_func_info (fre_type, SFRAME_FDE_TYPE_PCINC);
> +
> + /* Add SFrame FDE and the associated FREs for PLT0 if PLT0 has been
> + generated. */
> + if (plt0_entry_size)
> + {
> + /* Add SFrame FDE for PLT0, the function start address is updated later
> + at _bfd_elf_merge_section_sframe time. */
> + sframe_encoder_add_funcdesc_v2 (*ectx,
> + 0, /* func start addr. */
> + plt0_entry_size,
> + func_info,
> + 0, /* Rep block size. */
> + 0 /* Num FREs. */);
> + sframe_frame_row_entry plt0_fre;
> + unsigned int num_plt0_fres = htab->sframe_plt->plt0_num_fres;
> + for (unsigned int j = 0; j < num_plt0_fres; j++)
> + {
> + plt0_fre = *(htab->sframe_plt->plt0_fres[j]);
> + sframe_encoder_add_fre (*ectx, 0, &plt0_fre);
> + }
> + }
> +
> + if (num_pltn_entries)
> + {
> + /* PLTn entries use an SFrame FDE of type
> + SFRAME_FDE_TYPE_PCMASK to exploit the repetitive
> + pattern of the instructions in these entries. Using this SFrame FDE
> + type helps in keeping the SFrame stack trace info for PLTn entries
> + compact. */
> + func_info = sframe_fde_create_func_info (fre_type,
> + SFRAME_FDE_TYPE_PCMASK);
> + /* Add the SFrame FDE for all PCs starting at the first PLTn entry (hence,
> + function start address = plt0_entry_size. As usual, this will be
> + updated later at _bfd_elf_merge_section_sframe, by when the
> + sections are relocated. */
> + sframe_encoder_add_funcdesc_v2 (*ectx,
> + plt0_entry_size, /* func start addr. */
> + dpltsec->size - plt0_entry_size,
> + func_info,
> + plt_entry_size,
> + 0 /* Num FREs. */);
> +
> + sframe_frame_row_entry pltn_fre;
> + /* Now add the FREs for PLTn. Simply adding the FREs suffices due
> + to the usage of SFRAME_FDE_TYPE_PCMASK above. */
> + for (unsigned int j = 0; j < num_pltn_fres; j++)
> + {
> + unsigned int func_idx = plt0_entry_size ? 1 : 0;
> + pltn_fre = *(pltn_fres[j]);
> + sframe_encoder_add_fre (*ectx, func_idx, &pltn_fre);
> + }
> + }
> +
> + return true;
> +}
> +
> +/* Write contents of the .sframe section. */
> +
> +static bool
> +_bfd_s390_elf_write_sframe_plt (struct bfd_link_info *info)
> +{
> + struct elf_s390_link_hash_table *htab;
> + sframe_encoder_ctx *ectx;
> + size_t sec_size;
> + asection *sec;
> + bfd *dynobj;
> +
> + int err = 0;
> +
> + htab = elf_s390_hash_table (info);
> + dynobj = htab->elf.dynobj;
> +
> + ectx = htab->plt_cfe_ctx;
> + sec = htab->plt_sframe;
> +
> + BFD_ASSERT (ectx);
> +
> + void *contents = sframe_encoder_write (ectx, &sec_size, &err);
> +
> + sec->size = (bfd_size_type) sec_size;
> + sec->contents = (unsigned char *) bfd_zalloc (dynobj, sec->size);
> + sec->alloced = 1;
> + memcpy (sec->contents, contents, sec_size);
> +
> + sframe_encoder_free (&ectx);
> +
> + return true;
> +}
> +
> /* Allocate space in .plt, .got and associated reloc sections for
> dynamic relocs. */
>
> @@ -1892,6 +2073,25 @@ elf_s390_late_size_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
> htab->plt_eh_frame->size = sizeof (elf_s390x_eh_frame_plt);
> }
>
> + /* No need to size the .sframe section explicitly because the write-out
> + mechanism is different. Simply prep up the FDE/FRE for the
> + .plt section. */
> + if (_bfd_elf_sframe_present (info))
> + {
> + if (htab->plt_sframe != NULL
> + && htab->elf.splt != NULL
> + && htab->elf.splt->size != 0
> + && !bfd_is_abs_section (htab->elf.splt->output_section))
> + {
> + _bfd_s390_elf_create_sframe_plt (info);
> + /* FIXME - Dirty Hack. Set the size to something non-zero for now,
> + so that the section does not get stripped out below. The precise
> + size of this section is known only when the contents are
> + serialized in _bfd_s390x_elf_write_sframe_plt. */
> + htab->plt_sframe->size = sizeof (sframe_header) + 1;
> + }
> + }
> +
> /* We now have determined the sizes of the various dynamic sections.
> Allocate memory for them. */
> relocs = false;
> @@ -1904,6 +2104,7 @@ elf_s390_late_size_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
> || s == htab->elf.sgot
> || s == htab->elf.sgotplt
> || s == htab->plt_eh_frame
> + || s == htab->plt_sframe
> || s == htab->elf.sdynbss
> || s == htab->elf.sdynrelro
> || s == htab->elf.iplt
> @@ -1960,6 +2161,11 @@ elf_s390_late_size_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
> if ((s->flags & SEC_HAS_CONTENTS) == 0)
> continue;
>
> + /* Skip allocating contents for .sframe section as it is written
> + out differently. See below. */
> + if (s == htab->plt_sframe)
> + continue;
> +
> /* Allocate memory for the section contents. We use bfd_zalloc
> here in case unused entries are not reclaimed before the
> section's contents are written out. This should not happen,
> @@ -1981,6 +2187,15 @@ elf_s390_late_size_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
> htab->plt_eh_frame->contents + PLT_FDE_LEN_OFFSET);
> }
>
> + if (_bfd_elf_sframe_present (info))
> + {
> + if (htab->plt_sframe != NULL
> + && htab->elf.splt != NULL
> + && htab->elf.splt->size != 0
> + && htab->plt_sframe->contents == NULL)
> + _bfd_s390_elf_write_sframe_plt (info);
> + }
> +
> return _bfd_elf_add_dynamic_tags (output_bfd, info, relocs);
> }
>
> @@ -3781,6 +3996,34 @@ elf_s390_finish_dynamic_sections (bfd *output_bfd,
> }
> }
>
> + /* Make any adjustment if necessary and merge .sframe section to
> + create the final .sframe section for output_bfd. */
> + if (htab->plt_sframe != NULL
> + && htab->plt_sframe->contents != NULL)
> + {
> + if (htab->elf.splt != NULL
> + && htab->elf.splt->size != 0
> + && (htab->elf.splt->flags & SEC_EXCLUDE) == 0
> + && htab->elf.splt->output_section != NULL
> + && htab->plt_sframe->output_section != NULL)
> + {
> + bfd_vma plt_start = htab->elf.splt->output_section->vma;
> + bfd_vma sframe_start = htab->plt_sframe->output_section->vma
> + + htab->plt_sframe->output_offset
> + + PLT_SFRAME_FDE_START_OFFSET;
> + bfd_put_signed_32 (dynobj, plt_start - sframe_start,
> + htab->plt_sframe->contents
> + + PLT_SFRAME_FDE_START_OFFSET);
> + }
> + if (htab->plt_sframe->sec_info_type == SEC_INFO_TYPE_SFRAME)
> + {
> + if (! _bfd_elf_merge_section_sframe (output_bfd, info,
> + htab->plt_sframe,
> + htab->plt_sframe->contents))
> + return false;
> + }
> + }
> +
> return true;
> }
>
> @@ -4021,6 +4264,8 @@ elf_s390_create_dynamic_sections (bfd *dynobj,
> if (htab == NULL)
> return false;
>
> + htab->sframe_plt = &elf_s390x_sframe_plt;
> +
> if (htab->elf.splt != NULL)
> {
> /* Create .eh_frame section for .plt section. */
> @@ -4041,6 +4286,20 @@ elf_s390_create_dynamic_sections (bfd *dynobj,
> return false;
> }
> }
> +
> + /* Create .sframe section for .plt section. */
> + if (!info->no_ld_generated_unwind_info)
> + {
> + flagword flags = (SEC_ALLOC | SEC_LOAD | SEC_READONLY
> + | SEC_HAS_CONTENTS | SEC_IN_MEMORY
> + | SEC_LINKER_CREATED);
> +
> + htab->plt_sframe = bfd_make_section_anyway_with_flags (dynobj,
> + ".sframe",
> + flags);
> + if (htab->plt_sframe == NULL)
> + return false;
> + }
> }
>
> return true;
> diff --git a/ld/NEWS b/ld/NEWS
> index b101dec4ed52..6e3b8fddf0b4 100644
> --- a/ld/NEWS
> +++ b/ld/NEWS
> @@ -1,5 +1,9 @@
> -*- text -*-
>
> +* On s390 64-bit (s390x), generate SFrame stack trace information (.sframe)
> + for the linker generated .plt section. Enabled by default. Can be disabled
> + using linker option --no-ld-generated-unwind-info.
> +
> * On RISC-V, add new PLT formats, and GNU property merge rules for zicfiss and
> zicfilp extensions.
>
> diff --git a/ld/testsuite/ld-s390/s390.exp b/ld/testsuite/ld-s390/s390.exp
> index c41acf10ca1c..452811dcb2ba 100644
> --- a/ld/testsuite/ld-s390/s390.exp
> +++ b/ld/testsuite/ld-s390/s390.exp
> @@ -149,5 +149,6 @@ if [istarget "s390x-*-*"] {
>
> if { ![skip_sframe_tests] } {
> run_dump_test "sframe-simple-1"
> + run_dump_test "sframe-plt-1"
> }
> }
> diff --git a/ld/testsuite/ld-s390/sframe-plt-1.d b/ld/testsuite/ld-s390/sframe-plt-1.d
> new file mode 100644
> index 000000000000..67b11bd3dcf4
> --- /dev/null
> +++ b/ld/testsuite/ld-s390/sframe-plt-1.d
> @@ -0,0 +1,28 @@
> +#as: --gsframe
> +#source: sframe-foo.s
> +#source: sframe-bar.s
> +#objdump: --sframe=.sframe
> +#ld: -shared --no-rosegment
> +#name: SFrame for plt0 and pltN
> +
> +.*: +file format .*
> +
> +Contents of the SFrame section .sframe:
> + Header :
> +
> + Version: SFRAME_VERSION_2
> + Flags: SFRAME_F_FDE_SORTED
> + Num FDEs: 4
> + Num FREs: 8
> +
> + Function Index :
> +
> + func idx \[0\]: pc = 0x1e8, size = 32 bytes
> + STARTPC +CFA +FP +RA +
> + 0+1e8 +sp\+160 +u +u +
> +
> + func idx \[1\]: pc = 0x208, size = 32 bytes
> + STARTPC\[m\] +CFA +FP +RA +
> + 0+0 +sp\+160 +u +u +
> +
> +#...
> diff --git a/ld/testsuite/ld-s390/sframe-simple-1.d b/ld/testsuite/ld-s390/sframe-simple-1.d
> index 4df5d492f01e..2967278e9f86 100644
> --- a/ld/testsuite/ld-s390/sframe-simple-1.d
> +++ b/ld/testsuite/ld-s390/sframe-simple-1.d
> @@ -12,16 +12,17 @@ Contents of the SFrame section .sframe:
>
> Version: SFRAME_VERSION_2
> Flags: SFRAME_F_FDE_SORTED
> - Num FDEs: 2
> - Num FREs: 6
> + Num FDEs: 4
> + Num FREs: 8
>
> Function Index :
>
> - func idx \[0\]: pc = 0x228, size = 8 bytes
> +#...
> + func idx \[2\]: pc = 0x228, size = 8 bytes
> STARTPC +CFA +FP +RA +
> 0+228 +sp\+160 +u +u +
>
> - func idx \[1\]: pc = 0x230, size = 42 bytes
> + func idx \[3\]: pc = 0x230, size = 42 bytes
> STARTPC +CFA +FP +RA +
> 0+230 +sp\+160 +u +u +
> 0+236 +sp\+160 +u +c-48 +
More information about the Binutils
mailing list