2012-07-13 Andreas Krebbel <Andreas.Krebbel@de.ibm.com>
authorAndreas Krebbel <Andreas.Krebbel@de.ibm.com>
Fri, 13 Jul 2012 15:47:27 +0000 (15:47 +0000)
committerAndreas Krebbel <Andreas.Krebbel@de.ibm.com>
Fri, 13 Jul 2012 15:47:27 +0000 (15:47 +0000)
* elf64-s390.c: Include elf-s390-common.c.
(R_390_IRELATIVE): New reloc.
(elf_s390_reloc_type_lookup): Support R_390_IRELATIVE.
(RELA_ENTRY_SIZE): New macro.
(elf_s390_link_hash_entry): New fields ifunc_resolver_address and
*ifunc_resolver_section.
(struct plt_entry): New struct.
(struct elf_s390_obj_tdata): New field local_plt.
(elf_s390_local_plt): New macro.
(struct elf_s390_link_hash_table): New field irelifunc.
(ELF64): New macro.
(link_hash_newfunc): Initialize new fields.
(elf_s390_check_relocs): Handle IFUNC symbols.
(elf_s390_adjust_dynamic_symbol): Don't do anything for IFUNC
symbols.
(allocate_dynrelocs): Call s390_elf_allocate_ifunc_dyn_relocs for
IFUNC symbols.
(elf_s390_size_dynamic_sections): Handle IFUNC symbols.
(elf_s390_relocate_section): Likewise.
(elf_s390_finish_dynamic_symbol): Likewise.
(elf_s390_finish_dynamic_sections): Handle local IFUNC symbols.
(elf_s390_finish_ifunc_symbol): New function.
(elf_s390_gc_sweep_hook): Handle local plt entries.
(elf_backend_add_symbol_hook): Define.
* elf32-s390.c: See elf64-s390.c changes.
* elf-s390-common.c: New file.
* bfd-in2.h (BFD_RELOC_390_IRELATIVE): New enum field.
* libbfd.h (BFD_RELOC_390_IRELATIVE): New entry for
BFD_RELOC_390_IRELATIVE.
* reloc.c (BFD_RELOC_390_IRELATIVE): Document new relocation.

2012-07-13  Andreas Krebbel  <Andreas.Krebbel@de.ibm.com>

* elf/s390.h (START_RELOC_NUMBERS): Define R_390_IRELATIVE reloc.

2012-07-13  Andreas Krebbel  <Andreas.Krebbel@de.ibm.com>

* emulparams/elf_s390.sh (IREL_IN_PLT): Define.
* emulparams/elf64_s390.sh (IREL_IN_PLT): Define.

12 files changed:
bfd/ChangeLog
bfd/bfd-in2.h
bfd/elf-s390-common.c [new file with mode: 0644]
bfd/elf32-s390.c
bfd/elf64-s390.c
bfd/libbfd.h
bfd/reloc.c
include/ChangeLog
include/elf/s390.h
ld/ChangeLog
ld/emulparams/elf64_s390.sh
ld/emulparams/elf_s390.sh

index 81da94426968c62b61319e4b5ba5e805022b8b73..da9e6a2e86e9d1ff5f5b9aa06426f732075a869e 100644 (file)
@@ -1,3 +1,36 @@
+2012-07-13  Andreas Krebbel  <Andreas.Krebbel@de.ibm.com>
+
+       * elf64-s390.c: Include elf-s390-common.c.
+       (R_390_IRELATIVE): New reloc.
+       (elf_s390_reloc_type_lookup): Support R_390_IRELATIVE.
+       (RELA_ENTRY_SIZE): New macro.
+       (elf_s390_link_hash_entry): New fields ifunc_resolver_address and
+       *ifunc_resolver_section.
+       (struct plt_entry): New struct.
+       (struct elf_s390_obj_tdata): New field local_plt.
+       (elf_s390_local_plt): New macro.
+       (struct elf_s390_link_hash_table): New field irelifunc.
+       (ELF64): New macro.
+       (link_hash_newfunc): Initialize new fields.
+       (elf_s390_check_relocs): Handle IFUNC symbols.
+       (elf_s390_adjust_dynamic_symbol): Don't do anything for IFUNC
+       symbols.
+       (allocate_dynrelocs): Call s390_elf_allocate_ifunc_dyn_relocs for
+       IFUNC symbols.
+       (elf_s390_size_dynamic_sections): Handle IFUNC symbols.
+       (elf_s390_relocate_section): Likewise.
+       (elf_s390_finish_dynamic_symbol): Likewise.
+       (elf_s390_finish_dynamic_sections): Handle local IFUNC symbols.
+       (elf_s390_finish_ifunc_symbol): New function.
+       (elf_s390_gc_sweep_hook): Handle local plt entries.
+       (elf_backend_add_symbol_hook): Define.
+       * elf32-s390.c: See elf64-s390.c changes.
+       * elf-s390-common.c: New file.
+       * bfd-in2.h (BFD_RELOC_390_IRELATIVE): New enum field.
+       * libbfd.h (BFD_RELOC_390_IRELATIVE): New entry for
+       BFD_RELOC_390_IRELATIVE.
+       * reloc.c (BFD_RELOC_390_IRELATIVE): Document new relocation.
+
 2012-07-13  Nick Clifton  <nickc@redhat.com>
 
        * aix386-core.c: Remove use of PTR and PARAMS macros.
index 11f3e497811d7d235436e5dfbfd4f70262e8f879..c1c5e90a37b5e1f17a8f24764aeab97c3256b126 100644 (file)
@@ -4293,6 +4293,7 @@ in .byte hlo8(symbol)  */
   BFD_RELOC_390_GOT20,
   BFD_RELOC_390_GOTPLT20,
   BFD_RELOC_390_TLS_GOTIE20,
+  BFD_RELOC_390_IRELATIVE,
 
 /* Score relocations
 Low 16 bit for load/store  */
