[RFC] Add IFUNC support for MIPS

Faraz Shahbazker Faraz.Shahbazker@imgtec.com
Tue Jul 21 12:08:00 GMT 2015


Hi,

I have revised the patch with more suggestions from the previous discussions. 
Detailed description is in the attached text file. I think I have got a better handle on
symbol preemption with this version, but I would really appreciate some input on that
aspect.

Previous discussions for reference:
https://www.sourceware.org/ml/binutils/2013-08/msg00121.html
https://www.sourceware.org/ml/binutils/2013-11/msg00155.html
https://sourceware.org/ml/binutils/2013-12/msg00058.html

Tests have been split out in to a separate patch and attached for review. 
These are carried forward from the previous iteration. I am working on translating 
more of my informal tests to the correct format.

Regards,
Faraz Shahbazker


bfd/ChangeLog:

	* bfd-in2.h (BFD_RELOC_MIPS_IRELATIVE): New relocation.
	* elf32-mips.c
	(elf_mips_eh_howto table): Howto for BFD_RELOC_MIPS_IRELATIVE.
	(bfd_elf32_bfd_reloc_type_lookup): Case for R_MIPS_IRELATIVE.
	(bfd_elf32_bfd_reloc_name_lookup): Case for R_MIPS_IRELATIVE.
	(mips_elf32_rtype_to_howto): Case for R_MIPS_IRELATIVE
	* elfxx-mips.c
	(struct mips_got_info): New fields general_gotno and
	assigned_general_gotno.
	(struct mips_elf_link_hash_entry): New fields for offset in to
	IPLT/IGOT and flags to indicate symbol needs IPLT or IRELOC.
	(mips_elf_link_hash_table): New fields for size of IPLT stubs
	and hash-table for local IFUNC symbols.
	(MIPS16_P): New macro to check ASE flag.
	(mips16_exec_iplt_entry): Template for mips16 IPLT stub.
	(mips32_exec_iplt_entry): Template for mips32 IPLT stub.
	(mips32r6_exec_iplt_entry): Template for mips32 R6 IPLT stub.
	(micromips32_exec_iplt_entry): Template for micromips32 stub.
	(mips64_exec_iplt_entry): Template for mips64 IPLT stub.
	(mips64_48b_exec_iplt_entry): Template for mips64 IPLT stub.
	(mips_elf_link_hash_newfunc): Initialization of new
	mips_elf_link_hash_entry elements.
	(mips_elf_create_stub_symbol): Set ISA bit in address and ST_MIPS16
	type for mips16 IPLT stubs.
	(mips_elf_rel_dyn_section): Moved up to avoid forward declaration.
	(mips_get_irel_section): New function.
	(mips_elf_allocate_ireloc): Likewise.
	(mips_elf_allocate_iplt): Likewise.
	(mips_elf_check_local_symbols): Likewise.
	(mips_elf_check_symbols): Allocate an IPLT entry and/or dynamic
	relocation for IFUNC symbols.
	(mips_elf_count_got_entry): Count local IFUNCs under general
	GOT entries instead of local.
	(mips_elf_got16_entry): Add argument h and pass it to
	mips_elf_create_local_got_entry instead of NULL.
	(mips_elf_create_local_got_entry): Add check for sufficient general
	GOT entries. Assign local IFUNCs from general GOT entries pool.
	(mips_elf_count_general_got_symbols): New function.
	(mips_elf_deallocate_irelocs): Likewise.
	(mips_elf_multi_got): Traverse primary GOT entries and deallocate
	previously allocated IRELOCs for symbols marked RELOC_ONLY.
	(mips_elf_create_ifunc_sections): New function.
	(get_local_sym_hash): Likewise.
        (mips_elf_calculate_relocation): Create hash-table for local IFUNC
	symbols and condition them to be accessed through GOT.
	Point IFUNC symbol value to IPLT stub for symbols that need IPLT.
	(mips_elf_create_dynamic_relocation): Emit IRELATIVE relocation
	instead of REL32 for local IFUNC reference.
        (_bfd_mips_elf_section_processing): Size .igot section.
	(_bfd_mips_elf_add_symbol_hook): Mark output as has_gnu_symbols.
	(_bfd_mips_elf_check_relocs): Check need to create IFUNC sections.
	If symbol is an IFUNC, don't convert it to an STT_FUNC. Relax error
	checking to allow local IFUNCs to be accessed via call16 reloc.
	(_bfd_mips_elf_adjust_dynamic_symbol): Add STT_GNU_IFUNC to sanity
	check.
	(_bfd_mips_elf_always_size_sections): Allocate relocs for local IFUNCs.
	(mips_elf_lay_out_got): Count general GOT entries. Offset local entries
	to follow general entries.
	(_bfd_mips_elf_size_dynamic_sections): Exclude IPLT and IGOT.
	Create dynamic tag for DT_MIPS_GENERAL_GOTNO if needed.
	(_bfd_mips_elf_relocate_section): Relax error checking to allow
	local IFUNCs to be accessed via standalone got16 reloc.
	(mips_elf_create_iplt): New function.
	(misp_elf_check_local_got_index): Likewise.
	(mips_elf_create_ireloc): Likewise.
	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IREL
	relocation for IFUNC symbols as necessary. Set IFUNC symbol value
	to the IPLT entry address for executable objects.
	(_bfd_mips_elf_finish_local_dynamic_symbol): New function.
	(_bfd_mips_elf_finish_dynamic_sections): Call
	_bfd_mips_elf_finish_local_dynamic_symbol for all local IFUNCs.
	Set values of dynamic tag - DT_MIPS_GENERAL_GOTNO.
	(local_htab_hash): New function, hash table for local IFUNCs.
	(loc_htab_eq): New comparison function for local IFUNC hash table.
	(_bfd_mips_elf_link_hash_table_free): New function.
	(_bfd_mips_elf_link_hash_table_create): Allocate a hash table for
	local IFUNCs.
	( _bfd_mips_elf_get_target_dtag): Add cases for dynamic tag -
	DT_MIPS_GENERAL_GOTNO.
	* libbfd.h
	(bfd_reloc_code_real_names): Entry for BFD_RELOC_MIPS_IRELATIVE.
	* reloc.c
	(ENUMDOC): BFD_RELOC_MIPS_IRELATIVE entry.

binutils/ChangeLog:

	* readelf.c:
	(get_mips_dynamic_type): Add case for DT_MIPS_GENERAL_GOTNO.
	(dynamic_section_mips_val): Add case for DT_MIPS_GENERAL_GOTNO.

elfcpp/ChangeLog:

	* elfcpp.h
	(enum DT): Add cases for dynamic tag DT_MIPS_GENERAL_GOTNO.

include/elf/ChangeLog:

	* mips.h
	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.
	(DT_MIPS_GENERAL_GOTNO): New dynamic tag.
---
 bfd/bfd-in2.h      |    3 +
 bfd/elf32-mips.c   |   22 ++
 bfd/elfxx-mips.c   |  991 ++++++++++++++++++++++++++++++++++++++++++++++++----
 bfd/libbfd.h       |    1 +
 bfd/reloc.c        |    5 +
 binutils/readelf.c |    3 +
 elfcpp/elfcpp.h    |    2 +
 include/elf/mips.h |    7 +
 8 files changed, 964 insertions(+), 70 deletions(-)

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index c27db8d..7eec1b5 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -3038,6 +3038,9 @@ to compensate for the borrow when the low bits are added.  */
   BFD_RELOC_MIPS_JUMP_SLOT,
 
 
+/* MIPS support for STT_GNU_IFUNC.  */
+  BFD_RELOC_MIPS_IRELATIVE,
+
 /* Moxie ELF relocations.  */
   BFD_RELOC_MOXIE_10_PCREL,
 
diff --git a/bfd/elf32-mips.c b/bfd/elf32-mips.c
index fff08e5..7f9c9b4 100644
--- a/bfd/elf32-mips.c
+++ b/bfd/elf32-mips.c
@@ -1646,6 +1646,22 @@ static reloc_howto_type elf_mips_eh_howto =
 	 0xffffffff,	        /* dst_mask */
 	 FALSE);		/* pcrel_offset */
 
