From 22472454fab8ce0596d68a78431864812d769dfb Mon Sep 17 00:00:00 2001 From: Jens Remus Date: Fri, 11 Jul 2025 10:29:40 +0200 Subject: [PATCH] s390: Add SFrame stack trace information for .plt section 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 --- bfd/elf64-s390.c | 259 +++++++++++++++++++++++++ ld/NEWS | 4 + ld/testsuite/ld-s390/s390.exp | 1 + ld/testsuite/ld-s390/sframe-plt-1.d | 29 +++ ld/testsuite/ld-s390/sframe-simple-1.d | 9 +- 5 files changed, 298 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 e758d087a61..491070da803 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 /* 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, + SFRAME_F_FDE_FUNC_START_PCREL, + 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 74abb54254d..85c74a96441 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. + * All SFrame sections generated by the linker have the header flag SFRAME_F_FDE_FUNC_START_PCREL set to indicate the new encoding for sfde_func_start_address field in the SFrame section. Relocatable SFrame diff --git a/ld/testsuite/ld-s390/s390.exp b/ld/testsuite/ld-s390/s390.exp index c41acf10ca1..452811dcb2b 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 00000000000..db09030cdc8 --- /dev/null +++ b/ld/testsuite/ld-s390/sframe-plt-1.d @@ -0,0 +1,29 @@ +#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, + SFRAME_F_FDE_FUNC_START_PCREL + 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 353dbce6ee2..1c1bf324690 100644 --- a/ld/testsuite/ld-s390/sframe-simple-1.d +++ b/ld/testsuite/ld-s390/sframe-simple-1.d @@ -13,16 +13,17 @@ Contents of the SFrame section .sframe: Version: SFRAME_VERSION_2 Flags: SFRAME_F_FDE_SORTED, SFRAME_F_FDE_FUNC_START_PCREL - 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 + -- 2.47.2