diff --git a/bfd/elf-s390-common.c b/bfd/elf-s390-common.c
new file mode 100644 (file)
index 0000000..691f751
--- /dev/null
@@ -0,0 +1,243 @@
+/* IBM S/390-specific support for ELF 32 and 64 bit functions
+   Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+   2011, 2012 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 bfd_boolean
+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;
+}
+
+/* Create sections needed by STT_GNU_IFUNC symbol.  */
+
+static bfd_boolean
+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 (info->shared)
+    {
+      s = bfd_make_section_with_flags (abfd, ".rela.ifunc",
+                                      flags | SEC_READONLY);
+      if (s == NULL
+         || ! bfd_set_section_alignment (abfd, 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 (abfd, 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 (abfd, 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 (abfd, 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 bfd_boolean
+s390_elf_allocate_ifunc_dyn_relocs (struct bfd_link_info *info,
+                                   struct elf_link_hash_entry *h,
+                                   struct elf_dyn_relocs **head)
+{
+  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;
+
+  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 (info->shared
+         && !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 (info->executable && !info->shared && 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;
+    }
+
+  /* We need dynamic relocation for STT_GNU_IFUNC symbol only when
+     there is a non-GOT reference in a shared object.  */
+  if (!info->shared || !h->non_got_ref)
+    *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
+      || (info->shared
+         && (h->dynindx == -1 || h->forced_local))
+      || (info->executable && info->shared)
+      || 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 (info->shared)
+       htab->srelgot->size += RELA_ENTRY_SIZE;
+    }
+
+  return TRUE;
+}
+
+static bfd_boolean
+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;
+}
+
+/* Pick ELFOSABI_GNU if IFUNC symbols are used.  */
+
+static bfd_boolean
+elf_s390_add_symbol_hook (bfd *abfd,
+                         struct bfd_link_info *info,
+                         Elf_Internal_Sym *sym,
+                         const char **namep ATTRIBUTE_UNUSED,
+                         flagword *flagsp ATTRIBUTE_UNUSED,
+                         asection **secp ATTRIBUTE_UNUSED,
+                         bfd_vma *valp ATTRIBUTE_UNUSED)
+{
+  if ((abfd->flags & DYNAMIC) == 0
+      && ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC)
+    elf_tdata (info->output_bfd)->has_gnu_symbols = TRUE;
+
+  return TRUE;
+}
index cbf768df421d01708e0f3122108c21b4d420111a..8d654d5486705c5372021493543ae0c416ecc232 100644 (file)
@@ -159,6 +159,8 @@ static reloc_howto_type elf_howto_table[] =
        s390_elf_ldisp_reloc, "R_390_GOTPLT20", FALSE, 0,0x0fffff00, FALSE),
   HOWTO(R_390_TLS_GOTIE20, 0, 2, 20, FALSE, 8, complain_overflow_dont,
        s390_elf_ldisp_reloc, "R_390_TLS_GOTIE20", FALSE, 0,0x0fffff00, FALSE),
+  HOWTO(R_390_IRELATIVE, 0, 2, 32, TRUE, 0, complain_overflow_bitfield,
+       bfd_elf_generic_reloc, "R_390_IRELATIVE", FALSE, 0, 0xffffffff, FALSE),
 };
 
 /* GNU extension to record C++ vtable hierarchy.  */
@@ -271,6 +273,8 @@ elf_s390_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
       return &elf_howto_table[(int) R_390_GOTPLT20];
     case BFD_RELOC_390_TLS_GOTIE20:
       return &elf_howto_table[(int) R_390_TLS_GOTIE20];
+    case BFD_RELOC_390_IRELATIVE:
+      return &elf_howto_table[(int) R_390_IRELATIVE];
     case BFD_RELOC_VTABLE_INHERIT:
       return &elf32_s390_vtinherit_howto;
     case BFD_RELOC_VTABLE_ENTRY:
@@ -425,6 +429,8 @@ elf_s390_is_local_label_name (bfd *abfd, const char *name)
 
 #define GOT_ENTRY_SIZE 4
 
+#define RELA_ENTRY_SIZE sizeof (Elf32_External_Rela)
+
 /* The first three entries in a procedure linkage table are reserved,
    and the initial contents are unimportant (we zero them out).
    Subsequent entries look like this.  See the SVR4 ABI 386
@@ -639,17 +645,43 @@ struct elf_s390_link_hash_entry
 #define GOT_TLS_IE     3
 #define GOT_TLS_IE_NLT 4
   unsigned char tls_type;
+
+  /* For pointer equality reasons we might need to change the symbol
+     type from STT_GNU_IFUNC to STT_FUNC together with its value and
+     section entry.  So after alloc_dynrelocs only these values should
+     be used.  In order to check whether a symbol is IFUNC use
+     s390_is_ifunc_symbol_p.  */
+  bfd_vma ifunc_resolver_address;
+  asection *ifunc_resolver_section;
 };
 
 #define elf_s390_hash_entry(ent) \
   ((struct elf_s390_link_hash_entry *)(ent))
 
+/* This structure represents an entry in the local PLT list needed for
+   local IFUNC symbols.  */
+struct plt_entry
+{
+  /* The section of the local symbol.
+     Set in relocate_section and used in finish_dynamic_sections.  */
+  asection *sec;
+
+  union
+  {
+    bfd_signed_vma refcount;
+    bfd_vma offset;
+  } plt;
+};
+
 /* NOTE: Keep this structure in sync with
    the one declared in elf64-s390.c.  */
 struct elf_s390_obj_tdata
 {
   struct elf_obj_tdata root;
 
+  /* A local PLT is needed for ifunc symbols.  */
+  struct plt_entry *local_plt;
+
   /* TLS type for each local got entry.  */
   char *local_got_tls_type;
 };
@@ -657,6 +689,9 @@ struct elf_s390_obj_tdata
 #define elf_s390_tdata(abfd) \
   ((struct elf_s390_obj_tdata *) (abfd)->tdata.any)
 
+#define elf_s390_local_plt(abfd)               \
+  (elf_s390_tdata (abfd)->local_plt)
+
 #define elf_s390_local_got_tls_type(abfd) \
   (elf_s390_tdata (abfd)->local_got_tls_type)
 
@@ -688,6 +723,7 @@ struct elf_s390_link_hash_table
   /* Short-cuts to get to dynamic linker sections.  */
   asection *sdynbss;
   asection *srelbss;
+  asection *irelifunc;
 
   union
   {
@@ -705,6 +741,9 @@ struct elf_s390_link_hash_table
   (elf_hash_table_id ((struct elf_link_hash_table *) ((p)->hash)) \
   == S390_ELF_DATA ? ((struct elf_s390_link_hash_table *) ((p)->hash)) : NULL)
 
+#undef ELF64
+#include "elf-s390-common.c"
+
 /* Create an entry in an s390 ELF linker hash table.  */
 
 static struct bfd_hash_entry *
@@ -732,6 +771,8 @@ link_hash_newfunc (struct bfd_hash_entry *entry,
       eh->dyn_relocs = NULL;
       eh->gotplt_refcount = 0;
       eh->tls_type = GOT_UNKNOWN;
+      eh->ifunc_resolver_address = 0;
+      eh->ifunc_resolver_section = NULL;
     }
 
   return entry;
@@ -930,6 +971,7 @@ elf_s390_check_relocs (bfd *abfd,
   asection *sreloc;
   bfd_signed_vma *local_got_refcounts;
   int tls_type, old_tls_type;
+  Elf_Internal_Sym *isym;
 
   if (info->relocatable)
     return TRUE;
@@ -960,7 +1002,31 @@ elf_s390_check_relocs (bfd *abfd,
        }
 
       if (r_symndx < symtab_hdr->sh_info)
-       h = NULL;
+       {
+         /* A local symbol.  */
+         isym = bfd_sym_from_r_symndx (&htab->sym_cache,
+                                       abfd, r_symndx);
+         if (isym == NULL)
+           return FALSE;
+
+         if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+           {
+             struct plt_entry *plt;
+
+             if (!s390_elf_create_ifunc_sections (htab->elf.dynobj, info))
+               return FALSE;
+
+             if (local_got_refcounts == NULL)
+               {
+                 if (!elf_s390_allocate_local_syminfo (abfd, symtab_hdr))
+                   return FALSE;
+                 local_got_refcounts = elf_local_got_refcounts (abfd);
+               }
+             plt = elf_s390_local_plt (abfd);
+             plt[r_symndx].plt.refcount++;
+           }
+         h = NULL;
+       }
       else
        {
          h = sym_hashes[r_symndx - symtab_hdr->sh_info];
@@ -996,17 +1062,9 @@ elf_s390_check_relocs (bfd *abfd,
          if (h == NULL
              && local_got_refcounts == NULL)
            {
-             bfd_size_type size;
-
-             size = symtab_hdr->sh_info;
-             size *= (sizeof (bfd_signed_vma) + sizeof(char));
-             local_got_refcounts = ((bfd_signed_vma *)
-                                    bfd_zalloc (abfd, size));
-             if (local_got_refcounts == NULL)
+             if (!elf_s390_allocate_local_syminfo (abfd, symtab_hdr))
                return FALSE;
-             elf_local_got_refcounts (abfd) = local_got_refcounts;
-             elf_s390_local_got_tls_type (abfd)
-               = (char *) (local_got_refcounts + symtab_hdr->sh_info);
+             local_got_refcounts = elf_local_got_refcounts (abfd);
            }
          /* Fall through.  */
        case R_390_GOTOFF16:
@@ -1022,13 +1080,34 @@ elf_s390_check_relocs (bfd *abfd,
            }
        }
 
+      if (h != NULL)
+       {
+         if (htab->elf.dynobj == NULL)
+           htab->elf.dynobj = abfd;
+         if (!s390_elf_create_ifunc_sections (htab->elf.dynobj, info))
+           return FALSE;
+
+         /* Make sure an IFUNC symbol defined in a non-shared object
+            always gets a PLT slot.  */
+         if (s390_is_ifunc_symbol_p (h) && h->def_regular)
+           {
+             /* The symbol is called by the dynamic loader in order
+                to resolve the relocation.  So it is in fact also
+                referenced.  */
+             h->ref_regular = 1;
+             h->needs_plt = 1;
+           }
+       }
       switch (r_type)
        {
        case R_390_GOTOFF16:
        case R_390_GOTOFF32:
        case R_390_GOTPC:
        case R_390_GOTPCDBL:
-         /* Got is created, nothing to be done.  */
+         /* These relocs do not need a GOT slot.  They just load the
+            GOT pointer itself or address something else relative to
+            the GOT.  Since the GOT pointer has been set up above we
+            are done.  */
          break;
 
        case R_390_PLT16DBL:
@@ -1169,7 +1248,7 @@ elf_s390_check_relocs (bfd *abfd,
        case R_390_PC16DBL:
        case R_390_PC32DBL:
        case R_390_PC32:
-         if (h != NULL && !info->shared)
+         if (h != NULL)
            {
              /* If this reloc is in a read-only section, we might
                 need a copy reloc.  We can't check reliably at this
@@ -1179,9 +1258,12 @@ elf_s390_check_relocs (bfd *abfd,
                 adjust_dynamic_symbol.  */
              h->non_got_ref = 1;
 
-             /* We may need a .plt entry if the function this reloc
-                refers to is in a shared lib.  */
-             h->plt.refcount += 1;
+             if (!info->shared)
+               {
+                 /* We may need a .plt entry if the function this reloc
+                    refers to is in a shared lib.  */
+                 h->plt.refcount += 1;
+               }
            }
 
          /* If we are creating a shared library, and this is a reloc
@@ -1253,7 +1335,6 @@ elf_s390_check_relocs (bfd *abfd,
                     easily.  Oh well.  */
                  asection *s;
                  void *vpp;
-                 Elf_Internal_Sym *isym;
 
                  isym = bfd_sym_from_r_symndx (&htab->sym_cache,
                                                abfd, r_symndx);
@@ -1346,6 +1427,7 @@ elf_s390_gc_sweep_hook (bfd *abfd,
                        asection *sec,
                        const Elf_Internal_Rela *relocs)
 {
+  struct elf_s390_link_hash_table *htab;
   Elf_Internal_Shdr *symtab_hdr;
   struct elf_link_hash_entry **sym_hashes;
   bfd_signed_vma *local_got_refcounts;
@@ -1354,6 +1436,10 @@ elf_s390_gc_sweep_hook (bfd *abfd,
   if (info->relocatable)
     return TRUE;
 
+  htab = elf_s390_hash_table (info);
+  if (htab == NULL)
+    return FALSE;
+
   elf_section_data (sec)->local_dynrel = NULL;
 
   symtab_hdr = &elf_symtab_hdr (abfd);
@@ -1388,6 +1474,23 @@ elf_s390_gc_sweep_hook (bfd *abfd,
                break;
              }
        }
+      else
+       {
+         Elf_Internal_Sym *isym;
+
+         /* A local symbol.  */
+         isym = bfd_sym_from_r_symndx (&htab->sym_cache,
+                                       abfd, r_symndx);
+         if (isym == NULL)
+           return FALSE;
+
+         if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+           {
+             struct plt_entry *plt = elf_s390_local_plt (abfd);
+             if (plt[r_symndx].plt.refcount > 0)
+               plt[r_symndx].plt.refcount--;
+           }
+       }
 
       r_type = ELF32_R_TYPE (rel->r_info);
       r_type = elf_s390_tls_transition (info, r_type, h != NULL);
@@ -1513,6 +1616,10 @@ elf_s390_adjust_dynamic_symbol (struct bfd_link_info *info,
   struct elf_s390_link_hash_table *htab;
   asection *s;
 
+  /* STT_GNU_IFUNC symbol must go through PLT. */
+  if (s390_is_ifunc_symbol_p (h))
+    return TRUE;
+
   /* If this is a function, put it in the procedure linkage table.  We
      will fill in the contents of the procedure linkage table later
      (although we could actually do it here).  */
@@ -1636,7 +1743,7 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void * inf)
 {
   struct bfd_link_info *info;
   struct elf_s390_link_hash_table *htab;
-  struct elf_s390_link_hash_entry *eh;
+  struct elf_s390_link_hash_entry *eh = (struct elf_s390_link_hash_entry *)h;
   struct elf_dyn_relocs *p;
 
   if (h->root.type == bfd_link_hash_indirect)
@@ -1645,8 +1752,13 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void * inf)
   info = (struct bfd_link_info *) inf;
   htab = elf_s390_hash_table (info);
 
-  if (htab->elf.dynamic_sections_created
-      && h->plt.refcount > 0)
+  /* Since STT_GNU_IFUNC symbol must go through PLT, we handle it
+     here if it is defined and referenced in a non-shared object.  */
+  if (s390_is_ifunc_symbol_p (h) && h->def_regular)
+    return s390_elf_allocate_ifunc_dyn_relocs (info, h,
+                                              &eh->dyn_relocs);
+  else if (htab->elf.dynamic_sections_created
+          && h->plt.refcount > 0)
     {
       /* Make sure this symbol is output as a dynamic symbol.
         Undefined weak syms won't yet be marked as dynamic.  */
@@ -1763,7 +1875,6 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void * inf)
   else
     h->got.offset = (bfd_vma) -1;
 
-  eh = (struct elf_s390_link_hash_entry *) h;
   if (eh->dyn_relocs == NULL)
     return TRUE;
 
@@ -1918,6 +2029,8 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
       bfd_size_type locsymcount;
       Elf_Internal_Shdr *symtab_hdr;
       asection *srela;
+      struct plt_entry *local_plt;
+      unsigned int i;
 
       if (! is_s390_elf (ibfd))
        continue;
@@ -1970,6 +2083,19 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
          else
            *local_got = (bfd_vma) -1;
        }
+      local_plt = elf_s390_local_plt (ibfd);
+      for (i = 0; i < symtab_hdr->sh_info; i++)
+       {
+         if (local_plt[i].plt.refcount > 0)
+           {
+             local_plt[i].plt.offset = htab->elf.iplt->size;
+             htab->elf.iplt->size += PLT_ENTRY_SIZE;
+             htab->elf.igotplt->size += GOT_ENTRY_SIZE;
+             htab->elf.irelplt->size += RELA_ENTRY_SIZE;
+           }
+         else
+            local_plt[i].plt.offset = (bfd_vma) -1;
+       }
     }
 
   if (htab->tls_ldm_got.refcount > 0)
@@ -1998,7 +2124,10 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
       if (s == htab->elf.splt
          || s == htab->elf.sgot
          || s == htab->elf.sgotplt
-         || s == htab->sdynbss)
+         || s == htab->sdynbss
+         || s == htab->elf.iplt
+         || s == htab->elf.igotplt
+         || s == htab->irelifunc)
        {
          /* Strip this section if we don't need it; see the
             comment below.  */
@@ -2184,6 +2313,7 @@ elf_s390_relocate_section (bfd *output_bfd,
       bfd_boolean unresolved_reloc;
       bfd_reloc_status_type r;
       int tls_type;
+      asection *base_got = htab->elf.sgot;
 
       r_type = ELF32_R_TYPE (rel->r_info);
       if (r_type == (int) R_390_GNU_VTINHERIT
@@ -2206,7 +2336,52 @@ elf_s390_relocate_section (bfd *output_bfd,
        {
          sym = local_syms + r_symndx;
          sec = local_sections[r_symndx];
-         relocation = _bfd_elf_rela_local_sym (output_bfd, sym, &sec, rel);
+         if (ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC)
+           {
+             struct plt_entry *local_plt = elf_s390_local_plt (input_bfd);
+             if (local_plt == NULL)
+               return FALSE;
+
+             /* Address of the PLT slot.  */
+             relocation = (htab->elf.iplt->output_section->vma
+                           + htab->elf.iplt->output_offset
+                           + local_plt[r_symndx].plt.offset);
+
+             switch (r_type)
+               {
+               case R_390_GOTPLT12:
+               case R_390_GOTPLT16:
+               case R_390_GOTPLT20:
+               case R_390_GOTPLT32:
+               case R_390_GOTPLTENT:
+               case R_390_GOT12:
+               case R_390_GOT16:
+               case R_390_GOT20:
+               case R_390_GOT32:
+               case R_390_GOTENT:
+                 {
+                   /* Write the PLT slot address into the GOT slot.  */
+                   bfd_put_32 (output_bfd, relocation,
+                               htab->elf.sgot->contents +
+                               local_got_offsets[r_symndx]);
+                   relocation = (local_got_offsets[r_symndx] +
+                                 htab->elf.sgot->output_offset);
+
+                   if (r_type == R_390_GOTENT || r_type == R_390_GOTPLTENT)
+                     relocation += htab->elf.sgot->output_section->vma;
+                   break;
+                 }
+               default:
+                 break;
+               }
+             /* The output section is needed later in
+                finish_dynamic_section when creating the dynamic
+                relocation.  */
+             local_plt[r_symndx].sec = sec;
+             goto do_relocation;
+           }
+         else
+           relocation = _bfd_elf_rela_local_sym (output_bfd, sym, &sec, rel);
        }
       else
        {
@@ -2245,19 +2420,29 @@ elf_s390_relocate_section (bfd *output_bfd,
            {
              bfd_vma plt_index;
 
-             /* Calc. index no.
-                Current offset - size first entry / entry size.  */
-             plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) /
-               PLT_ENTRY_SIZE;
-
-             /* Offset in GOT is PLT index plus GOT headers(3) times 4,
-                addr & GOT addr.  */
-             relocation = (plt_index + 3) * GOT_ENTRY_SIZE;
+             if (s390_is_ifunc_symbol_p (h))
+               {
+                 plt_index = h->plt.offset / PLT_ENTRY_SIZE;
+                 relocation = (plt_index * GOT_ENTRY_SIZE +
+                               htab->elf.igotplt->output_offset);
+                 if (r_type == R_390_GOTPLTENT)
+                   relocation += htab->elf.igotplt->output_section->vma;
+               }
+             else
+               {
+                 /* Calc. index no.
+                    Current offset - size first entry / entry size.  */
+                 plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) /
+                   PLT_ENTRY_SIZE;
+
+                 /* Offset in GOT is PLT index plus GOT headers(3)
+                    times 4, addr & GOT addr.  */
+                 relocation = (plt_index + 3) * GOT_ENTRY_SIZE;
+                 if (r_type == R_390_GOTPLTENT)
+                   relocation += htab->elf.sgot->output_section->vma;
+               }
              unresolved_reloc = FALSE;
 
-             if (r_type == R_390_GOTPLTENT)
-               relocation += htab->elf.sgot->output_section->vma;
-             break;
            }
          /* Fall through.  */
 
@@ -2268,7 +2453,7 @@ elf_s390_relocate_section (bfd *output_bfd,
        case R_390_GOTENT:
          /* Relocation is to the entry for this symbol in the global
             offset table.  */
-         if (htab->elf.sgot == NULL)
+         if (base_got == NULL)
            abort ();
 
          if (h != NULL)
@@ -2277,11 +2462,30 @@ elf_s390_relocate_section (bfd *output_bfd,
 
              off = h->got.offset;
              dyn = htab->elf.dynamic_sections_created;
-             if (! WILL_CALL_FINISH_DYNAMIC_SYMBOL (dyn, info->shared, h)
-                 || (info->shared
-                     && SYMBOL_REFERENCES_LOCAL (info, h))
-                 || (ELF_ST_VISIBILITY (h->other)
-                     && h->root.type == bfd_link_hash_undefweak))
+
+             if (s390_is_ifunc_symbol_p (h))
+               {
+                 BFD_ASSERT (h->plt.offset != (bfd_vma) -1);
+                 if (off == (bfd_vma)-1)
+                   {
+                     /* No explicit GOT usage so redirect to the
+                        got.iplt slot.  */
+                     base_got = htab->elf.igotplt;
+                     off = h->plt.offset / PLT_ENTRY_SIZE * GOT_ENTRY_SIZE;
+                   }
+                 else
+                   {
+                     /* Explicit GOT slots must contain the address
+                        of the PLT slot. This will be handled in
+                        finish_dynamic_symbol.  */
+                   }
+               }
+             else if (! WILL_CALL_FINISH_DYNAMIC_SYMBOL (dyn, info->shared, h)
+                      || (info->shared
+                          && SYMBOL_REFERENCES_LOCAL (info, h))
+                      || (ELF_ST_VISIBILITY (h->other)
+                          && h->root.type == bfd_link_hash_undefweak))
+
                {
                  /* This is actually a static link, or it is a
                     -Bsymbolic link and the symbol is defined
@@ -2300,7 +2504,7 @@ elf_s390_relocate_section (bfd *output_bfd,
                  else
                    {
                      bfd_put_32 (output_bfd, relocation,
-                                 htab->elf.sgot->contents + off);
+                                 base_got->contents + off);
                      h->got.offset |= 1;
                    }
                }
@@ -2351,7 +2555,7 @@ elf_s390_relocate_section (bfd *output_bfd,
          if (off >= (bfd_vma) -2)
            abort ();
 
-         relocation = htab->elf.sgot->output_offset + off;
+         relocation = base_got->output_offset + off;
 
          /* For @GOTENT the relocation is against the offset between
             the instruction and the symbols entry in the GOT and not
@@ -2359,7 +2563,7 @@ elf_s390_relocate_section (bfd *output_bfd,
             add the vma of the GOT to get the correct value.  */
          if (   r_type == R_390_GOTENT
              || r_type == R_390_GOTPLTENT)
-           relocation += htab->elf.sgot->output_section->vma;
+           relocation += base_got->output_section->vma;
 
          break;
 
@@ -2395,7 +2599,7 @@ elf_s390_relocate_section (bfd *output_bfd,
            break;
 
          if (h->plt.offset == (bfd_vma) -1
-             || htab->elf.splt == NULL)
+             || (htab->elf.splt == NULL && htab->elf.iplt == NULL))
            {
              /* We didn't make a PLT entry for this symbol.  This
                 happens when statically linking PIC code, or when
@@ -2403,9 +2607,14 @@ elf_s390_relocate_section (bfd *output_bfd,
              break;
            }
 
-         relocation = (htab->elf.splt->output_section->vma
-                       + htab->elf.splt->output_offset
-                       + h->plt.offset);
+         if (s390_is_ifunc_symbol_p (h))
+           relocation = (htab->elf.iplt->output_section->vma
+                          + htab->elf.iplt->output_offset
+                         + h->plt.offset);
+         else
+           relocation = (htab->elf.splt->output_section->vma
+                         + htab->elf.splt->output_offset
+                         + h->plt.offset);
          unresolved_reloc = FALSE;
          break;
 
@@ -2424,10 +2633,16 @@ elf_s390_relocate_section (bfd *output_bfd,
              break;
            }
 
-         relocation = (htab->elf.splt->output_section->vma
-                       + htab->elf.splt->output_offset
-                       + h->plt.offset
-                       - htab->elf.sgot->output_section->vma);
+         if (s390_is_ifunc_symbol_p (h))
+           relocation = (htab->elf.iplt->output_section->vma
+                         + htab->elf.iplt->output_offset
+                         + h->plt.offset
+                         - htab->elf.sgot->output_section->vma);
+         else
+           relocation = (htab->elf.splt->output_section->vma
+                         + htab->elf.splt->output_offset
+                         + h->plt.offset
+                         - htab->elf.sgot->output_section->vma);
          unresolved_reloc = FALSE;
          break;
 
@@ -2438,6 +2653,67 @@ elf_s390_relocate_section (bfd *output_bfd,
        case R_390_PC16DBL:
        case R_390_PC32DBL:
        case R_390_PC32:
+         if (h != NULL
+             && s390_is_ifunc_symbol_p (h)
+             && h->def_regular)
+           {
+             if (!info->shared || !h->non_got_ref)
+               {
+                 /* For a non-shared object STT_GNU_IFUNC symbol must
+                    go through PLT.  */
+                 relocation = (htab->elf.iplt->output_section->vma
+                               + htab->elf.iplt->output_offset
+                               + h ->plt.offset);
+                 goto do_relocation;
+               }
+             else
+               {
+                 /* For shared objects a runtime relocation is needed.  */
+
+                 Elf_Internal_Rela outrel;
+                 asection *sreloc;
+
+                 /* Need a dynamic relocation to get the real function
+                    address.  */
+                 outrel.r_offset = _bfd_elf_section_offset (output_bfd,
+                                                            info,
+                                                            input_section,
+                                                            rel->r_offset);
+                 if (outrel.r_offset == (bfd_vma) -1
+                     || outrel.r_offset == (bfd_vma) -2)
+                   abort ();
+
+                 outrel.r_offset += (input_section->output_section->vma
+                                     + input_section->output_offset);
+
+                 if (h->dynindx == -1
+                     || h->forced_local
+                     || info->executable)
+                   {
+                     /* This symbol is resolved locally.  */
+                     outrel.r_info = ELF32_R_INFO (0, R_390_IRELATIVE);
+                     outrel.r_addend = (h->root.u.def.value
+                                        + h->root.u.def.section->output_section->vma
+                                        + h->root.u.def.section->output_offset);
+                   }
+                 else
+                   {
+                     outrel.r_info = ELF32_R_INFO (h->dynindx, r_type);
+                     outrel.r_addend = 0;
+                   }
+
+                 sreloc = htab->elf.irelifunc;
+                 elf_append_rela (output_bfd, sreloc, &outrel);
+
+                 /* If this reloc is against an external symbol, we
+                    do not want to fiddle with the addend.  Otherwise,
+                    we need to include the symbol value so that it
+                    becomes an addend for the dynamic reloc.  For an
+                    internal symbol, we have updated addend.  */
+                 continue;
+               }
+           }
+
          if ((input_section->flags & SEC_ALLOC) == 0)
            break;
 
@@ -2963,6 +3239,8 @@ elf_s390_relocate_section (bfd *output_bfd,
           howto->name,
           h->root.root.string);
 
+    do_relocation:
+
       if (r_type == R_390_20
          || r_type == R_390_GOT20
          || r_type == R_390_GOTPLT20
@@ -3019,6 +3297,152 @@ elf_s390_relocate_section (bfd *output_bfd,
   return TRUE;
 }
 
+/* Generate the PLT slots together with the dynamic relocations needed
+   for IFUNC symbols.  */
+
+static void
+elf_s390_finish_ifunc_symbol (bfd *output_bfd,
+                             struct bfd_link_info *info,
+                             struct elf_link_hash_entry *h,
+                             struct elf_s390_link_hash_table *htab,
+                             bfd_vma iplt_offset,
+                             bfd_vma resolver_address)
+{
+  bfd_vma iplt_index;
+  bfd_vma got_offset;
+  bfd_vma igotiplt_offset;
+  Elf_Internal_Rela rela;
+  bfd_byte *loc;
+  asection *plt, *gotplt, *relplt;
+  bfd_vma relative_offset;
+
+  if (htab->elf.iplt == NULL
+      || htab->elf.igotplt == NULL
+      || htab->elf.irelplt == NULL)
+    abort ();
+
+  gotplt = htab->elf.igotplt;
+  relplt = htab->elf.irelplt;
+
+  /* Index of the PLT slot within iplt section.  */
+  iplt_index = iplt_offset / PLT_ENTRY_SIZE;
+  plt = htab->elf.iplt;
+  /* Offset into the igot.plt section.  */
+  igotiplt_offset = iplt_index * GOT_ENTRY_SIZE;
+  /* Offset into the got section.  */
+  got_offset = igotiplt_offset + gotplt->output_offset;
+
+  /* S390 uses halfwords for relative branch calc!  */
+  relative_offset = - (plt->output_offset +
+                      (PLT_ENTRY_SIZE * iplt_index) + 18) / 2;
+/* If offset is > 32768, branch to a previous branch
+   390 can only handle +-64 K jumps.  */
+  if ( -32768 > (int) relative_offset )
+    relative_offset
+      = -(unsigned) (((65536 / PLT_ENTRY_SIZE - 1) * PLT_ENTRY_SIZE) / 2);
+
+  /* Fill in the entry in the procedure linkage table.  */
+  if (!info->shared)
+    {
+      memcpy (plt->contents + iplt_offset, elf_s390_plt_entry,
+             PLT_ENTRY_SIZE);
+
+      /* Adjust jump to the first plt entry.  */
+      bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                 plt->contents + iplt_offset + 20);
+
+      /* Push the GOT offset field.  */
+      bfd_put_32 (output_bfd,
+                 (gotplt->output_section->vma
+                  + got_offset),
+                 plt->contents + iplt_offset + 24);
+    }
+  else if (got_offset < 4096)
+    {
+      /* The GOT offset is small enough to be used directly as
+        displacement.  */
+      memcpy (plt->contents + iplt_offset,
+             elf_s390_plt_pic12_entry,
+             PLT_ENTRY_SIZE);
+
+      /* Put in the GOT offset as displacement value.  The 0xc000
+        value comes from the first word of the plt entry.  Look
+        at the elf_s390_plt_pic16_entry content.  */
+      bfd_put_16 (output_bfd, (bfd_vma)0xc000 | got_offset,
+                 plt->contents + iplt_offset + 2);
+
+      /* Adjust the jump to the first plt entry.  */
+      bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                 plt->contents + iplt_offset + 20);
+    }
+  else if (got_offset < 32768)
+    {
+      /* The GOT offset is too big for a displacement but small
+        enough to be a signed 16 bit immediate value as it can be
+        used in an lhi instruction.  */
+      memcpy (plt->contents + iplt_offset,
+             elf_s390_plt_pic16_entry,
+             PLT_ENTRY_SIZE);
+
+      /* Put in the GOT offset for the lhi instruction.  */
+      bfd_put_16 (output_bfd, (bfd_vma)got_offset,
+                 plt->contents + iplt_offset + 2);
+
+      /* Adjust the jump to the first plt entry.  */
+      bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                 plt->contents + iplt_offset + 20);
+    }
+  else
+    {
+      memcpy (plt->contents + iplt_offset,
+             elf_s390_plt_pic_entry,
+             PLT_ENTRY_SIZE);
+
+      /* Adjust the jump to the first plt entry.  */
+      bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                 plt->contents + iplt_offset + 20);
+
+      /* Push the GOT offset field.  */
+      bfd_put_32 (output_bfd, got_offset,
+                 plt->contents + iplt_offset + 24);
+    }
+  /* Insert offset into  reloc. table here.  */
+  bfd_put_32 (output_bfd, relplt->output_offset +
+             iplt_index * RELA_ENTRY_SIZE,
+             plt->contents + iplt_offset + 28);
+
+  /* Fill in the entry in the global offset table.
+     Points to instruction after GOT offset.  */
+  bfd_put_32 (output_bfd,
+             (plt->output_section->vma
+              + plt->output_offset
+              + iplt_offset
+              + 12),
+             gotplt->contents + igotiplt_offset);
+
+  /* Fill in the entry in the .rela.plt section.  */
+  rela.r_offset = gotplt->output_section->vma + got_offset;
+
+  if (!h
+      || h->dynindx == -1
+      || ((info->executable
+          || ELF_ST_VISIBILITY (h->other) != STV_DEFAULT)
+         && h->def_regular))
+    {
+      /* The symbol can be locally resolved.  */
+      rela.r_info = ELF32_R_INFO (0, R_390_IRELATIVE);
+      rela.r_addend = resolver_address;
+    }
+  else
+    {
+      rela.r_info = ELF32_R_INFO (h->dynindx, R_390_JMP_SLOT);
+      rela.r_addend = 0;
+    }
+
+  loc = relplt->contents + iplt_index * RELA_ENTRY_SIZE;
+  bfd_elf32_swap_reloca_out (output_bfd, &rela, loc);
+}
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */
 
@@ -3029,6 +3453,7 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
                                Elf_Internal_Sym *sym)
 {
   struct elf_s390_link_hash_table *htab;
+  struct elf_s390_link_hash_entry *eh = (struct elf_s390_link_hash_entry*)h;
 
   htab = elf_s390_hash_table (info);
 
@@ -3042,125 +3467,139 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
 
       /* This symbol has an entry in the procedure linkage table.  Set
         it up.  */
-      if (h->dynindx == -1
-         || htab->elf.splt == NULL
-         || htab->elf.sgotplt == NULL
-         || htab->elf.srelplt == NULL)
-       abort ();
-
-      /* Calc. index no.
-        Current offset - size first entry / entry size.  */
-      plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) / PLT_ENTRY_SIZE;
-
-      /* Offset in GOT is PLT index plus GOT headers(3) times 4,
-        addr & GOT addr.  */
-      got_offset = (plt_index + 3) * GOT_ENTRY_SIZE;
-
-      /* S390 uses halfwords for relative branch calc!  */
-      relative_offset = - ((PLT_FIRST_ENTRY_SIZE +
-                           (PLT_ENTRY_SIZE * plt_index) + 18) / 2);
-      /* If offset is > 32768, branch to a previous branch
-        390 can only handle +-64 K jumps.  */
-      if ( -32768 > (int) relative_offset )
-       relative_offset
-         = -(unsigned) (((65536 / PLT_ENTRY_SIZE - 1) * PLT_ENTRY_SIZE) / 2);
-
-      /* Fill in the entry in the procedure linkage table.  */
-      if (!info->shared)
+      if (s390_is_ifunc_symbol_p (h))
        {
-         memcpy (htab->elf.splt->contents + h->plt.offset, elf_s390_plt_entry,
-                 PLT_ENTRY_SIZE);
-
-         /* Adjust jump to the first plt entry.  */
-         bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
-                     htab->elf.splt->contents + h->plt.offset + 20);
-
-         /* Push the GOT offset field.  */
-         bfd_put_32 (output_bfd,
-                     (htab->elf.sgotplt->output_section->vma
-                      + htab->elf.sgotplt->output_offset
-                      + got_offset),
-                     htab->elf.splt->contents + h->plt.offset + 24);
-       }
-      else if (got_offset < 4096)
-       {
-         /* The GOT offset is small enough to be used directly as
-            displacement.  */
-         memcpy (htab->elf.splt->contents + h->plt.offset,
-                 elf_s390_plt_pic12_entry,
-                 PLT_ENTRY_SIZE);
-
-         /* Put in the GOT offset as displacement value.  The 0xc000
-            value comes from the first word of the plt entry.  Look
-            at the elf_s390_plt_pic16_entry content.  */
-         bfd_put_16 (output_bfd, (bfd_vma)0xc000 | got_offset,
-                     htab->elf.splt->contents + h->plt.offset + 2);
-
-         /* Adjust the jump to the first plt entry.  */
-         bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
-                     htab->elf.splt->contents + h->plt.offset + 20);
-       }
-      else if (got_offset < 32768)
-       {
-         /* The GOT offset is too big for a displacement but small
-            enough to be a signed 16 bit immediate value as it can be
-            used in an lhi instruction.  */
-         memcpy (htab->elf.splt->contents + h->plt.offset,
-                 elf_s390_plt_pic16_entry,
-                 PLT_ENTRY_SIZE);
-
-         /* Put in the GOT offset for the lhi instruction.  */
-         bfd_put_16 (output_bfd, (bfd_vma)got_offset,
-                     htab->elf.splt->contents + h->plt.offset + 2);
-
-         /* Adjust the jump to the first plt entry.  */
-         bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
-                     htab->elf.splt->contents + h->plt.offset + 20);
+         /* If we can resolve the IFUNC symbol locally we generate an
+            IRELATIVE reloc.  */
+         elf_s390_finish_ifunc_symbol (output_bfd, info, h, htab, h->plt.offset,
+                                       eh->ifunc_resolver_address +
+                                       eh->ifunc_resolver_section->output_offset +
+                                       eh->ifunc_resolver_section->output_section->vma);
+         /* Fallthrough.  Handling of explicit GOT slots of IFUNC
+            symbols is below.  */
        }
       else
        {
-         memcpy (htab->elf.splt->contents + h->plt.offset,
-                 elf_s390_plt_pic_entry,
-                 PLT_ENTRY_SIZE);
+         if (h->dynindx == -1
+             || htab->elf.splt == NULL
+             || htab->elf.sgotplt == NULL
+             || htab->elf.srelplt == NULL)
+           abort ();
 
-         /* Adjust the jump to the first plt entry.  */
-         bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
-                     htab->elf.splt->contents + h->plt.offset + 20);
+         /* Calc. index no.
+            Current offset - size first entry / entry size.  */
+         plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) / PLT_ENTRY_SIZE;
 
-         /* Push the GOT offset field.  */
-         bfd_put_32 (output_bfd, got_offset,
-                     htab->elf.splt->contents + h->plt.offset + 24);
-       }
-      /* Insert offset into  reloc. table here.  */
-      bfd_put_32 (output_bfd, plt_index * sizeof (Elf32_External_Rela),
-                 htab->elf.splt->contents + h->plt.offset + 28);
+         /* Offset in GOT is PLT index plus GOT headers(3) times 4,
+            addr & GOT addr.  */
+         got_offset = (plt_index + 3) * GOT_ENTRY_SIZE;
 
-      /* Fill in the entry in the global offset table.
-        Points to instruction after GOT offset.  */
-      bfd_put_32 (output_bfd,
-                 (htab->elf.splt->output_section->vma
-                  + htab->elf.splt->output_offset
-                  + h->plt.offset
-                  + 12),
-                 htab->elf.sgotplt->contents + got_offset);
-
-      /* Fill in the entry in the .rela.plt section.  */
-      rela.r_offset = (htab->elf.sgotplt->output_section->vma
-                      + htab->elf.sgotplt->output_offset
-                      + got_offset);
-      rela.r_info = ELF32_R_INFO (h->dynindx, R_390_JMP_SLOT);
-      rela.r_addend = 0;
-      loc = htab->elf.srelplt->contents + plt_index * sizeof (Elf32_External_Rela);
-      bfd_elf32_swap_reloca_out (output_bfd, &rela, loc);
+         /* S390 uses halfwords for relative branch calc!  */
+         relative_offset = - ((PLT_FIRST_ENTRY_SIZE +
+                               (PLT_ENTRY_SIZE * plt_index) + 18) / 2);
+         /* If offset is > 32768, branch to a previous branch
+            390 can only handle +-64 K jumps.  */
+         if ( -32768 > (int) relative_offset )
+           relative_offset
+             = -(unsigned) (((65536 / PLT_ENTRY_SIZE - 1) * PLT_ENTRY_SIZE) / 2);
 
-      if (!h->def_regular)
-       {
-         /* Mark the symbol as undefined, rather than as defined in
-            the .plt section.  Leave the value alone.  This is a clue
-            for the dynamic linker, to make function pointer
-            comparisons work between an application and shared
-            library.  */
-         sym->st_shndx = SHN_UNDEF;
+         /* Fill in the entry in the procedure linkage table.  */
+         if (!info->shared)
+           {
+             memcpy (htab->elf.splt->contents + h->plt.offset, elf_s390_plt_entry,
+                     PLT_ENTRY_SIZE);
+
+             /* Adjust jump to the first plt entry.  */
+             bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                         htab->elf.splt->contents + h->plt.offset + 20);
+
+             /* Push the GOT offset field.  */
+             bfd_put_32 (output_bfd,
+                         (htab->elf.sgotplt->output_section->vma
+                          + htab->elf.sgotplt->output_offset
+                          + got_offset),
+                         htab->elf.splt->contents + h->plt.offset + 24);
+           }
+         else if (got_offset < 4096)
+           {
+             /* The GOT offset is small enough to be used directly as
+                displacement.  */
+             memcpy (htab->elf.splt->contents + h->plt.offset,
+                     elf_s390_plt_pic12_entry,
+                     PLT_ENTRY_SIZE);
+
+             /* Put in the GOT offset as displacement value.  The 0xc000
+                value comes from the first word of the plt entry.  Look
+                at the elf_s390_plt_pic16_entry content.  */
+             bfd_put_16 (output_bfd, (bfd_vma)0xc000 | got_offset,
+                         htab->elf.splt->contents + h->plt.offset + 2);
+
+             /* Adjust the jump to the first plt entry.  */
+             bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                         htab->elf.splt->contents + h->plt.offset + 20);
+           }
+         else if (got_offset < 32768)
+           {
+             /* The GOT offset is too big for a displacement but small
+                enough to be a signed 16 bit immediate value as it can be
+                used in an lhi instruction.  */
+             memcpy (htab->elf.splt->contents + h->plt.offset,
+                     elf_s390_plt_pic16_entry,
+                     PLT_ENTRY_SIZE);
+
+             /* Put in the GOT offset for the lhi instruction.  */
+             bfd_put_16 (output_bfd, (bfd_vma)got_offset,
+                         htab->elf.splt->contents + h->plt.offset + 2);
+
+             /* Adjust the jump to the first plt entry.  */
+             bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                         htab->elf.splt->contents + h->plt.offset + 20);
+           }
+         else
+           {
+             memcpy (htab->elf.splt->contents + h->plt.offset,
+                     elf_s390_plt_pic_entry,
+                     PLT_ENTRY_SIZE);
+
+             /* Adjust the jump to the first plt entry.  */
+             bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+                         htab->elf.splt->contents + h->plt.offset + 20);
+
+             /* Push the GOT offset field.  */
+             bfd_put_32 (output_bfd, got_offset,
+                         htab->elf.splt->contents + h->plt.offset + 24);
+           }
+         /* Insert offset into  reloc. table here.  */
+         bfd_put_32 (output_bfd, plt_index * sizeof (Elf32_External_Rela),
+                     htab->elf.splt->contents + h->plt.offset + 28);
+
+         /* Fill in the entry in the global offset table.
+            Points to instruction after GOT offset.  */
+         bfd_put_32 (output_bfd,
+                     (htab->elf.splt->output_section->vma
+                      + htab->elf.splt->output_offset
+                      + h->plt.offset
+                      + 12),
+                     htab->elf.sgotplt->contents + got_offset);
+
+         /* Fill in the entry in the .rela.plt section.  */
+         rela.r_offset = (htab->elf.sgotplt->output_section->vma
+                          + htab->elf.sgotplt->output_offset
+                          + got_offset);
+         rela.r_info = ELF32_R_INFO (h->dynindx, R_390_JMP_SLOT);
+         rela.r_addend = 0;
+         loc = htab->elf.srelplt->contents + plt_index * sizeof (Elf32_External_Rela);
+         bfd_elf32_swap_reloca_out (output_bfd, &rela, loc);
+
+         if (!h->def_regular)
+           {
+             /* Mark the symbol as undefined, rather than as defined in
+                the .plt section.  Leave the value alone.  This is a clue
+                for the dynamic linker, to make function pointer
+                comparisons work between an application and shared
+                library.  */
+             sym->st_shndx = SHN_UNDEF;
+           }
        }
     }
 