+/* STT_GNU_IFUNC support: */
+static reloc_howto_type elf_mips_irelative_howto =
+  HOWTO (R_MIPS_IRELATIVE,	/* type */
+	 0,			/* rightshift */
+	 2,			/* size (0 = byte, 1 = short, 2 = long) */
+	 32,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 0,			/* bitpos */
+	 complain_overflow_bitfield,/* complain_on_overflow */
+	 bfd_elf_generic_reloc, /* special_function */
+	 "R_MIPS_IRELATIVE",	/* name */
+	 TRUE,			/* partial_inplace */
+	 0xffffffff,		/* src_mask */
+	 0xffffffff,		/* dst_mask */
+	 FALSE);		/* pcrel_offset */
+
 /* Set the GP value for OUTPUT_BFD.  Returns FALSE if this is a
    dangerous relocation.  */
 
@@ -2126,6 +2142,8 @@ bfd_elf32_bfd_reloc_type_lookup (bfd *abfd, bfd_reloc_code_real_type code)
       return &elf_mips_jump_slot_howto;
     case BFD_RELOC_MIPS_EH:
       return &elf_mips_eh_howto;
+    case BFD_RELOC_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     }
 }
 
@@ -2173,6 +2191,8 @@ bfd_elf32_bfd_reloc_name_lookup (bfd *abfd ATTRIBUTE_UNUSED,
     return &elf_mips_jump_slot_howto;
   if (strcasecmp (elf_mips_eh_howto.name, r_name) == 0)
     return &elf_mips_eh_howto;
+  if (strcasecmp (elf_mips_irelative_howto.name, r_name) == 0)
+    return &elf_mips_irelative_howto;
 
   return NULL;
 }
@@ -2199,6 +2219,8 @@ mips_elf32_rtype_to_howto (unsigned int r_type,
       return &elf_mips_jump_slot_howto;
     case R_MIPS_EH:
       return &elf_mips_eh_howto;
+    case R_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     default:
       if (r_type >= R_MICROMIPS_min && r_type < R_MICROMIPS_max)
 	return &elf_micromips_howto_table_rel[r_type - R_MICROMIPS_min];
diff --git a/bfd/elfxx-mips.c b/bfd/elfxx-mips.c
index 9932453..521fcd6 100644
--- a/bfd/elfxx-mips.c
+++ b/bfd/elfxx-mips.c
@@ -45,6 +45,7 @@
 #include "coff/mips.h"
 
 #include "hashtab.h"
+#include "objalloc.h"
 
 /* Types of TLS GOT entry.  */
 enum mips_got_tls_type {
@@ -165,10 +166,14 @@ struct mips_got_info
   unsigned int tls_assigned_gotno;
   /* The number of local .got entries, eventually including page entries.  */
   unsigned int local_gotno;
+  /* The number of explicitly relocated .got entries.  */
+  unsigned int general_gotno;
   /* The maximum number of page entries needed.  */
   unsigned int page_gotno;
   /* The number of relocations needed for the GOT entries.  */
   unsigned int relocs;
+  /* The first unused general .got entry.  */
+  unsigned int assigned_general_gotno;
   /* The first unused local .got entry.  */
   unsigned int assigned_low_gotno;
   /* The last unused local .got entry.  */
@@ -375,6 +380,12 @@ struct mips_elf_link_hash_entry
      being called returns a floating point value.  */
   asection *call_fp_stub;
 
+  /* Offset into the IPLT table.  */
+  bfd_vma iplt_offset;
+
+  /* Offset into the IGOT table.  */
+  bfd_vma igot_offset;
+
   /* The highest GGA_* value that satisfies all references to this symbol.  */
   unsigned int global_got_area : 2;
 
@@ -413,6 +424,12 @@ struct mips_elf_link_hash_entry
 
   /* Does this symbol resolve to a PLT entry?  */
   unsigned int use_plt_entry : 1;
+
+  /* Does this symbol need an IPLT stub?  */
+  unsigned int needs_iplt : 1;
+
+  /* Does this ifunc symbol need an IGOT entry?  */
+  unsigned int needs_ireloc : 1;
 };
 
 /* MIPS ELF linker hash table.  */
@@ -485,6 +502,9 @@ struct mips_elf_link_hash_table
   /* The index of the next .got.plt entry to create.  */
   bfd_vma plt_got_index;
 
+  /* The size of an IPLT entry in bytes.  */
+  bfd_vma iplt_entry_size;
+
   /* The number of functions that need a lazy-binding stub.  */
   bfd_vma lazy_stub_count;
 
@@ -516,6 +536,10 @@ struct mips_elf_link_hash_table
 
   /* Is the PLT header compressed?  */
   unsigned int plt_header_is_comp : 1;
+
+  /* Used by local STT_GNU_IFUNC symbols.  */
+  htab_t loc_hash_table;
+  void *loc_hash_memory;
 };
 
 /* Get the MIPS ELF linker hash table from a link_info structure.  */
@@ -800,6 +824,10 @@ static bfd *reldyn_sorting_bfd;
 #define MICROMIPS_P(abfd) \
   ((elf_elfheader (abfd)->e_flags & EF_MIPS_ARCH_ASE_MICROMIPS) != 0)
 
+/* Nonzero if ABFD has mips16 code.  */
+#define MIPS16_P(abfd) \
+  ((elf_elfheader (abfd)->e_flags & EF_MIPS_ARCH_ASE_M16) != 0)
+
 /* Nonzero if ABFD is MIPS R6.  */
 #define MIPSR6_P(abfd) \
   ((elf_elfheader (abfd)->e_flags & EF_MIPS_ARCH) == E_MIPS_ARCH_32R6 \
@@ -1191,6 +1219,69 @@ static const bfd_vma mips_vxworks_shared_plt_entry[] =
   0x10000000,	/* b .PLT_resolver	*/
   0x24180000	/* li t8, <pltindex>	*/
 };
+
+/* The format of MIPS16 o32 IPLT entries.  We use v0 ($2)
+   and v1 ($3) as temporaries because t8 ($24) and t9 ($25) are not
+   directly addressable.  */
+static const bfd_vma mips16_exec_iplt_entry[] =
+{
+  0xb202,		/* lw 	 $2, 8($pc)       	*/
+  0x9a60,		/* lw 	 $3, 0($2)		*/
+  0xeb00,		/* jr 	 $3			*/
+  0x653b,		/* move  $25, $3		*/
+  0x0000, 0x0000	/* .word (.igot address)	*/
+};
+
+/* The format of 32 bit IPLT entries.  */
+static const bfd_vma mips32_exec_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
+  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
+  0x03200008,   /* jr  $25				*/
+  0x00000000    /* nop					*/
+};
+
+/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
+static const bfd_vma mips32r6_exec_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
+  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
+  0x03200009,   /* jr  $25				*/
+  0x00000000    /* nop					*/
+};
+
+/* The format of 32-bit micromips IPLT entries.  */
+static const bfd_vma micromips32_exec_iplt_entry[] =
+{
+  0x41a30000, 	/* lui $2, %hi(.igot address)		*/
+  0xff230000,	/* ld  $2, %lo(.igot address)($2) 	*/
+  0x4599,	/* jr  $25				*/
+  0x0c00,	/* nop					*/
+};
+
+/* The format of 64-bit IPLT entries.  */
+static const bfd_vma mips64_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %highest(.got.iplt entry)        */
+  0x3c0e0000,	/* lui $14, %hi(.got.iplt entry)             */
+  0x25ef0000,	/* addiu $15, $15, %higher(.got.iplt entry)  */
+  0x000f783c,	/* dsll32 $15, $15, 0x0                      */
+  0x01ee782d,	/* daddu $15, $15, $14                       */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
+static const bfd_vma mips64_48b_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)         */
+  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)    */
+  0x000f7c38,	/* dsll $15, $15, 16  			     */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
 ?
 /* microMIPS 32-bit opcode helper installer.  */
 
@@ -1289,6 +1380,10 @@ mips_elf_link_hash_newfunc (struct bfd_hash_entry *entry,
       ret->has_nonpic_branches = FALSE;
       ret->needs_lazy_stub = FALSE;
       ret->use_plt_entry = FALSE;
+      ret->needs_iplt = FALSE;
+      ret->needs_ireloc = FALSE;
+      ret->iplt_offset = -1;
+      ret->igot_offset = 0;
     }
 
   return (struct bfd_hash_entry *) ret;
