]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - bfd/elf64-s390.c
Update copyright years
[thirdparty/binutils-gdb.git] / bfd / elf64-s390.c
index cc0e473119a0402497c44709caafe7219d5619cf..19cbe2f1e6721882fa53d15404a808a74eec5989 100644 (file)
@@ -1,6 +1,5 @@
 /* IBM S/390-specific support for 64-bit ELF
-   Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
-   2010, 2011, 2012 Free Software Foundation, Inc.
+   Copyright (C) 2000-2014 Free Software Foundation, Inc.
    Contributed Martin Schwidefsky (schwidefsky@de.ibm.com).
 
    This file is part of BFD, the Binary File Descriptor library.
 #include "bfdlink.h"
 #include "libbfd.h"
 #include "elf-bfd.h"
-
-static reloc_howto_type *elf_s390_reloc_type_lookup
-  PARAMS ((bfd *, bfd_reloc_code_real_type));
-static void elf_s390_info_to_howto
-  PARAMS ((bfd *, arelent *, Elf_Internal_Rela *));
-static bfd_boolean elf_s390_is_local_label_name
-  PARAMS ((bfd *, const char *));
-static struct bfd_hash_entry *link_hash_newfunc
-  PARAMS ((struct bfd_hash_entry *, struct bfd_hash_table *, const char *));
-static struct bfd_link_hash_table *elf_s390_link_hash_table_create
-  PARAMS ((bfd *));
-static bfd_boolean create_got_section
-  PARAMS((bfd *, struct bfd_link_info *));
-static bfd_boolean elf_s390_create_dynamic_sections
-  PARAMS((bfd *, struct bfd_link_info *));
-static void elf_s390_copy_indirect_symbol
-  PARAMS ((struct bfd_link_info *, struct elf_link_hash_entry *,
-          struct elf_link_hash_entry *));
-static bfd_boolean elf_s390_check_relocs
-  PARAMS ((bfd *, struct bfd_link_info *, asection *,
-          const Elf_Internal_Rela *));
-struct elf_s390_link_hash_entry;
-static void elf_s390_adjust_gotplt
-  PARAMS ((struct elf_s390_link_hash_entry *));
-static bfd_boolean elf_s390_adjust_dynamic_symbol
-  PARAMS ((struct bfd_link_info *, struct elf_link_hash_entry *));
-static bfd_boolean allocate_dynrelocs
-  PARAMS ((struct elf_link_hash_entry *, PTR));
-static bfd_boolean readonly_dynrelocs
-  PARAMS ((struct elf_link_hash_entry *, PTR));
-static bfd_boolean elf_s390_size_dynamic_sections
-  PARAMS ((bfd *, struct bfd_link_info *));
-static bfd_boolean elf_s390_relocate_section
-  PARAMS ((bfd *, struct bfd_link_info *, bfd *, asection *, bfd_byte *,
-          Elf_Internal_Rela *, Elf_Internal_Sym *, asection **));
-static bfd_boolean elf_s390_finish_dynamic_symbol
-  PARAMS ((bfd *, struct bfd_link_info *, struct elf_link_hash_entry *,
-          Elf_Internal_Sym *));
-static enum elf_reloc_type_class elf_s390_reloc_type_class
-  PARAMS ((const Elf_Internal_Rela *));
-static bfd_boolean elf_s390_finish_dynamic_sections
-  PARAMS ((bfd *, struct bfd_link_info *));
-static bfd_boolean elf_s390_object_p
-  PARAMS ((bfd *));
-static int elf_s390_tls_transition
-  PARAMS ((struct bfd_link_info *, int, int));
-static bfd_reloc_status_type s390_tls_reloc
-  PARAMS ((bfd *, arelent *, asymbol *, PTR, asection *, bfd *, char **));
-static bfd_vma dtpoff_base
-  PARAMS ((struct bfd_link_info *));
-static bfd_vma tpoff
-  PARAMS ((struct bfd_link_info *, bfd_vma));
-static void invalid_tls_insn
-  PARAMS ((bfd *, asection *, Elf_Internal_Rela *));
-static bfd_reloc_status_type s390_elf_ldisp_reloc
-  PARAMS ((bfd *, arelent *, asymbol *, PTR, asection *, bfd *, char **));
-
 #include "elf/s390.h"
 
 /* In case we're on a 32-bit machine, construct a 64-bit "-1" value
    from smaller values.  Start with zero, widen, *then* decrement.  */
 #define MINUS_ONE      (((bfd_vma)0) - 1)
 