@@ -3187,9 +3626,37 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
         of a version file, we just want to emit a RELATIVE reloc.
         The entry in the global offset table will already have been
         initialized in the relocate_section function.  */
-      if (info->shared
+      if (h->def_regular && s390_is_ifunc_symbol_p (h))
+       {
+         if (info->shared)
+           {
+             /* An explicit GOT slot usage needs GLOB_DAT.  If the
+                symbol references local the implicit got.iplt slot
+                will be used and the IRELATIVE reloc has been created
+                above.  */
+             goto do_glob_dat;
+           }
+         else
+           {
+             /* For non-shared objects explicit GOT slots must be
+                filled with the PLT slot address for pointer
+                equality reasons.  */
+             bfd_put_32 (output_bfd, (htab->elf.iplt->output_section->vma
+                                      + htab->elf.iplt->output_offset
+                                      + h->plt.offset),
+                         htab->elf.sgot->contents + h->got.offset);
+             return TRUE;
+           }
+       }
+      else if (info->shared
          && SYMBOL_REFERENCES_LOCAL (info, h))
        {
+         /* If this is a static link, or it is a -Bsymbolic link and
+            the symbol is defined locally or was forced to be local
+            because of a version file, we just want to emit a
+            RELATIVE reloc.  The entry in the global offset table
+            will already have been initialized in the
+            relocate_section function.  */
          if (!h->def_regular)
            return FALSE;
          BFD_ASSERT((h->got.offset & 1) != 0);
@@ -3201,6 +3668,7 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
       else
        {
          BFD_ASSERT((h->got.offset & 1) == 0);
+       do_glob_dat:
          bfd_put_32 (output_bfd, (bfd_vma) 0, htab->elf.sgot->contents + h->got.offset);
          rela.r_info = ELF32_R_INFO (h->dynindx, R_390_GLOB_DAT);
          rela.r_addend = 0;
@@ -3271,6 +3739,8 @@ elf_s390_finish_dynamic_sections (bfd *output_bfd,
   struct elf_s390_link_hash_table *htab;
   bfd *dynobj;
   asection *sdyn;
+  bfd *ibfd;
+  unsigned int i;
 
   htab = elf_s390_hash_table (info);
   dynobj = htab->elf.dynobj;
@@ -3356,6 +3826,36 @@ elf_s390_finish_dynamic_sections (bfd *output_bfd,
       elf_section_data (htab->elf.sgotplt->output_section)
        ->this_hdr.sh_entsize = 4;
     }
+  /* Finish dynamic symbol for local IFUNC symbols.  */
+  for (ibfd = info->input_bfds; ibfd != NULL; ibfd = ibfd->link_next)
+    {
+      struct plt_entry *local_plt;
+      Elf_Internal_Sym *isym;
+      Elf_Internal_Shdr *symtab_hdr;
+
+      symtab_hdr = &elf_symtab_hdr (ibfd);
+
+      local_plt = elf_s390_local_plt (ibfd);
+      if (local_plt != NULL)
+       for (i = 0; i < symtab_hdr->sh_info; i++)
+         {
+           if (local_plt[i].plt.offset != (bfd_vma) -1)
+             {
+               asection *sec = local_plt[i].sec;
+               isym = bfd_sym_from_r_symndx (&htab->sym_cache, ibfd, i);
+               if (isym == NULL)
+                 return FALSE;
+
+               if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+                 elf_s390_finish_ifunc_symbol (output_bfd, info, NULL, htab,
+                                               local_plt[i].plt.offset,
+                                               isym->st_value
+                                               + sec->output_section->vma
+                                               + sec->output_offset);
+
+             }
+         }
+    }
   return TRUE;
 }
 
@@ -3446,6 +3946,7 @@ elf32_s390_merge_private_bfd_data (bfd *ibfd, bfd *obfd)
 #define elf_backend_reloc_type_class         elf_s390_reloc_type_class
 #define elf_backend_grok_prstatus            elf_s390_grok_prstatus
 #define elf_backend_plt_sym_val                      elf_s390_plt_sym_val
+#define elf_backend_add_symbol_hook           elf_s390_add_symbol_hook
 
 #define bfd_elf32_mkobject             elf_s390_mkobject
 #define elf_backend_object_p           elf_s390_object_p
index 7cf4706d9c6da765c4cb1d324e9c495e3a827aef..8512fc0700beae889d0ec4de30c45eed75333ac2 100644 (file)
@@ -169,6 +169,9 @@ static reloc_howto_type elf_howto_table[] =
        s390_elf_ldisp_reloc, "R_390_GOTPLT20", FALSE, 0,0x0fffff00, FALSE),
   HOWTO(R_390_TLS_GOTIE20, 0, 2, 20, FALSE, 8, complain_overflow_dont,
        s390_elf_ldisp_reloc, "R_390_TLS_GOTIE20", FALSE, 0,0x0fffff00, FALSE),
+  HOWTO(R_390_IRELATIVE, 0, 4, 64, FALSE, 0, complain_overflow_bitfield,
+       bfd_elf_generic_reloc, "R_390_IRELATIVE", FALSE, 0, MINUS_ONE, FALSE),
+
 };
 
 /* GNU extension to record C++ vtable hierarchy.  */
@@ -295,6 +298,8 @@ elf_s390_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
       return &elf_howto_table[(int) R_390_GOTPLT20];
     case BFD_RELOC_390_TLS_GOTIE20:
       return &elf_howto_table[(int) R_390_TLS_GOTIE20];
+    case BFD_RELOC_390_IRELATIVE:
+      return &elf_howto_table[(int) R_390_IRELATIVE];
     case BFD_RELOC_VTABLE_INHERIT:
       return &elf64_s390_vtinherit_howto;
     case BFD_RELOC_VTABLE_ENTRY:
@@ -450,6 +455,8 @@ elf_s390_is_local_label_name (bfd *abfd, const char *name)
 
 #define GOT_ENTRY_SIZE 8
 
+#define RELA_ENTRY_SIZE sizeof (Elf64_External_Rela)
+
 /* The first three entries in a procedure linkage table are reserved,
    and the initial contents are unimportant (we zero them out).
    Subsequent entries look like this.  See the SVR4 ABI 386
@@ -552,17 +559,43 @@ struct elf_s390_link_hash_entry
 #define GOT_TLS_IE     3
 #define GOT_TLS_IE_NLT 3
   unsigned char tls_type;
+
+  /* For pointer equality reasons we might need to change the symbol
+     type from STT_GNU_IFUNC to STT_FUNC together with its value and
+     section entry.  So after alloc_dynrelocs only these values should
+     be used.  In order to check whether a symbol is IFUNC use
+     s390_is_ifunc_symbol_p.  */
+  bfd_vma ifunc_resolver_address;
+  asection *ifunc_resolver_section;
 };
 
 #define elf_s390_hash_entry(ent) \
   ((struct elf_s390_link_hash_entry *)(ent))
 