@@ -1588,8 +1683,9 @@ mips_elf_create_stub_symbol (struct bfd_link_info *info,
   struct elf_link_hash_entry *elfh;
   const char *name;
 
-  if (ELF_ST_IS_MICROMIPS (h->root.other))
-    value |= 1;
+  if (ELF_ST_IS_MICROMIPS (h->root.other)
+      || (ELF_ST_IS_MIPS16 (h->root.other) && h->root.type == STT_GNU_IFUNC))
+      value |= 1;
 
   /* Create a new symbol.  */
   name = ACONCAT ((prefix, h->root.root.root.string, NULL));
@@ -1604,6 +1700,10 @@ mips_elf_create_stub_symbol (struct bfd_link_info *info,
   elfh->type = ELF_ST_INFO (STB_LOCAL, STT_FUNC);
   elfh->size = size;
   elfh->forced_local = 1;
+
+  if (ELF_ST_IS_MIPS16 (h->root.other) && h->needs_iplt)
+    elfh->other = STO_MIPS16;
+
   return TRUE;
 }
 
@@ -1966,6 +2066,137 @@ mips_elf_add_la25_stub (struct bfd_link_info *info,
 	  : mips_elf_add_la25_intro (stub, info));
 }
 
+/* Return the dynamic relocation section.  If it doesn't exist, try to
+   create a new it if CREATE_P, otherwise return NULL.  Also return NULL
+   if creation fails.  */
+
+static asection *
+mips_elf_rel_dyn_section (struct bfd_link_info *info, bfd_boolean create_p)
+{
+  const char *dname;
+  asection *sreloc;
+  bfd *dynobj;
+
+  dname = MIPS_ELF_REL_DYN_NAME (info);
+  dynobj = elf_hash_table (info)->dynobj;
+  sreloc = bfd_get_linker_section (dynobj, dname);
+  if (sreloc == NULL && create_p)
+    {
+      sreloc = bfd_make_section_anyway_with_flags (dynobj, dname,
+						   (SEC_ALLOC
+						    | SEC_LOAD
+						    | SEC_HAS_CONTENTS
+						    | SEC_IN_MEMORY
+						    | SEC_LINKER_CREATED
+						    | SEC_READONLY));
+      if (sreloc == NULL
+	  || !bfd_set_section_alignment (dynobj, sreloc,
+					 MIPS_ELF_LOG_FILE_ALIGN (dynobj)))
+	return NULL;
+    }
+  return sreloc;
+}
+
+/* Return section for R_MIPS_IRELATIVE relocations.  If the link is
+   dynamic, the relocations should go in .dynrel, otherwise they should
+   go in the special .rel.iplt section.  */
+
+static asection *
+mips_get_irel_section (struct bfd_link_info *info,
+		       struct mips_elf_link_hash_table *htab)
+{
+  asection *srel = (elf_hash_table (info)->dynamic_sections_created)
+		    ? mips_elf_rel_dyn_section (info, FALSE)
+		    : htab->root.irelplt;
+  BFD_ASSERT (srel != NULL);
+  return srel;
+}
+
+/* Reserve space in the rel.iplt section for IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_ireloc (struct bfd_link_info *info,
+			  struct mips_elf_link_hash_table *mhtab,
+			  struct mips_elf_link_hash_entry *mh)
+{
+  asection *srel;
+  bfd *dynobj;
+
+  if (mh->needs_iplt || mh->global_got_area == GGA_NORMAL
+      || mh->root.forced_local)
+    {
+      srel = mips_get_irel_section (info, mhtab);
+      dynobj = elf_hash_table (info)->dynobj;
+      if (srel != mhtab->root.irelplt && srel->size == 0)
+	{
+	  /* Make room for a null element.  */
+	  srel->size += MIPS_ELF_REL_SIZE (dynobj);
+	  ++srel->reloc_count;
+	}
+      srel->size += MIPS_ELF_REL_SIZE (dynobj);
+      mh->needs_ireloc = TRUE;
+    }
+
+  return TRUE;
+}
+
+/* Reserve space in the iplt and igot tables for an ifunc entry
+and allocate space for an IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_iplt (struct bfd_link_info *info,
+			struct mips_elf_link_hash_table *mhtab,
+			struct mips_elf_link_hash_entry *mh)
+{
+  asection *s;
+  bfd *abfd = info->output_bfd;
+
+  BFD_ASSERT (!mh->needs_iplt);
+  BFD_ASSERT (mhtab->root.iplt != NULL);
+
+  s = mhtab->root.iplt;
+
+  mh->iplt_offset = s->size;
+  mips_elf_create_stub_symbol (info, mh, ".iplt.", mhtab->root.iplt,
+			       s->size, mhtab->iplt_entry_size);
+  s->size += mhtab->iplt_entry_size;
+
+  BFD_ASSERT (mhtab->root.igotplt != NULL);
+  mh->igot_offset = mhtab->root.igotplt->size;
+  mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
+  mh->needs_iplt = TRUE;
+
+  return TRUE;
+}
+
+/* hash_traverse callback that is called before sizing sections.
+   DATA points to a mips_htab_traverse_info structure.  */
+
+static bfd_boolean
+mips_elf_check_local_symbols (void **slot, void *data)
+{
+  struct mips_htab_traverse_info *hti =
+    (struct mips_htab_traverse_info *) data;
+  struct mips_elf_link_hash_entry *h =
+    (struct mips_elf_link_hash_entry *) *slot;
+
+  /* If the referenced symbol is ifunc, allocate an iplt for it.  */
+  if (h && !h->needs_iplt && h->root.type == STT_GNU_IFUNC)
+    {
+      struct bfd_link_info *info = hti->info;
+      /* .iplt entry is needed only for executable objects.  */
+      if (!info->shared &&
+	  !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	return FALSE;
+
+      /* IRELATIVE fixup will be needed for each local IFUNC.  */
+      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
+	return FALSE;
+    }
+
+  return TRUE;
+}
+
 /* A mips_elf_link_hash_traverse callback that is called before sizing
    sections.  DATA points to a mips_htab_traverse_info structure.  */
 
@@ -1978,6 +2209,19 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
   if (!hti->info->relocatable)
     mips_elf_check_mips16_stubs (hti->info, h);
 
+  if (h && !h->needs_iplt &&
+      h->root.type == STT_GNU_IFUNC)
+    {
+      struct bfd_link_info *info = hti->info;
+      /* .iplt entry is needed only for executable objects.  */
+      if (!info->shared
+	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	return FALSE;
+      /* Allocate an extra relocation for this IFUNC, if needed.  */
+      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
+	return FALSE;
+    }
+
   if (mips_elf_local_pic_function_p (h))
     {
       /* PR 12845: If H is in a section that has been garbage
@@ -3145,37 +3389,6 @@ mips_elf_replace_bfd_got (bfd *abfd, struct mips_got_info *g)
   tdata->got = g;
 }
 
-/* Return the dynamic relocation section.  If it doesn't exist, try to
-   create a new it if CREATE_P, otherwise return NULL.  Also return NULL
-   if creation fails.  */
-
-static asection *
-mips_elf_rel_dyn_section (struct bfd_link_info *info, bfd_boolean create_p)
-{
-  const char *dname;
-  asection *sreloc;
-  bfd *dynobj;
-
-  dname = MIPS_ELF_REL_DYN_NAME (info);
-  dynobj = elf_hash_table (info)->dynobj;
-  sreloc = bfd_get_linker_section (dynobj, dname);
-  if (sreloc == NULL && create_p)
-    {
-      sreloc = bfd_make_section_anyway_with_flags (dynobj, dname,
-						   (SEC_ALLOC
-						    | SEC_LOAD
-						    | SEC_HAS_CONTENTS
-						    | SEC_IN_MEMORY
-						    | SEC_LINKER_CREATED
-						    | SEC_READONLY));
-      if (sreloc == NULL
-	  || ! bfd_set_section_alignment (dynobj, sreloc,
-					  MIPS_ELF_LOG_FILE_ALIGN (dynobj)))
-	return NULL;
-    }
-  return sreloc;
-}
-
 /* Return the GOT_TLS_* type required by relocation type R_TYPE.  */
 
 static int
@@ -3270,7 +3483,14 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
 					? &entry->d.h->root : NULL);
     }
   else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
-    g->local_gotno += 1;
+    {
+      /* Count IFUNCs as general GOT entries since they will need
+	 explicit IRELATIVE relocations.  */
+      if (entry->symndx < 0 && entry->d.h->root.type == STT_GNU_IFUNC)
+	g->general_gotno += 1;
+      else
+	g->local_gotno += 1;
+    }
   else
     g->global_gotno += 1;
 }
