|  | /* IBM S/390-specific support for ELF 32 and 64 bit functions | 
|  | Copyright (C) 2000-2025 Free Software Foundation, Inc. | 
|  | Contributed by Andreas Krebbel. | 
|  |  | 
|  | This file is part of BFD, the Binary File Descriptor library. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program; if not, write to the Free Software | 
|  | Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA | 
|  | 02110-1301, USA.  */ | 
|  |  | 
|  |  | 
|  | /* Return TRUE if H is an IFUNC symbol.  Simply checking for the | 
|  | symbol type might not be enough since it might get changed to | 
|  | STT_FUNC for pointer equality reasons.  */ | 
|  | static inline bool | 
|  | s390_is_ifunc_symbol_p (struct elf_link_hash_entry *h) | 
|  | { | 
|  | struct elf_s390_link_hash_entry *eh = (struct elf_s390_link_hash_entry*)h; | 
|  | return h->type == STT_GNU_IFUNC || eh->ifunc_resolver_address != 0; | 
|  | } | 
|  |  | 
|  | /* Return true if .got.plt is supposed to be emitted after .got.  */ | 
|  |  | 
|  | static inline bool | 
|  | s390_gotplt_after_got_p (struct bfd_link_info *info) | 
|  | { | 
|  | struct elf_s390_link_hash_table *htab = elf_s390_hash_table (info); | 
|  |  | 
|  | if (!htab->elf.sgot || !htab->elf.sgotplt) | 
|  | return true; | 
|  |  | 
|  | if (htab->elf.sgot->output_section == htab->elf.sgotplt->output_section) | 
|  | { | 
|  | if (htab->elf.sgot->output_offset < htab->elf.sgotplt->output_offset) | 
|  | return true; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (htab->elf.sgot->output_section->vma | 
|  | <= htab->elf.sgotplt->output_section->vma) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Return the value of the _GLOBAL_OFFSET_TABLE_ symbol.  */ | 
|  |  | 
|  | static inline bfd_vma | 
|  | s390_got_pointer (struct bfd_link_info *info) | 
|  | { | 
|  | struct elf_s390_link_hash_table *htab = elf_s390_hash_table (info); | 
|  | bfd_vma got_pointer; | 
|  |  | 
|  | BFD_ASSERT (htab && htab->elf.hgot); | 
|  |  | 
|  | got_pointer = (htab->elf.hgot->root.u.def.section->output_section->vma | 
|  | + htab->elf.hgot->root.u.def.section->output_offset); | 
|  | /* Our ABI requires the GOT pointer to point at the very beginning | 
|  | of the global offset table.  */ | 
|  | BFD_ASSERT (got_pointer | 
|  | <= (htab->elf.sgot->output_section->vma | 
|  | + htab->elf.sgot->output_offset)); | 
|  | BFD_ASSERT (got_pointer | 
|  | <= (htab->elf.sgotplt->output_section->vma | 
|  | + htab->elf.sgotplt->output_offset)); | 
|  |  | 
|  | return got_pointer; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Return the offset of the .got versus _GLOBAL_OFFSET_TABLE_.  */ | 
|  |  | 
|  | static inline bfd_vma | 
|  | s390_got_offset (struct bfd_link_info *info) | 
|  | { | 
|  | struct elf_s390_link_hash_table *htab = elf_s390_hash_table (info); | 
|  |  | 
|  | /* The absolute address of the .got in the target image.  */ | 
|  | bfd_vma got_address = (htab->elf.sgot->output_section->vma | 
|  | + htab->elf.sgot->output_offset); | 
|  |  | 
|  | /* GOT offset must not be negative.  */ | 
|  | BFD_ASSERT (s390_got_pointer (info) <= got_address); | 
|  | return got_address - s390_got_pointer (info); | 
|  | } | 
|  |  | 
|  | /* Return the offset of the .got.plt versus _GLOBAL_OFFSET_TABLE_.  */ | 
|  |  | 
|  | static inline bfd_vma | 
|  | s390_gotplt_offset (struct bfd_link_info *info) | 
|  | { | 
|  | struct elf_s390_link_hash_table *htab = elf_s390_hash_table (info); | 
|  |  | 
|  | /* The absolute address of the .got.plt in the target image.  */ | 
|  | bfd_vma gotplt_address = (htab->elf.sgotplt->output_section->vma | 
|  | + htab->elf.sgotplt->output_offset); | 
|  |  | 
|  | /* GOT offset must not be negative.  */ | 
|  | BFD_ASSERT (s390_got_pointer (info) <= gotplt_address); | 
|  | return gotplt_address - s390_got_pointer (info); | 
|  | } | 
|  |  | 
|  | /* Create sections needed by STT_GNU_IFUNC symbol.  */ | 
|  |  | 
|  | static bool | 
|  | s390_elf_create_ifunc_sections (bfd *abfd, struct bfd_link_info *info) | 
|  | { | 
|  | flagword flags; | 
|  | asection *s; | 
|  | const struct elf_backend_data *bed = get_elf_backend_data (abfd); | 
|  | struct elf_link_hash_table *htab = elf_hash_table (info); | 
|  |  | 
|  | if (htab->iplt != NULL) | 
|  | return true; | 
|  |  | 
|  | flags = bed->dynamic_sec_flags; | 
|  |  | 
|  | if (bfd_link_pic (info)) | 
|  | { | 
|  | s = bfd_make_section_with_flags (abfd, ".rela.ifunc", | 
|  | flags | SEC_READONLY); | 
|  | if (s == NULL | 
|  | || !bfd_set_section_alignment (s, bed->s->log_file_align)) | 
|  | return false; | 
|  | htab->irelifunc = s; | 
|  | } | 
|  |  | 
|  | /* Create .iplt, .rel[a].iplt, and .igot.plt.  */ | 
|  | s = bfd_make_section_with_flags (abfd, ".iplt", | 
|  | flags | SEC_CODE | SEC_READONLY); | 
|  | if (s == NULL | 
|  | || !bfd_set_section_alignment (s, bed->plt_alignment)) | 
|  | return false; | 
|  | htab->iplt = s; | 
|  |  | 
|  | s = bfd_make_section_with_flags (abfd, ".rela.iplt", flags | SEC_READONLY); | 
|  | if (s == NULL | 
|  | || !bfd_set_section_alignment (s, bed->s->log_file_align)) | 
|  | return false; | 
|  | htab->irelplt = s; | 
|  |  | 
|  | s = bfd_make_section_with_flags (abfd, ".igot.plt", flags); | 
|  | if (s == NULL | 
|  | || !bfd_set_section_alignment (s, bed->s->log_file_align)) | 
|  | return false; | 
|  | htab->igotplt = s; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Allocate space in .plt, .got and associated reloc sections for | 
|  | dynamic relocs against a STT_GNU_IFUNC symbol definition.  */ | 
|  |  | 
|  | static bool | 
|  | s390_elf_allocate_ifunc_dyn_relocs (struct bfd_link_info *info, | 
|  | struct elf_link_hash_entry *h) | 
|  | { | 
|  | struct elf_dyn_relocs *p; | 
|  | struct elf_link_hash_table *htab; | 
|  | struct elf_s390_link_hash_entry *eh = (struct elf_s390_link_hash_entry*)h; | 
|  | struct elf_dyn_relocs **head = &h->dyn_relocs; | 
|  |  | 
|  | htab = elf_hash_table (info); | 
|  | eh->ifunc_resolver_address = h->root.u.def.value; | 
|  | eh->ifunc_resolver_section = h->root.u.def.section; | 
|  |  | 
|  | /* Support garbage collection against STT_GNU_IFUNC symbols.  */ | 
|  | if (h->plt.refcount <= 0 && h->got.refcount <= 0) | 
|  | { | 
|  | /* When building shared library, we need to handle the case | 
|  | where it is marked with regular reference, but not non-GOT | 
|  | reference.  It may happen if we didn't see STT_GNU_IFUNC | 
|  | symbol at the time when checking relocations.  */ | 
|  | if (bfd_link_pic (info) | 
|  | && !h->non_got_ref | 
|  | && h->ref_regular) | 
|  | for (p = *head; p != NULL; p = p->next) | 
|  | if (p->count) | 
|  | { | 
|  | h->non_got_ref = 1; | 
|  | goto keep; | 
|  | } | 
|  |  | 
|  | h->got = htab->init_got_offset; | 
|  | h->plt = htab->init_plt_offset; | 
|  | *head = NULL; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Return and discard space for dynamic relocations against it if | 
|  | it is never referenced in a non-shared object.  */ | 
|  | if (!h->ref_regular) | 
|  | { | 
|  | if (h->plt.refcount > 0 | 
|  | || h->got.refcount > 0) | 
|  | abort (); | 
|  | h->got = htab->init_got_offset; | 
|  | h->plt = htab->init_plt_offset; | 
|  | *head = NULL; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | keep: | 
|  | /* Without checking h->plt.refcount here we allocate a PLT slot. | 
|  | When setting plt.refcount in check_relocs it might not have been | 
|  | known that this will be an IFUNC symol.  */ | 
|  | h->plt.offset = htab->iplt->size; | 
|  | h->needs_plt = 1; | 
|  | htab->iplt->size += PLT_ENTRY_SIZE; | 
|  | htab->igotplt->size += GOT_ENTRY_SIZE; | 
|  | htab->irelplt->size += RELA_ENTRY_SIZE; | 
|  | htab->irelplt->reloc_count++; | 
|  |  | 
|  | /* In order to make pointer equality work with IFUNC symbols defined | 
|  | in a non-PIE executable and referenced in a shared lib, we turn | 
|  | the symbol into a STT_FUNC symbol and make the symbol value to | 
|  | point to the IPLT slot.  That way the referencing shared lib will | 
|  | always get the PLT slot address when resolving the respective | 
|  | R_390_GLOB_DAT/R_390_64 relocs on that symbol.  */ | 
|  | if (bfd_link_pde (info) | 
|  | && h->def_regular | 
|  | && h->ref_dynamic) | 
|  | { | 
|  | h->root.u.def.section = htab->iplt; | 
|  | h->root.u.def.value = h->plt.offset; | 
|  | h->size = PLT_ENTRY_SIZE; | 
|  | h->type = STT_FUNC; | 
|  | } | 
|  |  | 
|  | if (!bfd_link_pic (info)) | 
|  | *head = NULL; | 
|  |  | 
|  | /* Finally, allocate space.  */ | 
|  | p = *head; | 
|  | if (p != NULL) | 
|  | { | 
|  | bfd_size_type count = 0; | 
|  | do | 
|  | { | 
|  | count += p->count; | 
|  | p = p->next; | 
|  | } | 
|  | while (p != NULL); | 
|  | htab->irelifunc->size += count * RELA_ENTRY_SIZE; | 
|  | } | 
|  |  | 
|  | /* Decide whether the got.iplt slot can be used.  This has to be | 
|  | avoided if the values in the GOT slots could differ for pointer | 
|  | equality reasons.  */ | 
|  | if (h->got.refcount <= 0 | 
|  | || (bfd_link_pic (info) | 
|  | && (h->dynindx == -1 || h->forced_local)) | 
|  | || bfd_link_pie (info) | 
|  | || htab->sgot == NULL) | 
|  | { | 
|  | /* Use .got.iplt.  */ | 
|  | h->got.offset = (bfd_vma) -1; | 
|  | } | 
|  | else | 
|  | { | 
|  | h->got.offset = htab->sgot->size; | 
|  | htab->sgot->size += GOT_ENTRY_SIZE; | 
|  | if (bfd_link_pic (info)) | 
|  | htab->srelgot->size += RELA_ENTRY_SIZE; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | elf_s390_allocate_local_syminfo (bfd *abfd, Elf_Internal_Shdr *symtab_hdr) | 
|  | { | 
|  | bfd_size_type size; | 
|  |  | 
|  | size = symtab_hdr->sh_info; | 
|  | size *= (sizeof (bfd_signed_vma)	 /* local got */ | 
|  | + sizeof (struct plt_entry)	 /* local plt */ | 
|  | + sizeof(char));		 /* local tls type */ | 
|  | elf_local_got_refcounts (abfd) = ((bfd_signed_vma *) | 
|  | bfd_zalloc (abfd, size)); | 
|  | if (elf_local_got_refcounts (abfd) == NULL) | 
|  | return false; | 
|  | elf_s390_local_plt (abfd) | 
|  | = (struct plt_entry*)(elf_local_got_refcounts (abfd) | 
|  | + symtab_hdr->sh_info); | 
|  | elf_s390_local_got_tls_type (abfd) | 
|  | = (char *) (elf_s390_local_plt (abfd) + symtab_hdr->sh_info); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Whether to sort relocs output by ld -r or ld --emit-relocs, by | 
|  | r_offset.  Don't do so for code sections.  We want to keep ordering | 
|  | of GDCALL / PLT32DBL for TLS optimizations as is.  On the other | 
|  | hand, elf-eh-frame.c processing requires .eh_frame relocs to be | 
|  | sorted.  */ | 
|  |  | 
|  | static bool | 
|  | elf_s390_elf_sort_relocs_p (asection *sec) | 
|  | { | 
|  | return (sec->flags & SEC_CODE) == 0; | 
|  | } | 
|  |  | 
|  | /* Merge object attributes from IBFD into OBFD.  Raise an error if | 
|  | there are conflicting attributes.  */ | 
|  | static bool | 
|  | elf_s390_merge_obj_attributes (bfd *ibfd, struct bfd_link_info *info) | 
|  | { | 
|  | bfd *obfd = info->output_bfd; | 
|  | obj_attribute *in_attr, *in_attrs; | 
|  | obj_attribute *out_attr, *out_attrs; | 
|  |  | 
|  | if (!elf_known_obj_attributes_proc (obfd)[0].i) | 
|  | { | 
|  | /* This is the first object.  Copy the attributes.  */ | 
|  | _bfd_elf_copy_obj_attributes (ibfd, obfd); | 
|  |  | 
|  | /* Use the Tag_null value to indicate the attributes have been | 
|  | initialized.  */ | 
|  | elf_known_obj_attributes_proc (obfd)[0].i = 1; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | in_attrs = elf_known_obj_attributes (ibfd)[OBJ_ATTR_GNU]; | 
|  | out_attrs = elf_known_obj_attributes (obfd)[OBJ_ATTR_GNU]; | 
|  |  | 
|  | /* Check for conflicting Tag_GNU_S390_ABI_Vector attributes and | 
|  | merge non-conflicting ones.  */ | 
|  | in_attr = &in_attrs[Tag_GNU_S390_ABI_Vector]; | 
|  | out_attr = &out_attrs[Tag_GNU_S390_ABI_Vector]; | 
|  |  | 
|  | if (in_attr->i > 2) | 
|  | _bfd_error_handler | 
|  | /* xgettext:c-format */ | 
|  | (_("warning: %pB uses unknown vector ABI %d"), ibfd, | 
|  | in_attr->i); | 
|  | else if (out_attr->i > 2) | 
|  | _bfd_error_handler | 
|  | /* xgettext:c-format */ | 
|  | (_("warning: %pB uses unknown vector ABI %d"), obfd, | 
|  | out_attr->i); | 
|  | else if (in_attr->i != out_attr->i) | 
|  | { | 
|  | out_attr->type = ATTR_TYPE_FLAG_INT_VAL; | 
|  |  | 
|  | if (in_attr->i && out_attr->i) | 
|  | { | 
|  | const char abi_str[3][9] = { "none", "software", "hardware" }; | 
|  |  | 
|  | _bfd_error_handler | 
|  | /* xgettext:c-format */ | 
|  | (_("warning: %pB uses vector %s ABI, %pB uses %s ABI"), | 
|  | ibfd, abi_str[in_attr->i], obfd, abi_str[out_attr->i]); | 
|  | } | 
|  | if (in_attr->i > out_attr->i) | 
|  | out_attr->i = in_attr->i; | 
|  | } | 
|  |  | 
|  | /* Merge Tag_compatibility attributes and any common GNU ones.  */ | 
|  | _bfd_elf_merge_object_attributes (ibfd, info); | 
|  |  | 
|  | return true; | 
|  | } |