+/* This structure represents an entry in the local PLT list needed for
+   local IFUNC symbols.  */
+struct plt_entry
+{
+  /* The section of the local symbol.
+     Set in relocate_section and used in finish_dynamic_sections.  */
+  asection *sec;
+
+  union
+    {
+      bfd_signed_vma refcount;
+      bfd_vma offset;
+    } plt;
+};
+
 /* NOTE: Keep this structure in sync with
    the one declared in elf32-s390.c.  */
 struct elf_s390_obj_tdata
 {
   struct elf_obj_tdata root;
 
+  /* A local PLT is needed for ifunc symbols.  */
+  struct plt_entry *local_plt;
+
   /* TLS type for each local got entry.  */
   char *local_got_tls_type;
 };
@@ -570,6 +603,9 @@ struct elf_s390_obj_tdata
 #define elf_s390_tdata(abfd) \
   ((struct elf_s390_obj_tdata *) (abfd)->tdata.any)
 
+#define elf_s390_local_plt(abfd) \
+  (elf_s390_tdata (abfd)->local_plt)
+
 #define elf_s390_local_got_tls_type(abfd) \
   (elf_s390_tdata (abfd)->local_got_tls_type)
 
@@ -601,6 +637,7 @@ struct elf_s390_link_hash_table
   /* Short-cuts to get to dynamic linker sections.  */
   asection *sdynbss;
   asection *srelbss;