@@ -3600,7 +3820,8 @@ mips_elf_got_page (bfd *abfd, bfd *ibfd, struct bfd_link_info *info,
 
 static bfd_vma
 mips_elf_got16_entry (bfd *abfd, bfd *ibfd, struct bfd_link_info *info,
-		      bfd_vma value, bfd_boolean external)
+		      bfd_vma value, bfd_boolean external,
+		      struct mips_elf_link_hash_entry *h)
 {
   struct mips_got_entry *entry;
 
@@ -3615,7 +3836,7 @@ mips_elf_got16_entry (bfd *abfd, bfd *ibfd, struct bfd_link_info *info,
      R_MIPS16_GOT16, R_MIPS_CALL16, etc.  The format of the entry is the
      same in all cases.  */
   entry = mips_elf_create_local_got_entry (abfd, info, ibfd, value, 0,
-					   NULL, R_MIPS_GOT16);
+					   h, R_MIPS_GOT16);
   if (entry)
     return entry->gotidx;
   else
@@ -3715,7 +3936,8 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
   if (entry)
     return entry;
 
-  if (g->assigned_low_gotno > g->assigned_high_gotno)
+  if (g->assigned_low_gotno > g->assigned_high_gotno ||
+      g->assigned_general_gotno > g->assigned_low_gotno)
     {
       /* We didn't allocate enough space in the GOT.  */
       (*_bfd_error_handler)
@@ -3728,7 +3950,11 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
   if (!entry)
     return NULL;
 
-  if (got16_reloc_p (r_type)
+  if (h && h->root.type == STT_GNU_IFUNC)
+    /* Allocate IFUNC slots in the general GOT region since they
+       will need explicit IRELATIVE relocations.  */
+    lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
+  else if (got16_reloc_p (r_type)
       || call16_reloc_p (r_type)
       || got_page_reloc_p (r_type)
       || got_disp_reloc_p (r_type))
@@ -4460,6 +4686,29 @@ mips_elf_count_got_symbols (struct mips_elf_link_hash_entry *h, void *data)
     }
   return 1;
 }
+
+/* A elf_link_hash_traverse callback for which INF points to the
+   link_info structure. Count the number of GOT entries that need
+   explicit relocations by iterating over the local hash table.  */
+
+static int
+mips_elf_count_general_got_symbols (void **slot, void *inf)
+{
+  struct mips_elf_link_hash_entry *h =
+    (struct mips_elf_link_hash_entry *) *slot;
+  struct bfd_link_info *info;
+  struct mips_elf_link_hash_table *htab;
+  struct mips_got_info *g;
+
+  info = (struct bfd_link_info *) inf;
+  htab = mips_elf_hash_table (info);
+
+  g = htab->got_info;
+  if (h != NULL && h->root.type == STT_GNU_IFUNC &&
+      (h->root.forced_local || h->global_got_area == GGA_NONE))
+    g->general_gotno++;
+  return 1;
+}
 ?
 /* A htab_traverse callback for GOT entries.  Add each one to the GOT
    given in mips_elf_traverse_got_arg DATA.  Clear DATA->G on error.  */
@@ -4698,6 +4947,31 @@ mips_elf_set_global_got_area (void **entryp, void *data)
   return 1;
 }
 
+/* A htab_traverse callback for GOT entries, where DATA points to a
+   mips_elf_traverse_got_arg. Deallocate dedicated REL32 relocations
+   for global IFUNCs that are marked RELOC_ONLY.  */
+
+static int
+mips_elf_deallocate_irelocs (void **entryp, void *data)
+{
+  struct mips_got_entry *entry;
+
+  entry = (struct mips_got_entry *) *entryp;
+  if (entry->abfd != NULL
+      && entry->symndx == -1
+      && entry->d.h->global_got_area == GGA_RELOC_ONLY
+      && entry->d.h->root.type == STT_GNU_IFUNC
+      && entry->d.h->needs_ireloc)
+    {
+      struct mips_elf_traverse_got_arg *arg
+	= (struct mips_elf_traverse_got_arg *) data;
+      asection *srel = mips_elf_rel_dyn_section (arg->info, FALSE);
+      srel->size -= MIPS_ELF_REL_SIZE (elf_hash_table (arg->info)->dynobj);
+      entry->d.h->needs_ireloc = FALSE;
+    }
+  return 1;
+}
+
 /* A htab_traverse callback for secondary GOT entries, where DATA points
    to a mips_elf_traverse_got_arg.  Assign GOT indices to global entries
    and record the number of relocations they require.  DATA->value is
@@ -4852,6 +5126,11 @@ mips_elf_multi_got (bfd *abfd, struct bfd_link_info *info,
   tga.value = GGA_NORMAL;
   htab_traverse (g->got_entries, mips_elf_set_global_got_area, &tga);
 
+  /* Deallocate extra relocation intended for IFUNCs that have been marked
+     as RELOC_ONLY. These entries will get explicit relocs due to multi-got
+     which will work just as well for IFUNC resolution.  */
+  htab_traverse (gg->got_entries, mips_elf_deallocate_irelocs, &tga);
+
   /* Now go through the GOTs assigning them offset ranges.
      [assigned_low_gotno, local_gotno[ will be set to the range of local
      entries in each GOT.  We can then compute the end of a GOT by
@@ -5087,6 +5366,64 @@ mips_elf_create_compact_rel_section
   return TRUE;
 }
 
+/* Create the .iplt, .rel(a).iplt and .igot sections.  */
+
+static bfd_boolean
+mips_elf_create_ifunc_sections (struct bfd_link_info *info)
+{
+  struct mips_elf_link_hash_table * volatile htab;
+  const struct elf_backend_data *bed;
+  bfd *dynobj;
+  asection *s;
+  flagword flags;
+
+  htab = mips_elf_hash_table (info);
+  dynobj = htab->root.dynobj;
+  bed = get_elf_backend_data (dynobj);
+  flags = bed->dynamic_sec_flags;
+
+  s = bfd_make_section_anyway_with_flags (dynobj, ".iplt",
+					  flags | SEC_READONLY | SEC_CODE);
+  if (s == NULL || !bfd_set_section_alignment (dynobj, s, bed->plt_alignment))
+    return FALSE;
+
+  htab->root.iplt = s;
+
+  if (!info->shared)
+    {
+      if (ABI_64_P (dynobj))
+	htab->iplt_entry_size = 4 * ARRAY_SIZE (mips64_exec_iplt_entry);
+      else if (MIPS16_P (dynobj))
+	    htab->iplt_entry_size = 2 * ARRAY_SIZE (mips16_exec_iplt_entry);
+      else if (MICROMIPS_P (dynobj))
+	  /* Multiply by compression ratio for micromips.  */
+	    htab->iplt_entry_size = 4
+	      * (ARRAY_SIZE (micromips32_exec_iplt_entry) * 3 / 4);
+      else
+	htab->iplt_entry_size = 4 * (ARRAY_SIZE (mips32_exec_iplt_entry)
+				     + (LOAD_INTERLOCKS_P (dynobj)? 0 : 1));
+    }
+
+  BFD_ASSERT (htab->root.igotplt == NULL);
+
+  s = bfd_make_section_anyway_with_flags (dynobj, ".igot", flags);
+  if (s == NULL
+      || !bfd_set_section_alignment (dynobj, s, bed->s->log_file_align))
+    return FALSE;
+  htab->root.igotplt = s;
+  mips_elf_section_data (s)->elf.this_hdr.sh_flags |= SHF_ALLOC | SHF_WRITE;
+
+  BFD_ASSERT (htab->root.irelplt == NULL);
+
+  s = bfd_make_section_with_flags (dynobj, ".rel.iplt", flags | SEC_READONLY);
+  if (s == NULL
+      || !bfd_set_section_alignment (dynobj, s, bed->s->log_file_align))
+    return FALSE;
+
+  htab->root.irelplt = s;
+  return TRUE;
+}
+
 /* Create the .got section to hold the global offset table.  */
 
 static bfd_boolean