+static bfd_reloc_status_type
+s390_tls_reloc (bfd *, arelent *, asymbol *, void *,
+               asection *, bfd *, char **);
+static bfd_reloc_status_type
+s390_elf_ldisp_reloc (bfd *, arelent *, asymbol *, void *,
+                     asection *, bfd *, char **);
+
 /* The relocation "howto" table.  */
 static reloc_howto_type elf_howto_table[] =
 {
   HOWTO (R_390_NONE,           /* type */
         0,                     /* rightshift */
-        0,                     /* size (0 = byte, 1 = short, 2 = long) */
+        0,                     /* size (0 = byte, 1 = 2 byte, 2 = 4 byte) */
         0,                     /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
@@ -219,6 +168,16 @@ 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),
+  HOWTO(R_390_PC12DBL,   1, 1, 12,  TRUE, 0, complain_overflow_bitfield,
+       bfd_elf_generic_reloc, "R_390_PC12DBL",  FALSE, 0,0x00000fff, TRUE),
+  HOWTO(R_390_PLT12DBL,  1, 1, 12,  TRUE, 0, complain_overflow_bitfield,
+       bfd_elf_generic_reloc, "R_390_PLT12DBL", FALSE, 0,0x00000fff, TRUE),
+  HOWTO(R_390_PC24DBL,   1, 2, 24,  TRUE, 0, complain_overflow_bitfield,
+       bfd_elf_generic_reloc, "R_390_PC24DBL",  FALSE, 0,0x00ffffff, TRUE),
+  HOWTO(R_390_PLT24DBL,  1, 2, 24,  TRUE, 0, complain_overflow_bitfield,
+       bfd_elf_generic_reloc, "R_390_PLT24DBL", FALSE, 0,0x00ffffff, TRUE),
 };
 
 /* GNU extension to record C++ vtable hierarchy.  */
@@ -228,9 +187,8 @@ static reloc_howto_type elf64_s390_vtentry_howto =
   HOWTO (R_390_GNU_VTENTRY, 0,4,0,FALSE,0,complain_overflow_dont, _bfd_elf_rel_vtable_reloc_fn,"R_390_GNU_VTENTRY", FALSE,0,0, FALSE);
 
 static reloc_howto_type *