+  asection *irelifunc;
 
   union {
     bfd_signed_vma refcount;
@@ -617,6 +654,9 @@ struct elf_s390_link_hash_table
   (elf_hash_table_id ((struct elf_link_hash_table *) ((p)->hash)) \
   == S390_ELF_DATA ? ((struct elf_s390_link_hash_table *) ((p)->hash)) : NULL)
 
+#define ELF64 1
+#include "elf-s390-common.c"
+
 /* Create an entry in an s390 ELF linker hash table.  */
 
 static struct bfd_hash_entry *
@@ -644,6 +684,8 @@ link_hash_newfunc (struct bfd_hash_entry *entry,
       eh->dyn_relocs = NULL;
       eh->gotplt_refcount = 0;
       eh->tls_type = GOT_UNKNOWN;
+      eh->ifunc_resolver_address = 0;
+      eh->ifunc_resolver_section = NULL;
     }
 
   return entry;
@@ -871,6 +913,7 @@ elf_s390_check_relocs (bfd *abfd,
       unsigned int r_type;
       unsigned long r_symndx;
       struct elf_link_hash_entry *h;
+      Elf_Internal_Sym *isym;
 
       r_symndx = ELF64_R_SYM (rel->r_info);
 
@@ -883,7 +926,31 @@ elf_s390_check_relocs (bfd *abfd,
        }
 
       if (r_symndx < symtab_hdr->sh_info)
-       h = NULL;
+       {
+         /* A local symbol.  */
+         isym = bfd_sym_from_r_symndx (&htab->sym_cache,
+                                       abfd, r_symndx);
+         if (isym == NULL)
+           return FALSE;
+
+         if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+           {
+             struct plt_entry *plt;
+
+             if (!s390_elf_create_ifunc_sections (htab->elf.dynobj, info))
+               return FALSE;
+
+             if (local_got_refcounts == NULL)
+               {
+                 if (!elf_s390_allocate_local_syminfo (abfd, symtab_hdr))
+                   return FALSE;
+                 local_got_refcounts = elf_local_got_refcounts (abfd);
+               }
+             plt = elf_s390_local_plt (abfd);
+             plt[r_symndx].plt.refcount++;
+           }
+         h = NULL;
+       }
       else
        {
          h = sym_hashes[r_symndx - symtab_hdr->sh_info];
@@ -921,18 +988,11 @@ elf_s390_check_relocs (bfd *abfd,
          if (h == NULL
              && local_got_refcounts == NULL)
            {
-             bfd_size_type size;
-
-             size = symtab_hdr->sh_info;
-             size *= (sizeof (bfd_signed_vma) + sizeof(char));
-             local_got_refcounts = ((bfd_signed_vma *)
-                                    bfd_zalloc (abfd, size));
-             if (local_got_refcounts == NULL)
+             if (!elf_s390_allocate_local_syminfo (abfd, symtab_hdr))
                return FALSE;
-             elf_local_got_refcounts (abfd) = local_got_refcounts;
-             elf_s390_local_got_tls_type (abfd)
-               = (char *) (local_got_refcounts + symtab_hdr->sh_info);
+             local_got_refcounts = elf_local_got_refcounts (abfd);
            }
+
          /* Fall through.  */
        case R_390_GOTOFF16:
        case R_390_GOTOFF32:
@@ -948,6 +1008,25 @@ elf_s390_check_relocs (bfd *abfd,
            }
        }
 
+      if (h != NULL)
+       {
+         if (htab->elf.dynobj == NULL)
+           htab->elf.dynobj = abfd;
+         if (!s390_elf_create_ifunc_sections (htab->elf.dynobj, info))
+           return FALSE;
+
+         /* Make sure an IFUNC symbol defined in a non-shared object
+            always gets a PLT slot.  */
+         if (s390_is_ifunc_symbol_p (h) && h->def_regular)
+           {
+             /* The symbol is called by the dynamic loader in order
+                to resolve the relocation.  So it is in fact also
+                referenced.  */
+             h->ref_regular = 1;
+             h->needs_plt = 1;
+           }
+       }
+
       switch (r_type)
        {
        case R_390_GOTOFF16:
@@ -955,7 +1034,10 @@ elf_s390_check_relocs (bfd *abfd,
        case R_390_GOTOFF64:
        case R_390_GOTPC:
        case R_390_GOTPCDBL:
-         /* Got is created, nothing to be done.  */
+         /* These relocs do not need a GOT slot.  They just load the
+            GOT pointer itself or address something else relative to
+            the GOT.  Since the GOT pointer has been set up above we
+            are done.  */
          break;
 
        case R_390_PLT16DBL:
@@ -1102,7 +1184,7 @@ elf_s390_check_relocs (bfd *abfd,
        case R_390_PC32:
        case R_390_PC32DBL:
        case R_390_PC64:
-         if (h != NULL && !info->shared)
+         if (h != NULL)
            {
              /* If this reloc is in a read-only section, we might
                 need a copy reloc.  We can't check reliably at this
@@ -1112,9 +1194,12 @@ elf_s390_check_relocs (bfd *abfd,
                 adjust_dynamic_symbol.  */
              h->non_got_ref = 1;
 
-             /* We may need a .plt entry if the function this reloc
-                refers to is in a shared lib.  */
-             h->plt.refcount += 1;
+             if (!info->shared)
+               {
+                 /* We may need a .plt entry if the function this reloc
+                    refers to is in a shared lib.  */
+                 h->plt.refcount += 1;
+               }
            }
 
          /* If we are creating a shared library, and this is a reloc
@@ -1187,7 +1272,6 @@ elf_s390_check_relocs (bfd *abfd,
                     easily.  Oh well.  */
                  asection *s;
                  void *vpp;
-                 Elf_Internal_Sym *isym;
 
                  isym = bfd_sym_from_r_symndx (&htab->sym_cache,
                                                abfd, r_symndx);
@@ -1327,6 +1411,23 @@ elf_s390_gc_sweep_hook (bfd *abfd,
                break;
              }
        }
+      else
+       {
+         Elf_Internal_Sym *isym;
+
+         /* A local symbol.  */
+         isym = bfd_sym_from_r_symndx (&htab->sym_cache,
+                                       abfd, r_symndx);
+         if (isym == NULL)
+           return FALSE;
+
+         if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+           {
+             struct plt_entry *plt = elf_s390_local_plt (abfd);
+             if (plt[r_symndx].plt.refcount > 0)
+               plt[r_symndx].plt.refcount--;
+           }
+       }
 
       r_type = ELF64_R_TYPE (rel->r_info);
       r_type = elf_s390_tls_transition (info, r_type, h != NULL);
@@ -1459,6 +1560,10 @@ elf_s390_adjust_dynamic_symbol (struct bfd_link_info *info,
   struct elf_s390_link_hash_table *htab;
   asection *s;
 
+  /* STT_GNU_IFUNC symbol must go through PLT. */
+  if (s390_is_ifunc_symbol_p (h))
+    return TRUE;
+
   /* If this is a function, put it in the procedure linkage table.  We
      will fill in the contents of the procedure linkage table later
      (although we could actually do it here).  */
@@ -1585,7 +1690,7 @@ allocate_dynrelocs (struct elf_link_hash_entry *h,
 {
   struct bfd_link_info *info;
   struct elf_s390_link_hash_table *htab;
-  struct elf_s390_link_hash_entry *eh;
+  struct elf_s390_link_hash_entry *eh = (struct elf_s390_link_hash_entry *)h;
   struct elf_dyn_relocs *p;
 
   if (h->root.type == bfd_link_hash_indirect)
@@ -1596,8 +1701,13 @@ allocate_dynrelocs (struct elf_link_hash_entry *h,
   if (htab == NULL)
     return FALSE;
 
-  if (htab->elf.dynamic_sections_created
-      && h->plt.refcount > 0)
+  /* Since STT_GNU_IFUNC symbol must go through PLT, we handle it
+     here if it is defined and referenced in a non-shared object.  */
+  if (s390_is_ifunc_symbol_p (h) && h->def_regular)
+    return s390_elf_allocate_ifunc_dyn_relocs (info, h,
+                                              &eh->dyn_relocs);
+  else if (htab->elf.dynamic_sections_created
+          && h->plt.refcount > 0)
     {
       /* Make sure this symbol is output as a dynamic symbol.
         Undefined weak syms won't yet be marked as dynamic.  */
@@ -1714,7 +1824,6 @@ allocate_dynrelocs (struct elf_link_hash_entry *h,
   else
     h->got.offset = (bfd_vma) -1;
 
-  eh = (struct elf_s390_link_hash_entry *) h;
   if (eh->dyn_relocs == NULL)
     return TRUE;
 
@@ -1871,6 +1980,8 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
       bfd_size_type locsymcount;
       Elf_Internal_Shdr *symtab_hdr;
       asection *srela;
+      struct plt_entry *local_plt;
+      unsigned int i;
 
       if (! is_s390_elf (ibfd))
        continue;
@@ -1923,6 +2034,20 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
          else
            *local_got = (bfd_vma) -1;
        }
+
+      local_plt = elf_s390_local_plt (ibfd);
+      for (i = 0; i < symtab_hdr->sh_info; i++)
+       {
+         if (local_plt[i].plt.refcount > 0)
+           {
+             local_plt[i].plt.offset = htab->elf.iplt->size;
+             htab->elf.iplt->size += PLT_ENTRY_SIZE;
+             htab->elf.igotplt->size += GOT_ENTRY_SIZE;
+             htab->elf.irelplt->size += sizeof (Elf64_External_Rela);
+           }
+         else
+           local_plt[i].plt.offset = (bfd_vma) -1;
+       }
     }
 
   if (htab->tls_ldm_got.refcount > 0)
@@ -1951,7 +2076,10 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
       if (s == htab->elf.splt
          || s == htab->elf.sgot
          || s == htab->elf.sgotplt
-         || s == htab->sdynbss)
+         || s == htab->sdynbss
+         || s == htab->elf.iplt
+         || s == htab->elf.igotplt
+         || s == htab->irelifunc)
        {
          /* Strip this section if we don't need it; see the
             comment below.  */
@@ -2141,6 +2269,7 @@ elf_s390_relocate_section (bfd *output_bfd,
       bfd_boolean unresolved_reloc;
       bfd_reloc_status_type r;
       int tls_type;
+      asection *base_got = htab->elf.sgot;
 
       r_type = ELF64_R_TYPE (rel->r_info);
       if (r_type == (int) R_390_GNU_VTINHERIT
@@ -2163,7 +2292,55 @@ elf_s390_relocate_section (bfd *output_bfd,
        {
          sym = local_syms + r_symndx;
          sec = local_sections[r_symndx];
-         relocation = _bfd_elf_rela_local_sym (output_bfd, sym, &sec, rel);
+
+         if (ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC)
+           {
+             struct plt_entry *local_plt = elf_s390_local_plt (input_bfd);
+             if (local_plt == NULL)
+               return FALSE;
+
+             /* Address of the PLT slot.  */
+             relocation = (htab->elf.iplt->output_section->vma
+                           + htab->elf.iplt->output_offset
+                           + local_plt[r_symndx].plt.offset);
+
+             switch (r_type)
+               {
+               case R_390_GOTPLT12:
+               case R_390_GOTPLT16:
+               case R_390_GOTPLT20:
+               case R_390_GOTPLT32:
+               case R_390_GOTPLT64:
+               case R_390_GOTPLTENT:
+               case R_390_GOT12:
+               case R_390_GOT16:
+               case R_390_GOT20:
+               case R_390_GOT32:
+               case R_390_GOT64:
+               case R_390_GOTENT:
+                 {
+                   /* Write the PLT slot address into the GOT slot.  */
+                   bfd_put_64 (output_bfd, relocation,
+                               htab->elf.sgot->contents +
+                               local_got_offsets[r_symndx]);
+                   relocation = (local_got_offsets[r_symndx] +
+                                 htab->elf.sgot->output_offset);
+
+                   if (r_type == R_390_GOTENT || r_type == R_390_GOTPLTENT)
+                     relocation += htab->elf.sgot->output_section->vma;
+                   break;
+                 }
+               default:
+                 break;
+               }
+             /* The output section is needed later in
+                finish_dynamic_section when creating the dynamic
+                relocation.  */
+             local_plt[r_symndx].sec = sec;
+             goto do_relocation;
+           }
+         else
+           relocation = _bfd_elf_rela_local_sym (output_bfd, sym, &sec, rel);
        }
       else
        {
@@ -2203,18 +2380,28 @@ elf_s390_relocate_section (bfd *output_bfd,
            {
              bfd_vma plt_index;
 
-             /* Calc. index no.
-                Current offset - size first entry / entry size.  */
-             plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) /
-               PLT_ENTRY_SIZE;
-
-             /* Offset in GOT is PLT index plus GOT headers(3) times 4,
-                addr & GOT addr.  */
-             relocation = (plt_index + 3) * GOT_ENTRY_SIZE;
+             if (s390_is_ifunc_symbol_p (h))
+               {
+                 plt_index = h->plt.offset / PLT_ENTRY_SIZE;
+                 relocation = (plt_index * GOT_ENTRY_SIZE +
+                               htab->elf.igotplt->output_offset);
+                 if (r_type == R_390_GOTPLTENT)
+                   relocation += htab->elf.igotplt->output_section->vma;
+               }
+             else
+               {
+                 /* Calc. index no.
+                    Current offset - size first entry / entry size.  */
+                 plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) /
+                   PLT_ENTRY_SIZE;
+
+                 /* Offset in GOT is PLT index plus GOT headers(3)
+                    times 4, addr & GOT addr.  */
+                 relocation = (plt_index + 3) * GOT_ENTRY_SIZE;
+                 if (r_type == R_390_GOTPLTENT)
+                   relocation += htab->elf.sgot->output_section->vma;
+               }
              unresolved_reloc = FALSE;
-
-             if (r_type == R_390_GOTPLTENT)
-               relocation += htab->elf.sgot->output_section->vma;
              break;
            }
          /* Fall through.  */
@@ -2227,7 +2414,7 @@ elf_s390_relocate_section (bfd *output_bfd,
        case R_390_GOTENT:
          /* Relocation is to the entry for this symbol in the global
             offset table.  */
-         if (htab->elf.sgot == NULL)
+         if (base_got == NULL)
            abort ();
 
          if (h != NULL)
@@ -2236,11 +2423,29 @@ elf_s390_relocate_section (bfd *output_bfd,
 
              off = h->got.offset;
              dyn = htab->elf.dynamic_sections_created;
-             if (! WILL_CALL_FINISH_DYNAMIC_SYMBOL (dyn, info->shared, h)
-                 || (info->shared
-                     && SYMBOL_REFERENCES_LOCAL (info, h))
-                 || (ELF_ST_VISIBILITY (h->other)
-                     && h->root.type == bfd_link_hash_undefweak))
+
+             if (s390_is_ifunc_symbol_p (h))
+               {
+                 BFD_ASSERT (h->plt.offset != (bfd_vma) -1);
+                 if (off == (bfd_vma)-1)
+                   {
+                     /* No explicit GOT usage so redirect to the
+                        got.iplt slot.  */
+                     base_got = htab->elf.igotplt;
+                     off = h->plt.offset / PLT_ENTRY_SIZE * GOT_ENTRY_SIZE;
+                   }
+                 else
+                   {
+                     /* Explicit GOT slots must contain the address
+                        of the PLT slot. This will be handled in
+                        finish_dynamic_symbol.  */
+                   }
+               }
+             else if (! WILL_CALL_FINISH_DYNAMIC_SYMBOL (dyn, info->shared, h)
+                      || (info->shared
+                          && SYMBOL_REFERENCES_LOCAL (info, h))
+                      || (ELF_ST_VISIBILITY (h->other)
+                          && h->root.type == bfd_link_hash_undefweak))
                {
                  /* This is actually a static link, or it is a
                     -Bsymbolic link and the symbol is defined
@@ -2259,7 +2464,7 @@ elf_s390_relocate_section (bfd *output_bfd,
                  else
                    {
                      bfd_put_64 (output_bfd, relocation,
-                                 htab->elf.sgot->contents + off);
+                                 base_got->contents + off);
                      h->got.offset |= 1;
                    }
                }
@@ -2310,7 +2515,7 @@ elf_s390_relocate_section (bfd *output_bfd,
          if (off >= (bfd_vma) -2)
            abort ();
 
-         relocation = htab->elf.sgot->output_offset + off;
+         relocation = base_got->output_offset + off;
 
          /* For @GOTENT the relocation is against the offset between
             the instruction and the symbols entry in the GOT and not
@@ -2318,7 +2523,7 @@ elf_s390_relocate_section (bfd *output_bfd,
             add the vma of the GOT to get the correct value.  */
          if (   r_type == R_390_GOTENT
              || r_type == R_390_GOTPLTENT)
-           relocation += htab->elf.sgot->output_section->vma;
+           relocation += base_got->output_section->vma;
 
          break;
 
@@ -2356,17 +2561,21 @@ elf_s390_relocate_section (bfd *output_bfd,
            break;
 
          if (h->plt.offset == (bfd_vma) -1
-             || htab->elf.splt == NULL)
+             || (htab->elf.splt == NULL && htab->elf.iplt == NULL))
            {
              /* We didn't make a PLT entry for this symbol.  This
                 happens when statically linking PIC code, or when
                 using -Bsymbolic.  */
              break;
            }
-
-         relocation = (htab->elf.splt->output_section->vma
-                       + htab->elf.splt->output_offset
-                       + h->plt.offset);
+         if (s390_is_ifunc_symbol_p (h))
+           relocation = (htab->elf.iplt->output_section->vma
+                         + htab->elf.iplt->output_offset
+                         + h->plt.offset);
+         else
+           relocation = (htab->elf.splt->output_section->vma
+                         + htab->elf.splt->output_offset
+                         + h->plt.offset);
          unresolved_reloc = FALSE;
          break;
 
@@ -2386,10 +2595,16 @@ elf_s390_relocate_section (bfd *output_bfd,
              break;
            }
 
-         relocation = (htab->elf.splt->output_section->vma
-                       + htab->elf.splt->output_offset
-                       + h->plt.offset
-                       - htab->elf.sgot->output_section->vma);
+         if (s390_is_ifunc_symbol_p (h))
+           relocation = (htab->elf.iplt->output_section->vma
+                         + htab->elf.iplt->output_offset
+                         + h->plt.offset
+                         - htab->elf.sgot->output_section->vma);
+         else
+           relocation = (htab->elf.splt->output_section->vma
+                         + htab->elf.splt->output_offset
+                         + h->plt.offset
+                         - htab->elf.sgot->output_section->vma);
          unresolved_reloc = FALSE;
          break;
 
@@ -2402,6 +2617,68 @@ elf_s390_relocate_section (bfd *output_bfd,
        case R_390_PC32:
        case R_390_PC32DBL:
        case R_390_PC64:
+
+         if (h != NULL
+             && s390_is_ifunc_symbol_p (h)
+             && h->def_regular)
+           {
+             if (!info->shared || !h->non_got_ref)
+               {
+                 /* For a non-shared object STT_GNU_IFUNC symbol must
+                    go through PLT.  */
+                 relocation = (htab->elf.iplt->output_section->vma
+                               + htab->elf.iplt->output_offset
+                               + h ->plt.offset);
+                 goto do_relocation;
+               }
+             else
+               {
+                 /* For shared objects a runtime relocation is needed.  */
+
+                 Elf_Internal_Rela outrel;
+                 asection *sreloc;
+
+                 /* Need a dynamic relocation to get the real function
+                    address.  */
+                 outrel.r_offset = _bfd_elf_section_offset (output_bfd,
+                                                            info,
+                                                            input_section,
+                                                            rel->r_offset);
+                 if (outrel.r_offset == (bfd_vma) -1
+                     || outrel.r_offset == (bfd_vma) -2)
+                   abort ();
+
+                 outrel.r_offset += (input_section->output_section->vma
+                                     + input_section->output_offset);
+
+                 if (h->dynindx == -1
+                     || h->forced_local
+                     || info->executable)
+                   {
+                     /* This symbol is resolved locally.  */
+                     outrel.r_info = ELF64_R_INFO (0, R_390_IRELATIVE);
+                     outrel.r_addend = (h->root.u.def.value
+                                        + h->root.u.def.section->output_section->vma
+                                        + h->root.u.def.section->output_offset);
+                   }
+                 else
+                   {
+                     outrel.r_info = ELF64_R_INFO (h->dynindx, r_type);
+                     outrel.r_addend = 0;
+                   }
+
+                 sreloc = htab->elf.irelifunc;
+                 elf_append_rela (output_bfd, sreloc, &outrel);
+
+                 /* If this reloc is against an external symbol, we
+                    do not want to fiddle with the addend.  Otherwise,
+                    we need to include the symbol value so that it
+                    becomes an addend for the dynamic reloc.  For an
+                    internal symbol, we have updated addend.  */
+                 continue;
+               }
+           }
+
          if ((input_section->flags & SEC_ALLOC) == 0)
            break;
 
@@ -2887,6 +3164,8 @@ elf_s390_relocate_section (bfd *output_bfd,
           howto->name,
           h->root.root.string);
 
+    do_relocation:
+
       if (r_type == R_390_20
          || r_type == R_390_GOT20
          || r_type == R_390_GOTPLT20
@@ -2943,6 +3222,92 @@ elf_s390_relocate_section (bfd *output_bfd,
   return TRUE;
 }
 
+/* Generate the PLT slots together with the dynamic relocations needed
+   for IFUNC symbols.  */
+
+static void
+elf_s390_finish_ifunc_symbol (bfd *output_bfd,
+                             struct bfd_link_info *info,
+                             struct elf_link_hash_entry *h,
+                             struct elf_s390_link_hash_table *htab,
+                             bfd_vma plt_offset,
+                             bfd_vma resolver_address)
+{
+  bfd_vma plt_index;
+  bfd_vma got_offset;
+  Elf_Internal_Rela rela;
+  bfd_byte *loc;
+  asection *plt, *gotplt, *relplt;
+
+  if (htab->elf.iplt == NULL
+      || htab->elf.igotplt == NULL
+      || htab->elf.irelplt == NULL)
+    abort ();
+
+  /* Index of the PLT slot within iplt section.  */
+  plt_index = plt_offset / PLT_ENTRY_SIZE;
+  plt = htab->elf.iplt;
+  /* Offset into the igot.plt section.  */
+  got_offset = plt_index * GOT_ENTRY_SIZE;
+  gotplt = htab->elf.igotplt;
+  relplt = htab->elf.irelplt;
+
+  /* Fill in the blueprint of a PLT.  */
+  memcpy (plt->contents + plt_offset, elf_s390x_plt_entry,
+         PLT_ENTRY_SIZE);
+
+  /* Fixup the relative address to the GOT entry */
+  bfd_put_32 (output_bfd,
+             (gotplt->output_section->vma +
+              gotplt->output_offset + got_offset
+              - (plt->output_section->vma +
+                 plt->output_offset +
+                 plt_offset))/2,
+             plt->contents + plt_offset + 2);
+  /* Fixup the relative branch to PLT 0 */
+  bfd_put_32 (output_bfd, - (plt->output_offset +
+                            (PLT_ENTRY_SIZE * plt_index) + 22)/2,
+             plt->contents + plt_offset + 24);
+  /* Fixup offset into .rela.plt section.  */
+  bfd_put_32 (output_bfd, relplt->output_offset +
+             plt_index * sizeof (Elf64_External_Rela),
+             plt->contents + plt_offset + 28);
+
+  /* Fill in the entry in the global offset table.
+     Points to instruction after GOT offset.  */
+  bfd_put_64 (output_bfd,
+             (plt->output_section->vma
+              + plt->output_offset
+              + plt_offset
+              + 14),
+             gotplt->contents + got_offset);
+
+  /* Fill in the entry in the .rela.plt section.  */
+  rela.r_offset = (gotplt->output_section->vma
+                  + gotplt->output_offset
+                  + got_offset);
+
+  if (!h
+      || h->dynindx == -1
+      || ((info->executable
+          || ELF_ST_VISIBILITY (h->other) != STV_DEFAULT)
+         && h->def_regular))
+    {
+      /* The symbol can be locally resolved.  */
+      rela.r_info = ELF64_R_INFO (0, R_390_IRELATIVE);
+      rela.r_addend = resolver_address;
+    }
+  else
+    {
+      rela.r_info = ELF64_R_INFO (h->dynindx, R_390_JMP_SLOT);
+      rela.r_addend = 0;
+    }
+
+  loc = relplt->contents + plt_index * sizeof (Elf64_External_Rela);
+  bfd_elf64_swap_reloca_out (output_bfd, &rela, loc);
+}
+
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */
 
@@ -2953,6 +3318,7 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
                                Elf_Internal_Sym *sym)
 {
   struct elf_s390_link_hash_table *htab;
+  struct elf_s390_link_hash_entry *eh = (struct elf_s390_link_hash_entry*)h;
 
   htab = elf_s390_hash_table (info);
   if (htab == NULL)
@@ -2967,65 +3333,82 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
 
       /* This symbol has an entry in the procedure linkage table.  Set
         it up.  */
+      if (s390_is_ifunc_symbol_p (h))
+       {
+         /* If we can resolve the IFUNC symbol locally we generate an
+            IRELATIVE reloc.  */
+         elf_s390_finish_ifunc_symbol (output_bfd, info, h, htab, h->plt.offset,
+                                       eh->ifunc_resolver_address +
+                                       eh->ifunc_resolver_section->output_offset +
+                                       eh->ifunc_resolver_section->output_section->vma);
+                                ;
+         /* Fallthrough.  Handling of explicit GOT slots of IFUNC
+            symbols is below.  */
+       }
+      else
+       {
+         if (h->dynindx == -1
+             || htab->elf.splt == NULL
+             || htab->elf.sgotplt == NULL
+             || htab->elf.srelplt == NULL)
+           abort ();
 
-      if (h->dynindx == -1
-         || htab->elf.splt == NULL
-         || htab->elf.sgotplt == NULL
-         || htab->elf.srelplt == NULL)
-       abort ();
+         /* Calc. index no.
+            Current offset - size first entry / entry size.  */
+         plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) / PLT_ENTRY_SIZE;
 
-      /* Calc. index no.
-        Current offset - size first entry / entry size.  */
-      plt_index = (h->plt.offset - PLT_FIRST_ENTRY_SIZE) / PLT_ENTRY_SIZE;
-
-      /* Offset in GOT is PLT index plus GOT headers(3) times 8,
-        addr & GOT addr.  */
-      got_offset = (plt_index + 3) * GOT_ENTRY_SIZE;
-
-      /* Fill in the blueprint of a PLT.  */
-      memcpy (htab->elf.splt->contents + h->plt.offset, elf_s390x_plt_entry,
-             PLT_ENTRY_SIZE);
-
-      /* Fixup the relative address to the GOT entry */
-      bfd_put_32 (output_bfd,
-                 (htab->elf.sgotplt->output_section->vma +
-                  htab->elf.sgotplt->output_offset + got_offset
-                  - (htab->elf.splt->output_section->vma + h->plt.offset))/2,
-                 htab->elf.splt->contents + h->plt.offset + 2);
-      /* Fixup the relative branch to PLT 0 */
-      bfd_put_32 (output_bfd, - (PLT_FIRST_ENTRY_SIZE +
-                                (PLT_ENTRY_SIZE * plt_index) + 22)/2,
-                 htab->elf.splt->contents + h->plt.offset + 24);
-      /* Fixup offset into .rela.plt section.  */
-      bfd_put_32 (output_bfd, plt_index * sizeof (Elf64_External_Rela),
-                 htab->elf.splt->contents + h->plt.offset + 28);
-
-      /* Fill in the entry in the global offset table.
-        Points to instruction after GOT offset.  */
-      bfd_put_64 (output_bfd,
-                 (htab->elf.splt->output_section->vma
-                  + htab->elf.splt->output_offset
-                  + h->plt.offset
-                  + 14),
-                 htab->elf.sgotplt->contents + got_offset);
-
-      /* Fill in the entry in the .rela.plt section.  */
-      rela.r_offset = (htab->elf.sgotplt->output_section->vma
-                      + htab->elf.sgotplt->output_offset
-                      + got_offset);
-      rela.r_info = ELF64_R_INFO (h->dynindx, R_390_JMP_SLOT);
-      rela.r_addend = 0;
-      loc = htab->elf.srelplt->contents + plt_index * sizeof (Elf64_External_Rela);
-      bfd_elf64_swap_reloca_out (output_bfd, &rela, loc);
+         /* Offset in GOT is PLT index plus GOT headers(3) times 8,
+            addr & GOT addr.  */
+         got_offset = (plt_index + 3) * GOT_ENTRY_SIZE;
 
-      if (!h->def_regular)
-       {
-         /* Mark the symbol as undefined, rather than as defined in
-            the .plt section.  Leave the value alone.  This is a clue
-            for the dynamic linker, to make function pointer
-            comparisons work between an application and shared
-            library.  */
-         sym->st_shndx = SHN_UNDEF;
+         /* Fill in the blueprint of a PLT.  */
+         memcpy (htab->elf.splt->contents + h->plt.offset, elf_s390x_plt_entry,
+                 PLT_ENTRY_SIZE);
+
+         /* Fixup the relative address to the GOT entry */
+         bfd_put_32 (output_bfd,
+                     (htab->elf.sgotplt->output_section->vma +
+                      htab->elf.sgotplt->output_offset + got_offset
+                      - (htab->elf.splt->output_section->vma +
+                         htab->elf.splt->output_offset +
+                         h->plt.offset))/2,
+                     htab->elf.splt->contents + h->plt.offset + 2);
+         /* Fixup the relative branch to PLT 0 */
+         bfd_put_32 (output_bfd, - (PLT_FIRST_ENTRY_SIZE +
+                                    (PLT_ENTRY_SIZE * plt_index) + 22)/2,
+                     htab->elf.splt->contents + h->plt.offset + 24);
+         /* Fixup offset into .rela.plt section.  */
+         bfd_put_32 (output_bfd, plt_index * sizeof (Elf64_External_Rela),
+                     htab->elf.splt->contents + h->plt.offset + 28);
+
+         /* Fill in the entry in the global offset table.
+            Points to instruction after GOT offset.  */
+         bfd_put_64 (output_bfd,
+                     (htab->elf.splt->output_section->vma
+                      + htab->elf.splt->output_offset
+                      + h->plt.offset
+                      + 14),
+                     htab->elf.sgotplt->contents + got_offset);
+
+         /* Fill in the entry in the .rela.plt section.  */
+         rela.r_offset = (htab->elf.sgotplt->output_section->vma
+                          + htab->elf.sgotplt->output_offset
+                          + got_offset);
+         rela.r_info = ELF64_R_INFO (h->dynindx, R_390_JMP_SLOT);
+         rela.r_addend = 0;
+         loc = htab->elf.srelplt->contents + plt_index *
+           sizeof (Elf64_External_Rela);
+         bfd_elf64_swap_reloca_out (output_bfd, &rela, loc);
+
+         if (!h->def_regular)
+           {
+             /* Mark the symbol as undefined, rather than as defined in
+                the .plt section.  Leave the value alone.  This is a clue
+                for the dynamic linker, to make function pointer
+                comparisons work between an application and shared
+                library.  */
+             sym->st_shndx = SHN_UNDEF;
+           }
        }
     }
 
@@ -3046,14 +3429,37 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
                       + htab->elf.sgot->output_offset
                       + (h->got.offset &~ (bfd_vma) 1));
 
-      /* If this is a static link, or it is a -Bsymbolic link and the
-        symbol is defined locally or was forced to be local because
-        of a version file, we just want to emit a RELATIVE reloc.
-        The entry in the global offset table will already have been
-        initialized in the relocate_section function.  */
-      if (info->shared
+      if (h->def_regular && s390_is_ifunc_symbol_p (h))
+       {
+         if (info->shared)
+           {
+             /* An explicit GOT slot usage needs GLOB_DAT.  If the
+                symbol references local the implicit got.iplt slot
+                will be used and the IRELATIVE reloc has been created
+                above.  */
+             goto do_glob_dat;
+           }
+         else
+           {
+             /* For non-shared objects explicit GOT slots must be
+                filled with the PLT slot address for pointer
+                equality reasons.  */
+             bfd_put_64 (output_bfd, (htab->elf.iplt->output_section->vma
+                                      + htab->elf.iplt->output_offset
+                                      + h->plt.offset),
+                         htab->elf.sgot->contents + h->got.offset);
+             return TRUE;
+           }
+       }
+      else if (info->shared
          && SYMBOL_REFERENCES_LOCAL (info, h))
        {
+         /* If this is a static link, or it is a -Bsymbolic link and
+            the symbol is defined locally or was forced to be local
+            because of a version file, we just want to emit a
+            RELATIVE reloc.  The entry in the global offset table
+            will already have been initialized in the
+            relocate_section function.  */
          if (!h->def_regular)
            return FALSE;
          BFD_ASSERT((h->got.offset & 1) != 0);
@@ -3065,6 +3471,7 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
       else
        {
          BFD_ASSERT((h->got.offset & 1) == 0);
+do_glob_dat:
          bfd_put_64 (output_bfd, (bfd_vma) 0, htab->elf.sgot->contents + h->got.offset);
          rela.r_info = ELF64_R_INFO (h->dynindx, R_390_GLOB_DAT);
          rela.r_addend = 0;
@@ -3135,6 +3542,8 @@ elf_s390_finish_dynamic_sections (bfd *output_bfd,
   struct elf_s390_link_hash_table *htab;
   bfd *dynobj;
   asection *sdyn;
+  bfd *ibfd;
+  unsigned int i;
 
   htab = elf_s390_hash_table (info);
   if (htab == NULL)
@@ -3228,6 +3637,38 @@ elf_s390_finish_dynamic_sections (bfd *output_bfd,
       elf_section_data (htab->elf.sgot->output_section)
        ->this_hdr.sh_entsize = 8;
     }
+
+  /* Finish dynamic symbol for local IFUNC symbols.  */
+  for (ibfd = info->input_bfds; ibfd != NULL; ibfd = ibfd->link_next)
+    {
+      struct plt_entry *local_plt;
+      Elf_Internal_Sym *isym;
+      Elf_Internal_Shdr *symtab_hdr;
+
+      symtab_hdr = &elf_symtab_hdr (ibfd);
+
+      local_plt = elf_s390_local_plt (ibfd);
+      if (local_plt != NULL)
+       for (i = 0; i < symtab_hdr->sh_info; i++)
+         {
+           if (local_plt[i].plt.offset != (bfd_vma) -1)
+             {
+               asection *sec = local_plt[i].sec;
+               isym = bfd_sym_from_r_symndx (&htab->sym_cache, ibfd, i);
+               if (isym == NULL)
+                 return FALSE;
+
+               if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+                 elf_s390_finish_ifunc_symbol (output_bfd, info, NULL, htab,
+                                               local_plt[i].plt.offset,
+                                               isym->st_value
+                                               + sec->output_section->vma
+                                               + sec->output_offset);
+
+             }
+         }
+    }
+
   return TRUE;
 }
 
@@ -3241,7 +3682,6 @@ elf_s390_plt_sym_val (bfd_vma i, const asection *plt,
   return plt->vma + PLT_FIRST_ENTRY_SIZE + i * PLT_ENTRY_SIZE;
 }
 
-
 /* Why was the hash table entry size definition changed from
    ARCH_SIZE/8 to 4? This breaks the 64 bit dynamic linker and
    this is the only reason for the s390_elf64_size_info structure.  */
@@ -3316,6 +3756,7 @@ const struct elf_size_info s390_elf64_size_info =
 #define elf_backend_init_index_section       _bfd_elf_init_1_index_section
 #define elf_backend_reloc_type_class         elf_s390_reloc_type_class
 #define elf_backend_plt_sym_val                      elf_s390_plt_sym_val
+#define elf_backend_add_symbol_hook           elf_s390_add_symbol_hook
 
 #define bfd_elf64_mkobject             elf_s390_mkobject
 #define elf_backend_object_p           elf_s390_object_p
index 0bd5dd2abf8619400a0c9616b07b937a7395d168..1d95fd8352f8e8a272bb5e458236af8341f905c7 100644 (file)
@@ -1996,6 +1996,7 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_390_GOT20",
   "BFD_RELOC_390_GOTPLT20",
   "BFD_RELOC_390_TLS_GOTIE20",
+  "BFD_RELOC_390_IRELATIVE",
   "BFD_RELOC_SCORE_GPREL15",
   "BFD_RELOC_SCORE_DUMMY2",
   "BFD_RELOC_SCORE_JMP",
index cc4a5db59a20af83fdef0abb8845bae0c59533a1..19c1f96a275132f35210653937b3f6aa682d04a4 100644 (file)
@@ -4693,6 +4693,11 @@ ENUMX
 ENUMDOC
   Long displacement extension.
 
+ENUM
+  BFD_RELOC_390_IRELATIVE
+ENUMDOC
+  STT_GNU_IFUNC relocation.
+
 ENUM
   BFD_RELOC_SCORE_GPREL15
 ENUMDOC
index aad44435fe2fa3c50c55d6278a2be97a81f38e16..08e1a793d0161d419820adb693e0998e6c6abf0f 100644 (file)
@@ -1,3 +1,7 @@
+2012-07-13  Andreas Krebbel  <Andreas.Krebbel@de.ibm.com>
+
+       * elf/s390.h (START_RELOC_NUMBERS): Define R_390_IRELATIVE reloc.
+
 2012-06-18  Doug Evans  <dje@google.com>
 
        * dwarf2.def (DW_OP): Add DW_OP_GNU_const_index.
index 807b7e8e08699c22214793a4fba3dff36a7c0b18..a5b4217bc5b0ae348936952fad450b1986c9b778 100644 (file)
@@ -119,6 +119,7 @@ START_RELOC_NUMBERS (elf_s390_reloc_type)
     RELOC_NUMBER (R_390_GOTPLT20, 59)  /* 20 bit offset to jump slot.  */
     RELOC_NUMBER (R_390_TLS_GOTIE20, 60)/* 20 bit GOT offset for statis TLS
                                           block offset.  */
+    RELOC_NUMBER (R_390_IRELATIVE, 61)  /* IFUNC relocation.  */
     /* These are GNU extensions to enable C++ vtable garbage collection.  */
     RELOC_NUMBER (R_390_GNU_VTINHERIT, 250)
     RELOC_NUMBER (R_390_GNU_VTENTRY, 251)
index a9525308f8150b91ada6bfac3a54587a783b2cee..a6c9665b34631728fedea4b9acccdfc9a6ba8eb5 100644 (file)
@@ -1,3 +1,8 @@
+2012-07-13  Andreas Krebbel  <Andreas.Krebbel@de.ibm.com>
+
+       * emulparams/elf_s390.sh (IREL_IN_PLT): Define.
+       * emulparams/elf64_s390.sh (IREL_IN_PLT): Define.
+
 2012-07-13  Nick Clifton  <nickc@redhat.com>
 
        PR ld/14357
index ae8f26e411e03968e0208efc57991ac96483c61d..61d4c029588aba7b61c29c8d7c42416998baca55 100644 (file)
@@ -12,6 +12,7 @@ TEMPLATE_NAME=elf32
 GENERATE_SHLIB_SCRIPT=yes 
 GENERATE_PIE_SCRIPT=yes
 NO_SMALL_DATA=yes
+IREL_IN_PLT=
 
 # Treat a host that matches the target with the possible exception of "x"
 # in the name as if it were native.
index 218558bd54e562dbe7d6523c427312418a6a4cac..8f35cf3cb05fd32b292529cad4e5e01c820151fe 100644 (file)
@@ -11,3 +11,4 @@ TEMPLATE_NAME=elf32
 GENERATE_SHLIB_SCRIPT=yes 
 GENERATE_PIE_SCRIPT=yes 
 NO_SMALL_DATA=yes
+IREL_IN_PLT=