@@ -5203,6 +5540,74 @@ mips_elf_relocation_needs_la25_stub (bfd *input_bfd, int r_type,
       return FALSE;
     }
 }
+
+/* Find and/or create a hash entry for local symbol.  */
+
+static struct mips_elf_link_hash_entry *
+get_local_sym_hash (struct mips_elf_link_hash_table *htab,
+		    bfd *abfd, const Elf_Internal_Rela *rel)
+{
+  struct mips_elf_link_hash_entry e, *ret;
+  asection *sec;
+  hashval_t h;
+  void **slot;
+  Elf_Internal_Sym *isym;
+  Elf_Internal_Shdr *symtab_hdr;
+  char *namep;
+
+  sec = bfd_get_section_by_name (abfd, ".text");
+  h = ELF_LOCAL_SYMBOL_HASH (sec->id, ELF_R_SYM (abfd, rel->r_info));
+  isym = bfd_sym_from_r_symndx (&htab->sym_cache, abfd,
+				ELF_R_SYM (abfd, rel->r_info));
+  symtab_hdr =  &elf_tdata (abfd)->symtab_hdr;
+  namep = bfd_elf_string_from_elf_section (abfd, symtab_hdr->sh_link,
+					   isym->st_name);
+
+  if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+    elf_tdata (abfd)->has_gnu_symbols = TRUE;
+
+  e.root.indx = sec->id;
+  e.root.dynstr_index = ELF_R_SYM (abfd, rel->r_info);
+  e.root.root.root.string = namep;
+
+  slot = htab_find_slot_with_hash (htab->loc_hash_table, &e, h, INSERT);
+  if (!slot)
+    return NULL;
+
+  /* Found match */
+  if (*slot)
+    {
+      ret = (struct mips_elf_link_hash_entry *) *slot;
+      return ret;
+    }
+
+  /* Allocate new slot */
+  ret = (struct mips_elf_link_hash_entry *)
+    objalloc_alloc ((struct objalloc *) htab->loc_hash_memory,
+		    sizeof (struct mips_elf_link_hash_entry));
+
+  if (ret)
+    {
+      memset (ret, 0, sizeof (*ret));
+      ret->root.indx = sec->id;
+      ret->root.dynstr_index = ELF_R_SYM (abfd, rel->r_info);
+      ret->root.dynindx = -1;
+      ret->root.root.root.string = namep;
+      ret->root.root.u.def.section = sec;
+      ret->root.root.u.def.value = isym->st_value;
+      ret->root.got.offset = (bfd_vma) -1;
+      ret->global_got_area = GGA_NONE;
+      ret->root.type = STT_GNU_IFUNC;
+      ret->root.def_regular = 1;
+      ret->root.ref_regular = 1;
+      ret->root.forced_local = 1;
+      ret->root.root.type = bfd_link_hash_defined;
+
+      *slot = ret;
+    }
+
+  return ret;
+}
 ?
 /* Calculate the value produced by the RELOCATION (which comes from
    the INPUT_BFD).  The ADDEND is the addend to use for this
@@ -5253,6 +5658,8 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
   /* TRUE if the symbol referred to by this relocation is a local
      symbol.  */
   bfd_boolean local_p, was_local_p;
+  /* TRUE if the symbol referred to by this relocation is a local IFUNC */
+  bfd_boolean local_gnu_ifunc_p = FALSE;
   /* TRUE if the symbol referred to by this relocation is "_gp_disp".  */
   bfd_boolean gp_disp_p = FALSE;
   /* TRUE if the symbol referred to by this relocation is
@@ -5333,6 +5740,13 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
 
       target_is_16_bit_code_p = ELF_ST_IS_MIPS16 (sym->st_other);
       target_is_micromips_code_p = ELF_ST_IS_MICROMIPS (sym->st_other);
+
+      if (sym->st_info == STT_GNU_IFUNC)
+	{
+	  h = get_local_sym_hash (mips_elf_hash_table (info), input_bfd,
+				  relocation);
+	  local_gnu_ifunc_p = TRUE;
+	}
     }
   else
     {
@@ -5557,6 +5971,17 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
       target_is_16_bit_code_p = !micromips_p;
       target_is_micromips_code_p = micromips_p;
     }
+  /* If this symbol is an ifunc, point to the iplt stub for it.  */
+  else if (h && h->needs_iplt)
+    {
+      BFD_ASSERT (htab->root.iplt != NULL);
+      symbol = (htab->root.iplt->output_section->vma
+		+ htab->root.iplt->output_offset
+		+ h->iplt_offset);
+      /* Set ISA bit in address for compressed code.  */
+      if (ELF_ST_IS_COMPRESSED (h->root.other))
+	symbol |= 1;
+    }
 
   /* Make sure MIPS16 and microMIPS are not used together.  */
   if ((r_type == R_MIPS16_26 && target_is_micromips_code_p)
@@ -5941,10 +6366,13 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
     case R_MICROMIPS_CALL16:
       /* VxWorks does not have separate local and global semantics for
 	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
+      /* Local IFUNC symbols must be accessed through GOT, similar to global
+	 symbols, to allow for indirection */
       if (!htab->is_vxworks && local_p)
 	{
 	  value = mips_elf_got16_entry (abfd, input_bfd, info,
-					symbol + addend, !was_local_p);
+					symbol + addend,
+					!was_local_p || local_gnu_ifunc_p, h);
 	  if (value == MINUS_ONE)
 	    return bfd_reloc_outofrange;
 	  value
@@ -6448,31 +6876,41 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
   if (defined_p && r_type != R_MIPS_REL32)
     *addendp += symbol;
 
-  if (htab->is_vxworks)
-    /* VxWorks uses non-relative relocations for this.  */
-    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+  if (h && !indx && h->root.type == STT_GNU_IFUNC)
+    {
+      outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				     R_MIPS_IRELATIVE);
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     R_MIPS_NONE);
+    }
   else
-    /* The relocation is always an REL32 relocation because we don't
-       know where the shared library will wind up at load-time.  */
-    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
-				   R_MIPS_REL32);
-
-  /* For strict adherence to the ABI specification, we should
-     generate a R_MIPS_64 relocation record by itself before the
-     _REL32/_64 record as well, such that the addend is read in as
-     a 64-bit value (REL32 is a 32-bit relocation, after all).
-     However, since none of the existing ELF64 MIPS dynamic
-     loaders seems to care, we don't waste space with these
-     artificial relocations.  If this turns out to not be true,
-     mips_elf_allocate_dynamic_relocation() should be tweaked so
-     as to make room for a pair of dynamic relocations per
-     invocation if ABI_64_P, and here we should generate an
-     additional relocation record with R_MIPS_64 by itself for a
-     NULL symbol before this relocation record.  */
-  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
-				 ABI_64_P (output_bfd)
-				 ? R_MIPS_64
-				 : R_MIPS_NONE);
+    {
+      if (htab->is_vxworks)
+	/* VxWorks uses non-relative relocations for this.  */
+	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+      else
+	/* The relocation is always an REL32 relocation because we don't
+	   know where the shared library will wind up at load-time.  */
+	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				       R_MIPS_REL32);
+
+      /* For strict adherence to the ABI specification, we should
+	 generate a R_MIPS_64 relocation record by itself before the
+	 _REL32/_64 record as well, such that the addend is read in as
+	 a 64-bit value (REL32 is a 32-bit relocation, after all).
+	 However, since none of the existing ELF64 MIPS dynamic
+	 loaders seems to care, we don't waste space with these
+	 artificial relocations.  If this turns out to not be true,
+	 mips_elf_allocate_dynamic_relocation() should be tweaked so
+	 as to make room for a pair of dynamic relocations per
+	 invocation if ABI_64_P, and here we should generate an
+	 additional relocation record with R_MIPS_64 by itself for a
+	 NULL symbol before this relocation record.  */
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     ABI_64_P (output_bfd)
+				     ? R_MIPS_64
+				     : R_MIPS_NONE);
+    }
   outrel[2].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_NONE);
 
   /* Adjust the output offset of the relocation to reference the
@@ -7019,6 +7457,8 @@ _bfd_mips_elf_section_processing (bfd *abfd, Elf_Internal_Shdr *hdr)
 		hdr->sh_size += hdr->sh_addralign - adjust;
 	    }
 	}
+      else if (strcmp (name, ".igot") == 0)
+	hdr->sh_entsize =  MIPS_ELF_GOT_SIZE (abfd);
     }
 
   return TRUE;
@@ -7381,6 +7821,9 @@ _bfd_mips_elf_add_symbol_hook (bfd *abfd, struct bfd_link_info *info,
 			       flagword *flagsp ATTRIBUTE_UNUSED,
 			       asection **secp, bfd_vma *valp)
 {
+  if (ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC)
+    elf_tdata (info->output_bfd)->has_gnu_symbols = TRUE;
+
   if (SGI_COMPAT (abfd)
       && (abfd->flags & DYNAMIC) != 0
       && strcmp (*namep, "_rld_new_interface") == 0)
@@ -7932,6 +8375,13 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
   bed = get_elf_backend_data (abfd);
   rel_end = relocs + sec->reloc_count * bed->s->int_rels_per_ext_rel;
 
+  /* This needs to happen early. If the sections aren't needed
+     they will not get generated.  */
+  if (htab->root.dynobj == NULL)
+    htab->root.dynobj = abfd;
+  if (!htab->root.iplt && !mips_elf_create_ifunc_sections (info))
+    return FALSE;
+
   /* Check for the mips16 stub sections.  */
 
   name = bfd_get_section_name (abfd, sec);