-elf_s390_reloc_type_lookup (abfd, code)
-     bfd *abfd ATTRIBUTE_UNUSED;
-     bfd_reloc_code_real_type code;
+elf_s390_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
+                           bfd_reloc_code_real_type code)
 {
   switch (code)
     {
@@ -270,10 +228,18 @@ elf_s390_reloc_type_lookup (abfd, code)
       return &elf_howto_table[(int) R_390_GOT16];
     case BFD_RELOC_16_PCREL:
       return &elf_howto_table[(int) R_390_PC16];
+    case BFD_RELOC_390_PC12DBL:
+      return &elf_howto_table[(int) R_390_PC12DBL];
+    case BFD_RELOC_390_PLT12DBL:
+      return &elf_howto_table[(int) R_390_PLT12DBL];
     case BFD_RELOC_390_PC16DBL:
       return &elf_howto_table[(int) R_390_PC16DBL];
     case BFD_RELOC_390_PLT16DBL:
       return &elf_howto_table[(int) R_390_PLT16DBL];
+    case BFD_RELOC_390_PC24DBL:
+      return &elf_howto_table[(int) R_390_PC24DBL];
+    case BFD_RELOC_390_PLT24DBL:
+      return &elf_howto_table[(int) R_390_PLT24DBL];
     case BFD_RELOC_390_PC32DBL:
       return &elf_howto_table[(int) R_390_PC32DBL];
     case BFD_RELOC_390_PLT32DBL:
@@ -346,6 +312,8 @@ elf_s390_reloc_type_lookup (abfd, code)
       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:
@@ -381,10 +349,9 @@ elf_s390_reloc_name_lookup (bfd *abfd ATTRIBUTE_UNUSED,
    and elf64-s390.c has its own copy.  */
 
 static void
-elf_s390_info_to_howto (abfd, cache_ptr, dst)
-     bfd *abfd ATTRIBUTE_UNUSED;
-     arelent *cache_ptr;
-     Elf_Internal_Rela *dst;
+elf_s390_info_to_howto (bfd *abfd ATTRIBUTE_UNUSED,
+                       arelent *cache_ptr,
+                       Elf_Internal_Rela *dst)
 {
   unsigned int r_type = ELF64_R_TYPE(dst->r_info);
   switch (r_type)
@@ -410,15 +377,13 @@ elf_s390_info_to_howto (abfd, cache_ptr, dst)
 
 /* A relocation function which doesn't do anything.  */
 static bfd_reloc_status_type
-s390_tls_reloc (abfd, reloc_entry, symbol, data, input_section,
-               output_bfd, error_message)
-     bfd *abfd ATTRIBUTE_UNUSED;
-     arelent *reloc_entry;
-     asymbol *symbol ATTRIBUTE_UNUSED;
-     PTR data ATTRIBUTE_UNUSED;
-     asection *input_section;
-     bfd *output_bfd;
-     char **error_message ATTRIBUTE_UNUSED;
+s390_tls_reloc (bfd *abfd ATTRIBUTE_UNUSED,
+               arelent *reloc_entry,
+               asymbol *symbol ATTRIBUTE_UNUSED,
+               void * data ATTRIBUTE_UNUSED,
+               asection *input_section,
+               bfd *output_bfd,
+               char **error_message ATTRIBUTE_UNUSED)
 {
   if (output_bfd)
     reloc_entry->address += input_section->output_offset;
@@ -427,15 +392,13 @@ s390_tls_reloc (abfd, reloc_entry, symbol, data, input_section,
 
 /* Handle the large displacement relocs.  */
 static bfd_reloc_status_type
-s390_elf_ldisp_reloc (abfd, reloc_entry, symbol, data, input_section,
-                      output_bfd, error_message)
-     bfd *abfd;
-     arelent *reloc_entry;
-     asymbol *symbol;
-     PTR data;
-     asection *input_section;
-     bfd *output_bfd;
-     char **error_message ATTRIBUTE_UNUSED;
+s390_elf_ldisp_reloc (bfd *abfd,
+                     arelent *reloc_entry,
+                     asymbol *symbol,
+                     void * data,
+                     asection *input_section,
+                     bfd *output_bfd,
+                     char **error_message ATTRIBUTE_UNUSED)
 {
   reloc_howto_type *howto = reloc_entry->howto;
   bfd_vma relocation;
@@ -466,7 +429,7 @@ s390_elf_ldisp_reloc (abfd, reloc_entry, symbol, data, input_section,
       relocation -= reloc_entry->address;
     }
 
-  insn = bfd_get_32 (abfd, (bfd_byte *) data + reloc_entry->address); 
+  insn = bfd_get_32 (abfd, (bfd_byte *) data + reloc_entry->address);
   insn |= (relocation & 0xfff) << 16 | (relocation & 0xff000) >> 4;
   bfd_put_32 (abfd, insn, (bfd_byte *) data + reloc_entry->address);
 
@@ -478,9 +441,7 @@ s390_elf_ldisp_reloc (abfd, reloc_entry, symbol, data, input_section,
 }
 
 static bfd_boolean
-elf_s390_is_local_label_name (abfd, name)
-     bfd *abfd;
-     const char *name;
+elf_s390_is_local_label_name (bfd *abfd, const char *name)
 {
   if (name[0] == '.' && (name[1] == 'X' || name[1] == 'L'))
     return TRUE;
@@ -508,6 +469,8 @@ elf_s390_is_local_label_name (abfd, 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
@@ -610,17 +573,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;
 };
@@ -628,6 +617,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)
 
@@ -644,8 +636,7 @@ elf_s390_mkobject (bfd *abfd)
 }
 
 static bfd_boolean
-elf_s390_object_p (abfd)
-     bfd *abfd;
+elf_s390_object_p (bfd *abfd)
 {
   /* Set the right machine number for an s390 elf32 file.  */
   return bfd_default_set_arch_mach (abfd, bfd_arch_s390, bfd_mach_s390_64);
@@ -660,6 +651,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;
@@ -676,13 +668,15 @@ 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 *
-link_hash_newfunc (entry, table, string)
-     struct bfd_hash_entry *entry;
-     struct bfd_hash_table *table;
-     const char *string;
+link_hash_newfunc (struct bfd_hash_entry *entry,
+                  struct bfd_hash_table *table,
+                  const char *string)
 {
   /* Allocate the structure if it has not already been allocated by a
      subclass.  */
@@ -704,6 +698,8 @@ link_hash_newfunc (entry, table, string)
       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;
@@ -712,13 +708,12 @@ link_hash_newfunc (entry, table, string)
 /* Create an s390 ELF linker hash table.  */
 
 static struct bfd_link_hash_table *
-elf_s390_link_hash_table_create (abfd)
-     bfd *abfd;
+elf_s390_link_hash_table_create (bfd *abfd)
 {
   struct elf_s390_link_hash_table *ret;
   bfd_size_type amt = sizeof (struct elf_s390_link_hash_table);
 
-  ret = (struct elf_s390_link_hash_table *) bfd_malloc (amt);
+  ret = (struct elf_s390_link_hash_table *) bfd_zmalloc (amt);
   if (ret == NULL)
     return NULL;
 
@@ -730,16 +725,6 @@ elf_s390_link_hash_table_create (abfd)
       return NULL;
     }
 
-  ret->elf.sgot = NULL;
-  ret->elf.sgotplt = NULL;
-  ret->elf.srelgot = NULL;
-  ret->elf.splt = NULL;
-  ret->elf.srelplt = NULL;
-  ret->sdynbss = NULL;
-  ret->srelbss = NULL;
-  ret->tls_ldm_got.refcount = 0;
-  ret->sym_cache.abfd = NULL;
-
   return &ret->elf.root;
 }
 
@@ -759,9 +744,9 @@ create_got_section (bfd *dynobj,
   if (htab == NULL)
     return FALSE;
 
-  htab->elf.sgot = bfd_get_section_by_name (dynobj, ".got");
-  htab->elf.sgotplt = bfd_get_section_by_name (dynobj, ".got.plt");
-  htab->elf.srelgot = bfd_get_section_by_name (dynobj, ".rela.got");
+  htab->elf.sgot = bfd_get_linker_section (dynobj, ".got");
+  htab->elf.sgotplt = bfd_get_linker_section (dynobj, ".got.plt");
+  htab->elf.srelgot = bfd_get_linker_section (dynobj, ".rela.got");
   if (!htab->elf.sgot || !htab->elf.sgotplt || !htab->elf.srelgot)
     abort ();
   return TRUE;
@@ -787,11 +772,11 @@ elf_s390_create_dynamic_sections (bfd *dynobj,
   if (!_bfd_elf_create_dynamic_sections (dynobj, info))
     return FALSE;
 
-  htab->elf.splt = bfd_get_section_by_name (dynobj, ".plt");
-  htab->elf.srelplt = bfd_get_section_by_name (dynobj, ".rela.plt");
-  htab->sdynbss = bfd_get_section_by_name (dynobj, ".dynbss");
+  htab->elf.splt = bfd_get_linker_section (dynobj, ".plt");
+  htab->elf.srelplt = bfd_get_linker_section (dynobj, ".rela.plt");
+  htab->sdynbss = bfd_get_linker_section (dynobj, ".dynbss");
   if (!info->shared)
-    htab->srelbss = bfd_get_section_by_name (dynobj, ".rela.bss");
+    htab->srelbss = bfd_get_linker_section (dynobj, ".rela.bss");
 
   if (!htab->elf.splt || !htab->elf.srelplt || !htab->sdynbss
       || (!info->shared && !htab->srelbss))
@@ -803,9 +788,9 @@ elf_s390_create_dynamic_sections (bfd *dynobj,
 /* Copy the extra info we tack onto an elf_link_hash_entry.  */
 
 static void
-elf_s390_copy_indirect_symbol (info, dir, ind)
-     struct bfd_link_info *info;
-     struct elf_link_hash_entry *dir, *ind;
+elf_s390_copy_indirect_symbol (struct bfd_link_info *info,
+                              struct elf_link_hash_entry *dir,
+                              struct elf_link_hash_entry *ind)
 {
   struct elf_s390_link_hash_entry *edir, *eind;
 
@@ -867,10 +852,9 @@ elf_s390_copy_indirect_symbol (info, dir, ind)
 }
 
 static int
-elf_s390_tls_transition (info, r_type, is_local)
-     struct bfd_link_info *info;
-     int r_type;
-     int is_local;
+elf_s390_tls_transition (struct bfd_link_info *info,
+                        int r_type,
+                        int is_local)
 {
   if (info->shared)
     return r_type;
@@ -933,6 +917,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);
 
@@ -945,13 +930,44 @@ 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 (htab->elf.dynobj == NULL)
+               htab->elf.dynobj = abfd;
+
+             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];
          while (h->root.type == bfd_link_hash_indirect
                 || h->root.type == bfd_link_hash_warning)
            h = (struct elf_link_hash_entry *) h->root.u.i.link;
+
+         /* PR15323, ref flags aren't set for references in the same
+            object.  */
+         h->root.non_ir_ref = 1;
        }
 
       /* Create got section and local_got_refcounts array if they
@@ -983,18 +999,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:
@@ -1010,6 +1019,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:
@@ -1017,10 +1045,15 @@ 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_PLT12DBL:
        case R_390_PLT16DBL:
+       case R_390_PLT24DBL:
        case R_390_PLT32:
        case R_390_PLT32DBL:
        case R_390_PLT64:
@@ -1159,12 +1192,14 @@ elf_s390_check_relocs (bfd *abfd,
        case R_390_16:
        case R_390_32:
        case R_390_64:
+       case R_390_PC12DBL:
        case R_390_PC16:
        case R_390_PC16DBL:
+       case R_390_PC24DBL:
        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
@@ -1174,9 +1209,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
@@ -1203,7 +1241,9 @@ elf_s390_check_relocs (bfd *abfd,
          if ((info->shared
               && (sec->flags & SEC_ALLOC) != 0
               && ((ELF64_R_TYPE (rel->r_info) != R_390_PC16
+                   && ELF64_R_TYPE (rel->r_info) != R_390_PC12DBL
                    && ELF64_R_TYPE (rel->r_info) != R_390_PC16DBL
+                   && ELF64_R_TYPE (rel->r_info) != R_390_PC24DBL
                    && ELF64_R_TYPE (rel->r_info) != R_390_PC32
                    && ELF64_R_TYPE (rel->r_info) != R_390_PC32DBL
                    && ELF64_R_TYPE (rel->r_info) != R_390_PC64)
@@ -1249,7 +1289,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);
@@ -1281,6 +1320,8 @@ elf_s390_check_relocs (bfd *abfd,
 
              p->count += 1;
              if (ELF64_R_TYPE (rel->r_info) == R_390_PC16
+                 || ELF64_R_TYPE (rel->r_info) == R_390_PC12DBL
+                 || ELF64_R_TYPE (rel->r_info) == R_390_PC16DBL
                  || ELF64_R_TYPE (rel->r_info) == R_390_PC16DBL
                  || ELF64_R_TYPE (rel->r_info) == R_390_PC32
                  || ELF64_R_TYPE (rel->r_info) == R_390_PC32DBL
@@ -1389,6 +1430,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);
@@ -1435,7 +1493,9 @@ elf_s390_gc_sweep_hook (bfd *abfd,
        case R_390_32:
        case R_390_64:
        case R_390_PC16:
+       case R_390_PC12DBL:
        case R_390_PC16DBL:
+       case R_390_PC24DBL:
        case R_390_PC32:
        case R_390_PC32DBL:
        case R_390_PC64:
@@ -1443,7 +1503,9 @@ elf_s390_gc_sweep_hook (bfd *abfd,
            break;
          /* Fall through */
 
+       case R_390_PLT12DBL:
        case R_390_PLT16DBL:
+       case R_390_PLT24DBL:
        case R_390_PLT32:
        case R_390_PLT32DBL:
        case R_390_PLT64:
@@ -1494,8 +1556,7 @@ elf_s390_gc_sweep_hook (bfd *abfd,
    created (we're only linking static objects).  */
 
 static void
-elf_s390_adjust_gotplt (h)
-     struct elf_s390_link_hash_entry *h;
+elf_s390_adjust_gotplt (struct elf_s390_link_hash_entry *h)
 {
   if (h->elf.root.type == bfd_link_hash_warning)
     h = (struct elf_s390_link_hash_entry *) h->elf.root.u.i.link;
@@ -1522,6 +1583,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).  */
@@ -1648,7 +1713,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)
@@ -1659,8 +1724,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.  */
@@ -1777,7 +1847,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;
 
@@ -1868,9 +1937,7 @@ allocate_dynrelocs (struct elf_link_hash_entry *h,
 /* Find any dynamic relocs that apply to read-only sections.  */
 
 static bfd_boolean
-readonly_dynrelocs (h, inf)
-     struct elf_link_hash_entry *h;
-     PTR inf;
+readonly_dynrelocs (struct elf_link_hash_entry *h, void * inf)
 {
   struct elf_s390_link_hash_entry *eh;
   struct elf_dyn_relocs *p;
@@ -1918,7 +1985,7 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
       /* Set the contents of the .interp section to the interpreter.  */
       if (info->executable)
        {
-         s = bfd_get_section_by_name (dynobj, ".interp");
+         s = bfd_get_linker_section (dynobj, ".interp");
          if (s == NULL)
            abort ();
          s->size = sizeof ELF_DYNAMIC_INTERPRETER;
@@ -1936,6 +2003,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;
@@ -1988,6 +2057,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)
@@ -2003,7 +2086,7 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
 
   /* Allocate global sym .plt and .got entries, and space for global
      sym dynamic relocs.  */
-  elf_link_hash_traverse (&htab->elf, allocate_dynrelocs, (PTR) info);
+  elf_link_hash_traverse (&htab->elf, allocate_dynrelocs, info);
 
   /* We now have determined the sizes of the various dynamic sections.
      Allocate memory for them.  */
@@ -2016,7 +2099,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.  */
@@ -2101,7 +2187,7 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
             then we need a DT_TEXTREL entry.  */
          if ((info->flags & DF_TEXTREL) == 0)
            elf_link_hash_traverse (&htab->elf, readonly_dynrelocs,
-                                   (PTR) info);
+                                   info);
 
          if ((info->flags & DF_TEXTREL) != 0)
            {
@@ -2120,8 +2206,7 @@ elf_s390_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
    This is PT_TLS segment p_vaddr.  */
 
 static bfd_vma
-dtpoff_base (info)
-     struct bfd_link_info *info;
+dtpoff_base (struct bfd_link_info *info)
 {
   /* If tls_sec is NULL, we should have signalled an error already.  */
   if (elf_hash_table (info)->tls_sec == NULL)
@@ -2133,9 +2218,7 @@ dtpoff_base (info)
    if STT_TLS virtual address is ADDRESS.  */
 
 static bfd_vma
-tpoff (info, address)
-     struct bfd_link_info *info;
-     bfd_vma address;
+tpoff (struct bfd_link_info *info, bfd_vma address)
 {
   struct elf_link_hash_table *htab = elf_hash_table (info);
 
@@ -2149,10 +2232,9 @@ tpoff (info, address)
    instruction.  */
 
 static void
-invalid_tls_insn (input_bfd, input_section, rel)
-     bfd *input_bfd;
-     asection *input_section;
-     Elf_Internal_Rela *rel;
+invalid_tls_insn (bfd *input_bfd,
+                 asection *input_section,
+                 Elf_Internal_Rela *rel)
 {
   reloc_howto_type *howto;
 
@@ -2210,6 +2292,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
@@ -2232,16 +2315,70 @@ 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_PLTOFF16:
+               case R_390_PLTOFF32:
+               case R_390_PLTOFF64:
+                 relocation -= htab->elf.sgot->output_section->vma;
+                 break;
+               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
        {
          bfd_boolean warned ATTRIBUTE_UNUSED;
+         bfd_boolean ignored ATTRIBUTE_UNUSED;
 
          RELOC_FOR_GLOBAL_SYMBOL (info, input_bfd, input_section, rel,
                                   r_symndx, symtab_hdr, sym_hashes,
                                   h, sec, relocation,
-                                  unresolved_reloc, warned);
+                                  unresolved_reloc, warned, ignored);
        }
 
       if (sec != NULL && discarded_section (sec))
@@ -2272,18 +2409,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.  */
@@ -2296,7 +2443,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)
@@ -2305,11 +2452,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
@@ -2328,9 +2493,40 @@ 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;
                    }
+
+                 if ((h->def_regular
+                      && info->shared
+                      && SYMBOL_REFERENCES_LOCAL (info, h))
+                     /* lgrl rx,sym@GOTENT -> larl rx, sym */
+                     && ((r_type == R_390_GOTENT
+                          && (bfd_get_16 (input_bfd,
+                                          contents + rel->r_offset - 2)
+                              & 0xff0f) == 0xc408)
+                         /* lg rx, sym@GOT(r12) -> larl rx, sym */
+                         || (r_type == R_390_GOT20
+                             && (bfd_get_32 (input_bfd,
+                                             contents + rel->r_offset - 2)
+                                 & 0xff00f000) == 0xe300c000
+                             && bfd_get_8 (input_bfd,
+                                           contents + rel->r_offset + 3) == 0x04)))
+
+                   {
+                     unsigned short new_insn =
+                       (0xc000 | (bfd_get_8 (input_bfd,
+                                             contents + rel->r_offset - 1) & 0xf0));
+                     bfd_put_16 (output_bfd, new_insn,
+                                 contents + rel->r_offset - 2);
+                     r_type = R_390_PC32DBL;
+                     rel->r_addend = 2;
+                     howto = elf_howto_table + r_type;
+                     relocation = h->root.u.def.value
+                       + h->root.u.def.section->output_section->vma
+                       + h->root.u.def.section->output_offset;
+                     goto do_relocation;
+                   }
                }
              else
                unresolved_reloc = FALSE;
@@ -2379,7 +2575,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
@@ -2387,7 +2583,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;
 
@@ -2412,7 +2608,9 @@ elf_s390_relocate_section (bfd *output_bfd,
          unresolved_reloc = FALSE;
          break;
 
+       case R_390_PLT12DBL:
        case R_390_PLT16DBL:
+       case R_390_PLT24DBL:
        case R_390_PLT32:
        case R_390_PLT32DBL:
        case R_390_PLT64:
@@ -2425,17 +2623,21 @@ elf_s390_relocate_section (bfd *output_bfd,
            break;
 
          if (h->plt.offset == (bfd_vma) -1
-             || htab->elf.splt == NULL)
+             || (htab->elf.splt == NULL && !s390_is_ifunc_symbol_p (h)))
            {
              /* 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;
 
@@ -2447,18 +2649,24 @@ elf_s390_relocate_section (bfd *output_bfd,
 
          /* For local symbols or if we didn't make a PLT entry for
             this symbol resolve the symbol directly.  */
-         if (   h == NULL
+         if (h == NULL
              || h->plt.offset == (bfd_vma) -1
-             || htab->elf.splt == NULL)
+             || (htab->elf.splt == NULL && !s390_is_ifunc_symbol_p (h)))
            {
              relocation -= htab->elf.sgot->output_section->vma;
              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;
 
@@ -2467,10 +2675,74 @@ elf_s390_relocate_section (bfd *output_bfd,
        case R_390_32:
        case R_390_64:
        case R_390_PC16:
+       case R_390_PC12DBL:
        case R_390_PC16DBL:
+       case R_390_PC24DBL:
        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;
 
@@ -2479,7 +2751,9 @@ elf_s390_relocate_section (bfd *output_bfd,
                   || ELF_ST_VISIBILITY (h->other) == STV_DEFAULT
                   || h->root.type != bfd_link_hash_undefweak)
               && ((r_type != R_390_PC16
+                   && r_type != R_390_PC12DBL
                    && r_type != R_390_PC16DBL
+                   && r_type != R_390_PC24DBL
                    && r_type != R_390_PC32
                    && r_type != R_390_PC32DBL
                    && r_type != R_390_PC64)
@@ -2521,7 +2795,9 @@ elf_s390_relocate_section (bfd *output_bfd,
              else if (h != NULL
                       && h->dynindx != -1
                       && (r_type == R_390_PC16
+                          || r_type == R_390_PC12DBL
                           || r_type == R_390_PC16DBL
+                          || r_type == R_390_PC24DBL
                           || r_type == R_390_PC32
                           || r_type == R_390_PC32DBL
                           || r_type == R_390_PC64
@@ -2956,6 +3232,15 @@ elf_s390_relocate_section (bfd *output_bfd,
           howto->name,
           h->root.root.string);
 
+    do_relocation:
+
+      /* When applying a 24 bit reloc we need to start one byte
+        earlier.  Otherwise the 32 bit get/put bfd operations might
+        access a byte after the actual section.  */
+      if (r_type == R_390_PC24DBL
+         || r_type == R_390_PLT24DBL)
+       rel->r_offset--;
+
       if (r_type == R_390_20
          || r_type == R_390_GOT20
          || r_type == R_390_GOTPLT20
@@ -3012,6 +3297,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.  */
 
@@ -3022,6 +3393,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)
@@ -3036,65 +3408,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;
+           }
        }
     }
 
@@ -3115,14 +3504,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);
@@ -3134,6 +3546,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;
@@ -3168,7 +3581,7 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
     }
 
   /* Mark some specially defined symbols as absolute.  */
-  if (strcmp (h->root.root.string, "_DYNAMIC") == 0
+  if (h == htab->elf.hdynamic
       || h == htab->elf.hgot
       || h == htab->elf.hplt)
     sym->st_shndx = SHN_ABS;
@@ -3180,8 +3593,9 @@ elf_s390_finish_dynamic_symbol (bfd *output_bfd,
    dynamic linker, before writing them out.  */
 
 static enum elf_reloc_type_class
-elf_s390_reloc_type_class (rela)
-     const Elf_Internal_Rela *rela;
+elf_s390_reloc_type_class (const struct bfd_link_info *info ATTRIBUTE_UNUSED,
+                          const asection *rel_sec ATTRIBUTE_UNUSED,
+                          const Elf_Internal_Rela *rela)
 {
   switch ((int) ELF64_R_TYPE (rela->r_info))
     {
@@ -3205,13 +3619,15 @@ 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)
     return FALSE;
 
   dynobj = htab->elf.dynobj;
-  sdyn = bfd_get_section_by_name (dynobj, ".dynamic");
+  sdyn = bfd_get_linker_section (dynobj, ".dynamic");
 
   if (htab->elf.dynamic_sections_created)
     {
@@ -3276,8 +3692,9 @@ elf_s390_finish_dynamic_sections (bfd *output_bfd,
                       - htab->elf.splt->output_section->vma - 6)/2,
                      htab->elf.splt->contents + 8);
        }
-      elf_section_data (htab->elf.splt->output_section)
-       ->this_hdr.sh_entsize = PLT_ENTRY_SIZE;
+      if (elf_section_data (htab->elf.splt->output_section) != NULL)
+       elf_section_data (htab->elf.splt->output_section)->this_hdr.sh_entsize
+         = PLT_ENTRY_SIZE;
     }
 
   if (htab->elf.sgotplt)
@@ -3298,6 +3715,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;
 }
 
@@ -3311,7 +3760,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.  */
@@ -3384,10 +3832,25 @@ const struct elf_size_info s390_elf64_size_info =
 #define elf_backend_relocate_section         elf_s390_relocate_section
 #define elf_backend_size_dynamic_sections     elf_s390_size_dynamic_sections
 #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
 
+/* Enable ELF64 archive functions.  */
+#define bfd_elf64_archive_functions
+extern bfd_boolean bfd_elf64_archive_slurp_armap (bfd *);
+extern bfd_boolean bfd_elf64_archive_write_armap (bfd *, unsigned int, struct orl *, unsigned int, int);
+
+#define bfd_elf64_archive_slurp_extended_name_table    _bfd_archive_coff_slurp_extended_name_table
+#define bfd_elf64_archive_construct_extended_name_table _bfd_archive_coff_construct_extended_name_table
+#define bfd_elf64_archive_truncate_arname              _bfd_archive_coff_truncate_arname
+#define bfd_elf64_archive_read_ar_hdr                  _bfd_archive_coff_read_ar_hdr
+#define bfd_elf64_archive_write_ar_hdr                 _bfd_archive_coff_write_ar_hdr
+#define bfd_elf64_archive_openr_next_archived_file     _bfd_archive_coff_openr_next_archived_file
+#define bfd_elf64_archive_get_elt_at_index             _bfd_archive_coff_get_elt_at_index
+#define bfd_elf64_archive_generic_stat_arch_elt        _bfd_archive_coff_generic_stat_arch_elt
+#define bfd_elf64_archive_update_armap_timestamp       _bfd_archive_coff_update_armap_timestamp
+
 #include "elf64-target.h"