@@ -8193,12 +8643,30 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
       bfd_boolean can_make_dynamic_p;
       bfd_boolean call_reloc_p;
       bfd_boolean constrain_symbol_p;
+      bfd_boolean local_gnu_ifunc_p = FALSE;
 
       r_symndx = ELF_R_SYM (abfd, rel->r_info);
       r_type = ELF_R_TYPE (abfd, rel->r_info);
 
       if (r_symndx < extsymoff)
-	h = NULL;
+	{
+	  Elf_Internal_Sym *isym;
+	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, abfd, r_symndx);
+
+	  if (isym == NULL)
+	    return FALSE;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, abfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
+
+	  h = NULL;
+       }
       else if (r_symndx >= extsymoff + NUM_SHDR_ENTRIES (symtab_hdr))
 	{
 	  (*_bfd_error_handler)
@@ -8410,7 +8878,8 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	case R_MIPS_CALL16:
 	case R_MIPS16_CALL16:
 	case R_MICROMIPS_CALL16:
-	  if (h == NULL)
+	  /* Exclude local IFUNCs from check */
+	  if (h == NULL && !local_gnu_ifunc_p)
 	    {
 	      (*_bfd_error_handler)
 		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),
@@ -8435,9 +8904,11 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
 	      /* We need a stub, not a plt entry for the undefined
 		 function.  But we record it as if it needs plt.  See
-		 _bfd_elf_adjust_dynamic_symbol.  */
+		 _bfd_elf_adjust_dynamic_symbol.  If it is an ifunc
+		 symbol it will go into an iplt section and not plt.  */
 	      h->needs_plt = 1;
-	      h->type = STT_FUNC;
+	      if (h->type != STT_GNU_IFUNC)
+		h->type = STT_FUNC;
 	    }
 	  break;
 
@@ -8976,6 +9447,7 @@ _bfd_mips_elf_adjust_dynamic_symbol (struct bfd_link_info *info,
   /* Make sure we know what is going on here.  */
   BFD_ASSERT (dynobj != NULL
 	      && (h->needs_plt
+		  || h->type == STT_GNU_IFUNC
 		  || h->u.weakdef != NULL
 		  || (h->def_dynamic
 		      && h->ref_regular
@@ -9254,6 +9726,11 @@ _bfd_mips_elf_always_size_sections (bfd *output_bfd,
   hti.error = FALSE;
   mips_elf_link_hash_traverse (mips_elf_hash_table (info),
 			       mips_elf_check_symbols, &hti);
+
+  /* Allocate relocs for local IFUNC symbols.  */
+  htab_traverse (htab->loc_hash_table,
+		 mips_elf_check_local_symbols, &hti);
+
   if (hti.error)
     return FALSE;
 
@@ -9298,9 +9775,17 @@ mips_elf_lay_out_got (bfd *output_bfd, struct bfd_link_info *info)
      count the number of reloc-only GOT symbols.  */
   mips_elf_link_hash_traverse (htab, mips_elf_count_got_symbols, info);
 
+  /* Count local IFUNC symbols. These need general GOT entries that
+     are fixed-up by explicit IRELATIVE relocations.  */
+  htab_traverse (htab->loc_hash_table, mips_elf_count_general_got_symbols, info);
+
   if (!mips_elf_resolve_final_got_entries (info, g))
     return FALSE;
 
+  g->assigned_general_gotno = htab->reserved_gotno;
+  g->local_gotno += g->general_gotno;
+  g->assigned_low_gotno += g->general_gotno;
+
   /* Calculate the total loadable size of the output.  That
      will give us the maximum number of GOT_PAGE entries
      required.  */
@@ -9742,6 +10227,8 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
       else if (! CONST_STRNEQ (name, ".init")
 	       && s != htab->sgot
 	       && s != htab->sgotplt
+	       && s != htab->root.iplt
+	       && s != htab->root.igotplt
 	       && s != htab->sstubs
 	       && s != htab->sdynbss)
 	{
@@ -9887,6 +10374,13 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
 	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
 	    return FALSE;
 	}
+
+      if (htab->got_info->general_gotno > 0)
+	{
+	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))
+	    return FALSE;
+	}
+
       if (htab->is_vxworks
 	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
 	return FALSE;
@@ -10018,6 +10512,7 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       Elf_Internal_Shdr *symtab_hdr;
       struct elf_link_hash_entry *h;
       bfd_boolean rel_reloc;
+      bfd_boolean local_gnu_ifunc_p = FALSE;
 
       rel_reloc = (NEWABI_P (input_bfd)
 		   && mips_elf_rel_relocation_p (input_bfd, input_section,
@@ -10031,6 +10526,23 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	{
 	  sec = local_sections[r_symndx];
 	  h = NULL;
+
+	  Elf_Internal_Sym *isym;
+	  struct mips_elf_link_hash_table *htab;
+	  htab = mips_elf_hash_table (info);
+	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, input_bfd, r_symndx);
+
+	  if (isym == NULL)
+	    return FALSE;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, input_bfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
 	}
       else
 	{
@@ -10091,8 +10603,9 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 		      && mips_elf_local_relocation_p (input_bfd, rel,
 						      local_sections)))
 		{
-		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
-						     contents, &addend))
+		  if (!local_gnu_ifunc_p
+		      && !mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
+							contents, &addend))
 		    {
 		      if (h)
 			name = h->root.root.string;
@@ -10479,6 +10992,237 @@ mips_elf_irix6_finish_dynamic_symbol (bfd *abfd ATTRIBUTE_UNUSED,
 	}
 }
 
+/* Create the contents of the iplt entry for an IFUNC symbol.  */
+
+static bfd_boolean
+mips_elf_create_iplt (bfd *output_bfd,
+		      struct mips_elf_link_hash_table *htab,
+		      struct mips_elf_link_hash_entry *hmips,
+		      bfd_vma igotplt_address)
+{
+  bfd_byte *loc;
+  const bfd_vma *iplt_entry;
+  bfd_vma high = mips_elf_high (igotplt_address);
+  bfd_vma low = igotplt_address & 0xffff;
+
+  /* Find out where the .iplt entry should go.  */
+  if (!htab->root.iplt->contents)
+    {
+      htab->root.iplt->contents = bfd_zalloc (output_bfd,
+					      htab->root.iplt->size);
+      if (!htab->root.iplt->contents)
+	return FALSE;
+    }
+  loc = htab->root.iplt->contents + hmips->iplt_offset;
+
+  /* Fill in the IPLT entry itself.  */
+  if (ABI_64_P (output_bfd))
+    {
+      bfd_vma highest = mips_elf_highest (igotplt_address);
+      bfd_vma higher = mips_elf_higher (igotplt_address);
+      iplt_entry = mips64_exec_iplt_entry;
+
+      if (highest)
+	{
+	  /* Full 64-bit address space */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3], loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 20);
+	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 24);
+	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 28);
+	}
+      else if (higher)
+	{
+	  /* 48-bit address space */
+	  iplt_entry = mips64_48b_exec_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
+	}
+      else
+	{
+	  /* 32-bit address space */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 12);
+	}
+    }
+  else if (ELF_ST_IS_MIPS16 (hmips->root.other))
+    {
+      iplt_entry = mips16_exec_iplt_entry;
+      bfd_put_16 (output_bfd, iplt_entry[0], loc);
+      bfd_put_16 (output_bfd, iplt_entry[1], loc + 2);
+      bfd_put_16 (output_bfd, iplt_entry[2], loc + 4);
+      bfd_put_16 (output_bfd, iplt_entry[3], loc + 6);
+      bfd_put_32 (output_bfd, igotplt_address, loc + 8);
+    }
+  else if (ELF_ST_IS_MICROMIPS (hmips->root.other))
+    {
+      iplt_entry = micromips32_exec_iplt_entry;
+      bfd_put_micromips_32 (output_bfd, iplt_entry[0] | high, loc);
+      bfd_put_micromips_32 (output_bfd, iplt_entry[1] | low , loc + 4);
+
+      bfd_put_16 (output_bfd, iplt_entry[2], loc + 8);
+      bfd_put_16 (output_bfd, iplt_entry[3], loc + 10);
+    }
+  else
+    {
+      if (MIPSR6_P(output_bfd))
+	iplt_entry = mips32r6_exec_iplt_entry;
+      else
+	iplt_entry = mips32_exec_iplt_entry;
+      bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+      bfd_put_32 (output_bfd, iplt_entry[1] | low, loc + 4);
+      if (LOAD_INTERLOCKS_P (output_bfd))
+	{
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3], loc + 12);
+	}
+      else
+	{
+	  bfd_put_32 (output_bfd, iplt_entry[3], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[3], loc + 16);
+	}
+    }
+
+  return TRUE;
+}
+
+/* Find local GOT index for VALUE. Return -1 if no GOT slot is found.  */
+
+static bfd_vma
+mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
+				 bfd_vma value)
+{
+  struct mips_got_entry lookup, *entry;
+  void **loc;
+  struct mips_got_info *g;
+  struct mips_elf_link_hash_table *htab;
+
+  htab = mips_elf_hash_table (info);
+  BFD_ASSERT (htab != NULL);
+
+  g = mips_elf_bfd_got (abfd, FALSE);
+  BFD_ASSERT (g != NULL);
+
+  lookup.abfd = NULL;
+  lookup.symndx = -1;
+  lookup.d.address = value;
+  lookup.tls_type = GOT_TLS_NONE;
+  loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
+  if (!loc)
+    return -1;
+
+  entry = (struct mips_got_entry *) *loc;
+  if (entry)
+    return entry->gotidx;
+  else
+    return -1;
+}
+
+/* Create the IRELATIVE relocation for an IFUNC symbol.  */
+
+static bfd_boolean
+mips_elf_create_ireloc (bfd *output_bfd,
+		      bfd *dynobj,
+		      struct mips_elf_link_hash_table *htab,
+		      struct mips_elf_link_hash_entry *hmips,
+		      Elf_Internal_Sym *sym,
+		      struct bfd_link_info *info)
+{
+  bfd_vma igotplt_address = 0;
+  int igot_offset = -1;
+  asection *gotsect, *relsect;
+
+  if (!hmips->needs_iplt)
+    {
+      gotsect = htab->sgot;
+      if (hmips->global_got_area == GGA_NORMAL)
+	  igot_offset = mips_elf_primary_global_got_index (output_bfd,
+							   info, &hmips->root);
+      else if (hmips->global_got_area == GGA_NONE)
+	  igot_offset = mips_elf_check_local_got_index (output_bfd, info,
+							sym->st_value);
+    }
+  else
+    {
+      bfd_byte *loc;
+      bfd_vma igot_index;
+      gotsect = htab->root.igotplt;
+      igot_offset = hmips->igot_offset;
+
+      /* Calculate the address of the IGOT entry.  */
+      igot_index = igot_offset / MIPS_ELF_GOT_SIZE (dynobj);
+
+      if (!gotsect->contents)
+	{
+	  gotsect->contents = bfd_zalloc (output_bfd, gotsect->size);
+	  if (!gotsect->contents)
+	    return FALSE;
+	}
+
+      /* Initially point the .igot entry at the IFUNC resolver routine.  */
+      loc = (bfd_byte *) gotsect->contents
+	+ igot_index * MIPS_ELF_GOT_SIZE (dynobj);
+
+      if (ABI_64_P (output_bfd))
+	bfd_put_64 (output_bfd, sym->st_value, loc);
+      else
+	bfd_put_32 (output_bfd, sym->st_value, loc);
+    }
+
+  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
+		     + igot_offset);
+
+  relsect = mips_get_irel_section (info, htab);
+
+  if (igot_offset >= 0)
+    {
+      if (hmips->needs_iplt && relsect->contents == NULL)
+	{
+	  /* Allocate memory for the relocation section contents.  */
+	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
+	  if (relsect->contents == NULL)
+	    return FALSE;
+	}
+
+      if (hmips->needs_iplt || hmips->global_got_area == GGA_NONE)
+	/* Emit an R_MIPS_IRELATIVE relocation against the [I]GOT entry.  */
+	mips_elf_output_dynamic_relocation (output_bfd, relsect,
+					    relsect->reloc_count++, 0,
+					    R_MIPS_IRELATIVE, igotplt_address);
+      else if (hmips->global_got_area == GGA_NORMAL)
+	{
+	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
+	     a preemptible symbol.  */
+	  asection *sec = hmips->root.root.u.def.section;
+	  Elf_Internal_Rela rel[3];
+
+	  memset (rel, 0, sizeof (rel));
+	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
+	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
+
+	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
+					      sec, sym->st_value, NULL,
+					      gotsect);
+	}
+    }
+  /* If necessary, generate the corresponding .iplt entry.  */
+  if (hmips->needs_iplt
+      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
+    return FALSE;
+
+  return TRUE;
+}
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */
 
@@ -10807,6 +11551,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other = other;
     }
 
+  if (hmips->root.type == STT_GNU_IFUNC)
+    {
+      if (hmips->needs_ireloc
+	  && !mips_elf_create_ireloc (output_bfd, dynobj, htab,
+				      hmips, sym, info))
+	return FALSE;
+
+      if (!elf_hash_table (info)->dynamic_sections_created)
+	return TRUE;
+      if (h->dynindx == -1  && !h->forced_local)
+	return TRUE;
+    }
+
   /* If we have a MIPS16 function with a stub, the dynamic symbol must
      refer to the stub, since only the stub uses the standard calling
      conventions.  */
@@ -10970,9 +11727,37 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other -= STO_MICROMIPS;
     }
 
+  if (hmips->needs_iplt)
+    {
+      /* Point at the iplt stub for this ifunc symbol.  */
+      sym->st_value = htab->root.iplt->output_section->vma
+	+ htab->root.iplt->output_offset + hmips->iplt_offset;
+      sym->st_info = ELF_ST_INFO (STB_GLOBAL, STT_FUNC);
+      if (ELF_ST_IS_COMPRESSED (hmips->root.other))
+	sym->st_value |= 1;
+    }
+
   return TRUE;
 }
 
+/* Finish up local dynamic symbol handling.  We set the contents of
+   various dynamic sections here.  */
+
+static bfd_boolean
+_bfd_mips_elf_finish_local_dynamic_symbol (void **slot, void *inf)
+{
+  struct mips_elf_link_hash_entry *h = (struct mips_elf_link_hash_entry *) *slot;
+  struct bfd_link_info *info  = (struct bfd_link_info *)inf;
+  Elf_Internal_Sym isym;
+
+  isym.st_value = h->root.root.u.def.section->output_section->vma
+    + h->root.root.u.def.section->output_offset + h->root.root.u.def.value;
+  isym.st_other = h->root.other;
+
+  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
+					      &h->root, &isym);
+}
+
 /* Likewise, for VxWorks.  */
 
 bfd_boolean
@@ -11358,6 +12143,13 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
   sgot = htab->sgot;
   gg = htab->got_info;
 
+  if (htab_elements (htab->loc_hash_table) > 0)
+  {
+    /* Fill PLT and GOT entries for local STT_GNU_IFUNC symbols.  */
+    htab_traverse (htab->loc_hash_table,
+		   _bfd_mips_elf_finish_local_dynamic_symbol, info);
+  }
+
   if (elf_hash_table (info)->dynamic_sections_created)
     {
       bfd_byte *b;
@@ -11528,6 +12320,10 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
 	      dyn.d_un.d_ptr = s->vma;
 	      break;
 
+	    case DT_MIPS_GENERAL_GOTNO:
+	      dyn.d_un.d_val = htab->reserved_gotno + gg->general_gotno;
+	      break;
+
 	    case DT_RELASZ:
 	      BFD_ASSERT (htab->is_vxworks);
 	      /* The count does not include the JUMP_SLOT relocations.  */
@@ -13862,6 +14658,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
   return FALSE;
 }
 ?
+/* Compute a hash of a local hash entry.  We use elf_link_hash_entry
+  for local symbol so that we can handle local STT_GNU_IFUNC symbols
+  as global symbol.  We reuse indx and dynstr_index for local symbol
+  hash since they aren't used by global symbols in this backend.  */
+
+static hashval_t
+local_htab_hash (const void *ptr)
+{
+  struct  mips_elf_link_hash_entry *h =
+    (struct  mips_elf_link_hash_entry *) ptr;
+  return ELF_LOCAL_SYMBOL_HASH (h->root.indx, h->root.dynstr_index);
+}
+
+/* Compare local hash entries.  */
+
+static int
+local_htab_eq (const void *ptr1, const void *ptr2)
+{
+  struct mips_elf_link_hash_entry *h1 =
+    (struct mips_elf_link_hash_entry *) ptr1;
+  struct  mips_elf_link_hash_entry *h2 =
+    (struct  mips_elf_link_hash_entry *) ptr2;
+
+  return h1->root.indx == h2->root.indx &&
+    h1->root.dynstr_index == h2->root.dynstr_index;
+}
+
+/* Destroy a MIPS ELF linker hash table.  */
+
+static void
+_bfd_mips_elf_link_hash_table_free (bfd *obfd)
+{
+  struct mips_elf_link_hash_table *htab;
+
+  htab = (struct mips_elf_link_hash_table *) obfd->link.hash;
+  if (htab->loc_hash_table)
+    htab_delete (htab->loc_hash_table);
+  if (htab->loc_hash_memory)
+    objalloc_free ((struct objalloc *) htab->loc_hash_memory);
+  _bfd_elf_link_hash_table_free (obfd);
+}
+
 /* Create a MIPS ELF linker hash table.  */
 
 struct bfd_link_hash_table *
@@ -13885,6 +14723,17 @@ _bfd_mips_elf_link_hash_table_create (bfd *abfd)
   ret->root.init_plt_refcount.plist = NULL;
   ret->root.init_plt_offset.plist = NULL;
 
+  /* Create hash table for local IFUNC symbols.  */
+  ret->loc_hash_table = htab_try_create (1024,
+					 local_htab_hash,
+					 local_htab_eq,
+					 NULL);
+  ret->loc_hash_memory = objalloc_create ();
+  if (!ret->loc_hash_table || !ret->loc_hash_memory)
+    {
+      _bfd_mips_elf_link_hash_table_free (abfd);
+      return NULL;
+    }
   return &ret->root.root;
 }
 
@@ -15509,7 +16358,9 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
       return "DT_MIPS_PLTGOT";
     case DT_MIPS_RWPLT:
       return "DT_MIPS_RWPLT";
-    }
+    case DT_MIPS_GENERAL_GOTNO:
+      return "DT_MIPS_GENERAL_GOTNO";
+   }
 }
 
 /* Return the meaning of Tag_GNU_MIPS_ABI_FP value FP, or null if
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index 18e3c40..2d0742d 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -1199,6 +1199,7 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_MIPS_COPY",
   "BFD_RELOC_MIPS_JUMP_SLOT",
 
+  "BFD_RELOC_MIPS_IRELATIVE",
   "BFD_RELOC_MOXIE_10_PCREL",
 
   "BFD_RELOC_FT32_10",
diff --git a/bfd/reloc.c b/bfd/reloc.c
index 3478006..18ed890 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -2450,6 +2450,11 @@ ENUMDOC
 COMMENT
 
 ENUM
+  BFD_RELOC_MIPS_IRELATIVE
+ENUMDOC
+  MIPS support for STT_GNU_IFUNC.
+
+ENUM
   BFD_RELOC_MOXIE_10_PCREL
 ENUMDOC
   Moxie ELF relocations.
diff --git a/binutils/readelf.c b/binutils/readelf.c
index c313db4..7f97758 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -1724,6 +1724,8 @@ get_mips_dynamic_type (unsigned long type)
     case DT_MIPS_AUX_DYNAMIC: return "MIPS_AUX_DYNAMIC";
     case DT_MIPS_PLTGOT: return "MIPS_PLTGOT";
     case DT_MIPS_RWPLT: return "MIPS_RWPLT";
+    case DT_MIPS_GENERAL_GOTNO: return "MIPS_GENERAL_GOTNO";
+
     default:
       return NULL;
     }
@@ -8514,6 +8516,7 @@ dynamic_section_mips_val (Elf_Internal_Dyn * entry)
     case DT_MIPS_DELTA_SYM_NO:
     case DT_MIPS_DELTA_CLASSSYM_NO:
     case DT_MIPS_COMPACT_SIZE:
+    case DT_MIPS_GENERAL_GOTNO:
       print_vma (entry->d_un.d_ptr, DEC);
       break;
 
diff --git a/elfcpp/elfcpp.h b/elfcpp/elfcpp.h
index 722984e..f919fb8 100644
--- a/elfcpp/elfcpp.h
+++ b/elfcpp/elfcpp.h
@@ -866,6 +866,8 @@ enum DT
   DT_MIPS_PLTGOT = 0x70000032,
   // Points to the base of a writable PLT.
   DT_MIPS_RWPLT = 0x70000034,
+  // The GOT index of the first implicitly relocated GOT entry.
+  DT_MIPS_GENERAL_GOTNO = 0x70000036,
 
   DT_AUXILIARY = 0x7ffffffd,
   DT_USED = 0x7ffffffe,
diff --git a/include/elf/mips.h b/include/elf/mips.h
index 57de3bc..4005047 100644
--- a/include/elf/mips.h
+++ b/include/elf/mips.h
@@ -115,6 +115,9 @@ START_RELOC_NUMBERS (elf_mips_reloc_type)
   RELOC_NUMBER (R_MIPS_COPY, 126)
   RELOC_NUMBER (R_MIPS_JUMP_SLOT, 127)
 
+  /* STT_GNU_IFUNC support */
+  RELOC_NUMBER (R_MIPS_IRELATIVE, 128)
+
   /* These relocations are specific to microMIPS.  */
   FAKE_RELOC (R_MICROMIPS_min, 130)
   RELOC_NUMBER (R_MICROMIPS_26_S1, 133)
@@ -751,6 +754,10 @@ extern void bfd_mips_elf32_swap_reginfo_out
 
 /* Relative offset of run time loader map, used for debugging.  */
 #define DT_MIPS_RLD_MAP_REL    0x70000035
+
+/* The GOT index of the first implicitly relocated GOT entry.  */
+#define DT_MIPS_GENERAL_GOTNO  0x70000036
+
 ?
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */
 
-- 
1.7.9.5
-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: Spec.txt
URL: <https://sourceware.org/pipermail/binutils/attachments/20150721/008b1ec6/attachment.txt>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0002-RFC-Add-IFUNC-tests-for-MIPS.patch
Type: text/x-patch
Size: 45869 bytes
Desc: 0002-RFC-Add-IFUNC-tests-for-MIPS.patch
URL: <https://sourceware.org/pipermail/binutils/attachments/20150721/008b1ec6/attachment.bin>


More information about the Binutils mailing list