gold: Add sparc IFUNC support to Gold.
authorDavid S. Miller <davem@redhat.com>
Tue, 17 Apr 2012 01:57:23 +0000 (01:57 +0000)
committerDavid S. Miller <davem@redhat.com>
Tue, 17 Apr 2012 01:57:23 +0000 (01:57 +0000)
elfcpp/

* sparc.h (R_SPARC_JMP_IREL): New relocation.

gold/

* sparc.cc (class Target_sparc): Add rela_ifunc_.
(Target_sparc::Target_sparc): Initialize new field.
(Target_sparc::do_plt_section_for_global): New function.
(Target_sparc::do_plt_section_for_local): New function.
(Target_sparc::reloc_needs_plt_for_ifunc): New function.
(Target_sparc::make_plt_section): New function, broken out of
make_plt_entry.  Use ORDER_NON_RELRO_FIRST for ".plt".
(Target_sparc::make_plt_entry): Call make_plt_section.
(Target_sparc::make_local_ifunc_plt_entry): New function.
(Target_sparc::rela_ifunc_section): New function.
(Target_sparc::plt_section): Remove const.
(Output_data_plt_sparc): Update declarations.  Define Global_ifunc
and Local_ifunc types.  Add global_ifuncs_, local_ifuncs_, ifunc_rel_,
and ifunc_count_ fields.
(Output_data_plt_sparc::Output_data_plt_sparc): Initialize new fields.
(Output_data_plt_sparc::add_entry): Handle IFUNC symbols.
(Output_data_plt_sparc::add_local_ifunc_entry): New function.
(Output_data_plt_sparc::rela_ifunc): New function.
(Output_data_plt_sparc::emit_pending_ifunc_relocs): New function.
(Output_data_plt_sparc::has_ifunc_section): New function.
(Output_data_plt_sparc::entry_count): Include ifunc_count_.
(Output_data_plt_sparc::address_for_global): New function.
(Output_data_plt_sparc::address_for_local): New function.
(Output_data_plt_sparc::plt_index_to_offset): New function.
(Output_data_plt_sparc::set_final_data_size): Use plt_index_to_offset
and entry_count.
(Output_data_plt_sparc::do_write): Use first_plt_entry_offset and
entry_count.
(Target_sparc::Scan::get_reference_flags): Add R_SPARC_IRELATIVE and
R_SPARC_JMP_IREL to switch.
(Target_sparc::Scan::check_non_pic): Likewise.
(Target_sparc::Scan::local): Handle IFUNC symbols.
(Target_sparc::Scan::local): Likewise.
(Target_sparc::Relocate::relocate): Likewise, use plt_address_for_global
and plt_address_for_local.
(Target_sparc::do_finalize_sections): Call emit_pending_ifunc_relocs.
Define __rel_iplt_start and __rel_iplt_end if doing a static link.

elfcpp/ChangeLog
elfcpp/sparc.h
gold/ChangeLog
gold/sparc.cc

index 3a764014d1f02e8c5e99a300012a1c45c64dec13..25502dfb3b81be6bd302fd5eec7b8d0b17ccd98b 100644 (file)
@@ -1,3 +1,7 @@
+2012-04-16  David S. Miller  <davem@davemloft.net>
+
+       * sparc.h (R_SPARC_JMP_IREL): New relocation.
+
 2012-04-12  David S. Miller  <davem@davemloft.net>
 
        * sparc.h (R_SPARC_WDISP10): New relocation.
index 77c4668773b904547b8c01ebd1da5ea7ed8aaa7c..6b561be21c971716cafd53c4cf5b7dcb968a1bd5 100644 (file)
@@ -142,6 +142,7 @@ enum
   R_SPARC_SIZE64 = 87,        // size of symbol, 64-bit
   R_SPARC_WDISP10 = 88,       // PC relative 10 bit shifted
 
+  R_SPARC_JMP_IREL = 248,     // Create PLT slot to IFUNC function
   R_SPARC_IRELATIVE = 249,    // Adjust indirectly by program base
 
   // GNU vtable garbage collection extensions.
index b88577ab66f5a75b37e5de9585e67b1a1ab7593e..eb3ad3a2f17963d2c5a2de267f9a010922c8629a 100644 (file)
@@ -1,5 +1,43 @@
 2012-04-16  David S. Miller  <davem@davemloft.net>
 
+       * sparc.cc (class Target_sparc): Add rela_ifunc_.
+       (Target_sparc::Target_sparc): Initialize new field.
+       (Target_sparc::do_plt_section_for_global): New function.
+       (Target_sparc::do_plt_section_for_local): New function.
+       (Target_sparc::reloc_needs_plt_for_ifunc): New function.
+       (Target_sparc::make_plt_section): New function, broken out of
+       make_plt_entry.  Use ORDER_NON_RELRO_FIRST for ".plt".
+       (Target_sparc::make_plt_entry): Call make_plt_section.
+       (Target_sparc::make_local_ifunc_plt_entry): New function.
+       (Target_sparc::rela_ifunc_section): New function.
+       (Target_sparc::plt_section): Remove const.
+       (Output_data_plt_sparc): Update declarations.  Define Global_ifunc
+       and Local_ifunc types.  Add global_ifuncs_, local_ifuncs_, ifunc_rel_,
+       and ifunc_count_ fields.
+       (Output_data_plt_sparc::Output_data_plt_sparc): Initialize new fields.
+       (Output_data_plt_sparc::add_entry): Handle IFUNC symbols.
+       (Output_data_plt_sparc::add_local_ifunc_entry): New function.
+       (Output_data_plt_sparc::rela_ifunc): New function.
+       (Output_data_plt_sparc::emit_pending_ifunc_relocs): New function.
+       (Output_data_plt_sparc::has_ifunc_section): New function.
+       (Output_data_plt_sparc::entry_count): Include ifunc_count_.
+       (Output_data_plt_sparc::address_for_global): New function.
+       (Output_data_plt_sparc::address_for_local): New function.
+       (Output_data_plt_sparc::plt_index_to_offset): New function.
+       (Output_data_plt_sparc::set_final_data_size): Use plt_index_to_offset
+       and entry_count.
+       (Output_data_plt_sparc::do_write): Use first_plt_entry_offset and
+       entry_count.
+       (Target_sparc::Scan::get_reference_flags): Add R_SPARC_IRELATIVE and
+       R_SPARC_JMP_IREL to switch.
+       (Target_sparc::Scan::check_non_pic): Likewise.
+       (Target_sparc::Scan::local): Handle IFUNC symbols.
+       (Target_sparc::Scan::local): Likewise.
+       (Target_sparc::Relocate::relocate): Likewise, use plt_address_for_global
+       and plt_address_for_local.
+       (Target_sparc::do_finalize_sections): Call emit_pending_ifunc_relocs.
+       Define __rel_iplt_start and __rel_iplt_end if doing a static link.
+
        * output.h (Output_reloc): Allow use_plt_offset for global relocs too.
        (class Output_data_reloc): Adjust calls to Output_reloc_type.
        (Output_data_reloc::add_global_relative): (RELA only) Add use_plt_offset.
index e1bdc8e0f37aa57669f87f458b346cc66ccfcff3..d1e83eb1671fb04046021a472558f80d5f5cb8a3 100644 (file)
@@ -1,6 +1,6 @@
 // sparc.cc -- sparc target support for gold.
 
-// Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+// Copyright 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
 // Written by David S. Miller <davem@davemloft.net>.
 
 // This file is part of gold.
@@ -58,7 +58,7 @@ class Target_sparc : public Sized_target<size, big_endian>
 
   Target_sparc()
     : Sized_target<size, big_endian>(&sparc_info),
-      got_(NULL), plt_(NULL), rela_dyn_(NULL),
+      got_(NULL), plt_(NULL), rela_dyn_(NULL), rela_ifunc_(NULL),
       copy_relocs_(elfcpp::R_SPARC_COPY), dynbss_(NULL),
       got_mod_index_offset_(-1U), tls_get_addr_sym_(NULL)
   {
@@ -154,6 +154,15 @@ class Target_sparc : public Sized_target<size, big_endian>
     return strcmp(sym->name(), "___tls_get_addr") == 0;
   }
 
+  // Return the PLT address to use for a global symbol.
+  uint64_t
+  do_plt_address_for_global(const Symbol* gsym) const
+  { return this->plt_section()->address_for_global(gsym); }
+
+  uint64_t
+  do_plt_address_for_local(const Relobj* relobj, unsigned int symndx) const
+  { return this->plt_section()->address_for_local(relobj, symndx); }
+
   // Return whether there is a GOT section.
   bool
   has_got_section() const
@@ -256,6 +265,10 @@ class Target_sparc : public Sized_target<size, big_endian>
     void
     check_non_pic(Relobj*, unsigned int r_type);
 
+    bool
+    reloc_needs_plt_for_ifunc(Sized_relobj_file<size, big_endian>*,
+                             unsigned int r_type);
+
     // Whether we have issued an error about a non-PIC compilation.
     bool issued_non_pic_error_;
   };
@@ -320,10 +333,20 @@ class Target_sparc : public Sized_target<size, big_endian>
   Output_data_got<size, big_endian>*
   got_section(Symbol_table*, Layout*);
 
+  // Create the PLT section.
+  void
+  make_plt_section(Symbol_table* symtab, Layout* layout);
+
   // Create a PLT entry for a global symbol.
   void
   make_plt_entry(Symbol_table*, Layout*, Symbol*);
 
+  // Create a PLT entry for a local STT_GNU_IFUNC symbol.
+  void
+  make_local_ifunc_plt_entry(Symbol_table*, Layout*,
+                            Sized_relobj_file<size, big_endian>* relobj,
+                            unsigned int local_sym_index);
+
   // Create a GOT entry for the TLS module index.
   unsigned int
   got_mod_index_entry(Symbol_table* symtab, Layout* layout,
@@ -341,7 +364,7 @@ class Target_sparc : public Sized_target<size, big_endian>
   }
 
   // Get the PLT section.
-  const Output_data_plt_sparc<size, big_endian>*
+  Output_data_plt_sparc<size, big_endian>*
   plt_section() const
   {
     gold_assert(this->plt_ != NULL);
@@ -352,6 +375,10 @@ class Target_sparc : public Sized_target<size, big_endian>
   Reloc_section*
   rela_dyn_section(Layout*);
 
+  // Get the section to use for IFUNC relocations.
+  Reloc_section*
+  rela_ifunc_section(Layout*);
+
   // Copy a relocation against a global symbol.
   void
   copy_reloc(Symbol_table* symtab, Layout* layout,
@@ -386,6 +413,8 @@ class Target_sparc : public Sized_target<size, big_endian>
   Output_data_plt_sparc<size, big_endian>* plt_;
   // The dynamic reloc section.
   Reloc_section* rela_dyn_;
+  // The section to use for IFUNC relocs.
+  Reloc_section* rela_ifunc_;
   // Relocs saved to avoid a COPY reloc.
   Copy_relocs<elfcpp::SHT_RELA, size, big_endian> copy_relocs_;
   // Space for variables copied with a COPY reloc.
@@ -1145,6 +1174,30 @@ Target_sparc<size, big_endian>::rela_dyn_section(Layout* layout)
   return this->rela_dyn_;
 }
 
+// Get the section to use for IFUNC relocs, creating it if
+// necessary.  These go in .rela.dyn, but only after all other dynamic
+// relocations.  They need to follow the other dynamic relocations so
+// that they can refer to global variables initialized by those
+// relocs.
+
+template<int size, bool big_endian>
+typename Target_sparc<size, big_endian>::Reloc_section*
+Target_sparc<size, big_endian>::rela_ifunc_section(Layout* layout)
+{
+  if (this->rela_ifunc_ == NULL)
+    {
+      // Make sure we have already created the dynamic reloc section.
+      this->rela_dyn_section(layout);
+      this->rela_ifunc_ = new Reloc_section(false);
+      layout->add_output_section_data(".rela.dyn", elfcpp::SHT_RELA,
+                                     elfcpp::SHF_ALLOC, this->rela_ifunc_,
+                                     ORDER_DYNAMIC_RELOCS, false);
+      gold_assert(this->rela_dyn_->output_section()
+                 == this->rela_ifunc_->output_section());
+    }
+  return this->rela_ifunc_;
+}
+
 // A class to handle the PLT data.
 
 template<int size, bool big_endian>
@@ -1157,7 +1210,13 @@ class Output_data_plt_sparc : public Output_section_data
   Output_data_plt_sparc(Layout*);
 
   // Add an entry to the PLT.
-  void add_entry(Symbol* gsym);
+  void add_entry(Symbol_table* symtab, Layout* layout, Symbol* gsym);
+
+  // Add an entry to the PLT for a local STT_GNU_IFUNC symbol.
+  unsigned int
+  add_local_ifunc_entry(Symbol_table*, Layout*,
+                       Sized_relobj_file<size, big_endian>* relobj,
+                       unsigned int local_sym_index);
 
   // Return the .rela.plt section data.
   const Reloc_section* rel_plt() const
@@ -1165,10 +1224,22 @@ class Output_data_plt_sparc : public Output_section_data
     return this->rel_;
   }
 
+  // Return where the IFUNC relocations should go.
+  Reloc_section*
+  rela_ifunc(Symbol_table*, Layout*);
+
+  void
+  emit_pending_ifunc_relocs();
+
+  // Return whether we created a section for IFUNC relocations.
+  bool
+  has_ifunc_section() const
+  { return this->ifunc_rel_ != NULL; }
+
   // Return the number of PLT entries.
   unsigned int
   entry_count() const
-  { return this->count_; }
+  { return this->count_ + this->ifunc_count_; }
 
   // Return the offset of the first non-reserved PLT entry.
   static unsigned int
@@ -1180,6 +1251,14 @@ class Output_data_plt_sparc : public Output_section_data
   get_plt_entry_size()
   { return base_plt_entry_size; }
 
+  // Return the PLT address to use for a global symbol.
+  uint64_t
+  address_for_global(const Symbol*);
+
+  // Return the PLT address to use for a local symbol.
+  uint64_t
+  address_for_local(const Relobj*, unsigned int symndx);
+
  protected:
   void do_adjust_output_section(Output_section* os);
 
@@ -1199,34 +1278,69 @@ class Output_data_plt_sparc : public Output_section_data
     (plt_entries_per_block
      * (plt_insn_chunk_size + plt_pointer_chunk_size));
 
-  // Set the final size.
-  void
-  set_final_data_size()
+  section_offset_type
+  plt_index_to_offset(unsigned int index)
   {
-    unsigned int full_count = this->count_ + 4;
-    unsigned int extra = (size == 32 ? 4 : 0);
+    section_offset_type offset;
 
-    if (size == 32 || full_count < 32768)
-      this->set_data_size((full_count * base_plt_entry_size) + extra);
+    if (size == 32 || index < 32768)
+      offset = index * base_plt_entry_size;
     else
       {
-       unsigned int ext_cnt = full_count - 32768;
+       unsigned int ext_index = index - 32768;
 
-       this->set_data_size((32768 * base_plt_entry_size)
-                           + (ext_cnt
-                              * (plt_insn_chunk_size
-                                 + plt_pointer_chunk_size)));
+       offset = (32768 * base_plt_entry_size)
+         + ((ext_index / plt_entries_per_block)
+            * plt_block_size)
+         + ((ext_index % plt_entries_per_block)
+            * plt_insn_chunk_size);
       }
+    return offset;
+  }
+
+  // Set the final size.
+  void
+  set_final_data_size()
+  {
+    unsigned int full_count = this->entry_count() + 4;
+    unsigned int extra = (size == 32 ? 4 : 0);
+    section_offset_type sz = plt_index_to_offset(full_count) + extra;
+
+    return this->set_data_size(sz);
   }
 
   // Write out the PLT data.
   void
   do_write(Output_file*);
 
+  struct Global_ifunc
+  {
+    Reloc_section* rel;
+    Symbol* gsym;
+    unsigned int plt_index;
+  };
+
+  struct Local_ifunc
+  {
+    Reloc_section* rel;
+    Sized_relobj_file<size, big_endian>* object;
+    unsigned int local_sym_index;
+    unsigned int plt_index;
+  };
+
   // The reloc section.
   Reloc_section* rel_;
+  // The IFUNC relocations, if necessary.  These must follow the
+  // regular relocations.
+  Reloc_section* ifunc_rel_;
   // The number of PLT entries.
   unsigned int count_;
+  // The number of PLT entries for IFUNC symbols.
+  unsigned int ifunc_count_;
+  // Global STT_GNU_IFUNC symbols.
+  std::vector<Global_ifunc> global_ifuncs_;
+  // Local STT_GNU_IFUNC symbols.
+  std::vector<Local_ifunc> local_ifuncs_;
 };
 
 // Define the constants as required by C++ standard.
@@ -1253,7 +1367,8 @@ const unsigned int Output_data_plt_sparc<size, big_endian>::plt_block_size;
 
 template<int size, bool big_endian>
 Output_data_plt_sparc<size, big_endian>::Output_data_plt_sparc(Layout* layout)
-  : Output_section_data(size == 32 ? 4 : 8), count_(0)
+  : Output_section_data(size == 32 ? 4 : 8), ifunc_rel_(NULL),
+    count_(0), ifunc_count_(0), global_ifuncs_(), local_ifuncs_()
 {
   this->rel_ = new Reloc_section(false);
   layout->add_output_section_data(".rela.plt", elfcpp::SHT_RELA,
@@ -1272,38 +1387,171 @@ Output_data_plt_sparc<size, big_endian>::do_adjust_output_section(Output_section
 
 template<int size, bool big_endian>
 void
-Output_data_plt_sparc<size, big_endian>::add_entry(Symbol* gsym)
+Output_data_plt_sparc<size, big_endian>::add_entry(Symbol_table* symtab,
+                                                  Layout* layout,
+                                                  Symbol* gsym)
 {
   gold_assert(!gsym->has_plt_offset());
 
-  unsigned int index = this->count_ + 4;
   section_offset_type plt_offset;
+  unsigned int index;
 
-  if (size == 32 || index < 32768)
-    plt_offset = index * base_plt_entry_size;
+  if (gsym->type() == elfcpp::STT_GNU_IFUNC
+      && gsym->can_use_relative_reloc(false))
+    {
+      index = this->ifunc_count_;
+      plt_offset = plt_index_to_offset(index);
+      gsym->set_plt_offset(plt_offset);
+      ++this->ifunc_count_;
+      Reloc_section* rel = this->rela_ifunc(symtab, layout);
+
+      struct Global_ifunc gi;
+      gi.rel = rel;
+      gi.gsym = gsym;
+      gi.plt_index = index;
+      this->global_ifuncs_.push_back(gi);
+    }
   else
     {
-       unsigned int ext_index = index - 32768;
+      plt_offset = plt_index_to_offset(this->count_ + 4);
+      gsym->set_plt_offset(plt_offset);
+      ++this->count_;
+      gsym->set_needs_dynsym_entry();
+      this->rel_->add_global(gsym, elfcpp::R_SPARC_JMP_SLOT, this,
+                            plt_offset, 0);
+    }
 
-       plt_offset = (32768 * base_plt_entry_size)
-         + ((ext_index / plt_entries_per_block)
-            * plt_block_size)
-         + ((ext_index % plt_entries_per_block)
-            * plt_insn_chunk_size);
+  // Note that we don't need to save the symbol.  The contents of the
+  // PLT are independent of which symbols are used.  The symbols only
+  // appear in the relocations.
+}
+
+template<int size, bool big_endian>
+unsigned int
+Output_data_plt_sparc<size, big_endian>::add_local_ifunc_entry(
+    Symbol_table* symtab,
+    Layout* layout,
+    Sized_relobj_file<size, big_endian>* relobj,
+    unsigned int local_sym_index)
+{
+  unsigned int index = this->ifunc_count_;
+  section_offset_type plt_offset;
+
+  plt_offset = plt_index_to_offset(index);
+  ++this->ifunc_count_;
+
+  Reloc_section* rel = this->rela_ifunc(symtab, layout);
+
+  struct Local_ifunc li;
+  li.rel = rel;
+  li.object = relobj;
+  li.local_sym_index = local_sym_index;
+  li.plt_index = index;
+  this->local_ifuncs_.push_back(li);
+
+  return plt_offset;
+}
+
+// Emit any pending IFUNC plt relocations.
+
+template<int size, bool big_endian>
+void
+Output_data_plt_sparc<size, big_endian>::emit_pending_ifunc_relocs()
+{
+  // Emit any pending IFUNC relocs.
+  for (typename std::vector<Global_ifunc>::const_iterator p =
+        this->global_ifuncs_.begin();
+       p != this->global_ifuncs_.end();
+       ++p)
+    {
+      section_offset_type plt_offset;
+      unsigned int index;
+
+      index = this->count_ + p->plt_index + 4;
+      plt_offset = this->plt_index_to_offset(index);
+      p->rel->add_symbolless_global_addend(p->gsym, elfcpp::R_SPARC_JMP_IREL,
+                                          this, plt_offset, 0);
+    }
+
+  for (typename std::vector<Local_ifunc>::const_iterator p =
+        this->local_ifuncs_.begin();
+       p != this->local_ifuncs_.end();
+       ++p)
+    {
+      section_offset_type plt_offset;
+      unsigned int index;
+
+      index = this->count_ + p->plt_index + 4;
+      plt_offset = this->plt_index_to_offset(index);
+      p->rel->add_symbolless_local_addend(p->object, p->local_sym_index,
+                                         elfcpp::R_SPARC_JMP_IREL,
+                                         this, plt_offset, 0);
     }
+}
 
-  gsym->set_plt_offset(plt_offset);
+// Return where the IFUNC relocations should go in the PLT.  These
+// follow the non-IFUNC relocations.
 
-  ++this->count_;
+template<int size, bool big_endian>
+typename Output_data_plt_sparc<size, big_endian>::Reloc_section*
+Output_data_plt_sparc<size, big_endian>::rela_ifunc(
+       Symbol_table* symtab,
+       Layout* layout)
+{
+  if (this->ifunc_rel_ == NULL)
+    {
+      this->ifunc_rel_ = new Reloc_section(false);
+      layout->add_output_section_data(".rela.plt", elfcpp::SHT_RELA,
+                                     elfcpp::SHF_ALLOC, this->ifunc_rel_,
+                                     ORDER_DYNAMIC_PLT_RELOCS, false);
+      gold_assert(this->ifunc_rel_->output_section()
+                 == this->rel_->output_section());
+
+      if (parameters->doing_static_link())
+       {
+         // A statically linked executable will only have a .rel.plt
+         // section to hold R_SPARC_IRELATIVE and R_SPARC_JMP_IREL
+         // relocs for STT_GNU_IFUNC symbols.  The library will use
+         // these symbols to locate the IRELATIVE and JMP_IREL relocs
+         // at program startup time.
+         symtab->define_in_output_data("__rela_iplt_start", NULL,
+                                       Symbol_table::PREDEFINED,
+                                       this->ifunc_rel_, 0, 0,
+                                       elfcpp::STT_NOTYPE, elfcpp::STB_GLOBAL,
+                                       elfcpp::STV_HIDDEN, 0, false, true);
+         symtab->define_in_output_data("__rela_iplt_end", NULL,
+                                       Symbol_table::PREDEFINED,
+                                       this->ifunc_rel_, 0, 0,
+                                       elfcpp::STT_NOTYPE, elfcpp::STB_GLOBAL,
+                                       elfcpp::STV_HIDDEN, 0, true, true);
+       }
+    }
+  return this->ifunc_rel_;
+}
 
-  // Every PLT entry needs a reloc.
-  gsym->set_needs_dynsym_entry();
-  this->rel_->add_global(gsym, elfcpp::R_SPARC_JMP_SLOT, this,
-                        plt_offset, 0);
+// Return the PLT address to use for a global symbol.
 
-  // Note that we don't need to save the symbol.  The contents of the
-  // PLT are independent of which symbols are used.  The symbols only
-  // appear in the relocations.
+template<int size, bool big_endian>
+uint64_t
+Output_data_plt_sparc<size, big_endian>::address_for_global(const Symbol* gsym)
+{
+  uint64_t offset = 0;
+  if (gsym->type() == elfcpp::STT_GNU_IFUNC
+      && gsym->can_use_relative_reloc(false))
+    offset = plt_index_to_offset(this->count_ + 4);
+  return this->address() + offset;
+}
+
+// Return the PLT address to use for a local symbol.  These are always
+// IRELATIVE relocs.
+
+template<int size, bool big_endian>
+uint64_t
+Output_data_plt_sparc<size, big_endian>::address_for_local(
+       const Relobj*,
+       unsigned int)
+{
+  return this->address() + plt_index_to_offset(this->count_ + 4);
 }
 
 static const unsigned int sparc_nop = 0x01000000;
@@ -1331,10 +1579,10 @@ Output_data_plt_sparc<size, big_endian>::do_write(Output_file* of)
   unsigned char* pov = oview;
 
   memset(pov, 0, base_plt_entry_size * 4);
-  pov += base_plt_entry_size * 4;
+  pov += this->first_plt_entry_offset();
 
   unsigned int plt_offset = base_plt_entry_size * 4;
-  const unsigned int count = this->count_;
+  const unsigned int count = this->entry_count();
 
   if (size == 64)
     {
@@ -1454,6 +1702,38 @@ Output_data_plt_sparc<size, big_endian>::do_write(Output_file* of)
   of->write_output_view(offset, oview_size, oview);
 }
 
+// Create the PLT section.
+
+template<int size, bool big_endian>
+void
+Target_sparc<size, big_endian>::make_plt_section(Symbol_table* symtab,
+                                                Layout* layout)
+{
+  // Create the GOT sections first.
+  this->got_section(symtab, layout);
+
+  // Ensure that .rela.dyn always appears before .rela.plt  This is
+  // necessary due to how, on Sparc and some other targets, .rela.dyn
+  // needs to include .rela.plt in it's range.
+  this->rela_dyn_section(layout);
+
+  this->plt_ = new Output_data_plt_sparc<size, big_endian>(layout);
+  layout->add_output_section_data(".plt", elfcpp::SHT_PROGBITS,
+                                 (elfcpp::SHF_ALLOC
+                                  | elfcpp::SHF_EXECINSTR
+                                  | elfcpp::SHF_WRITE),
+                                 this->plt_, ORDER_NON_RELRO_FIRST, false);
+
+  // Define _PROCEDURE_LINKAGE_TABLE_ at the start of the .plt section.
+  symtab->define_in_output_data("_PROCEDURE_LINKAGE_TABLE_", NULL,
+                               Symbol_table::PREDEFINED,
+                               this->plt_,
+                               0, 0, elfcpp::STT_OBJECT,
+                               elfcpp::STB_LOCAL,
+                               elfcpp::STV_HIDDEN, 0,
+                               false, false);
+}
+
 // Create a PLT entry for a global symbol.
 
 template<int size, bool big_endian>
@@ -1466,33 +1746,29 @@ Target_sparc<size, big_endian>::make_plt_entry(Symbol_table* symtab,
     return;
 
   if (this->plt_ == NULL)
-    {
-      // Create the GOT sections first.
-      this->got_section(symtab, layout);
-
-      // Ensure that .rela.dyn always appears before .rela.plt  This is
-      // necessary due to how, on Sparc and some other targets, .rela.dyn
-      // needs to include .rela.plt in it's range.
-      this->rela_dyn_section(layout);
+    this->make_plt_section(symtab, layout);
 
-      this->plt_ = new Output_data_plt_sparc<size, big_endian>(layout);
-      layout->add_output_section_data(".plt", elfcpp::SHT_PROGBITS,
-                                     (elfcpp::SHF_ALLOC
-                                      | elfcpp::SHF_EXECINSTR
-                                      | elfcpp::SHF_WRITE),
-                                     this->plt_, ORDER_PLT, false);
+  this->plt_->add_entry(symtab, layout, gsym);
+}
 
-      // Define _PROCEDURE_LINKAGE_TABLE_ at the start of the .plt section.
-      symtab->define_in_output_data("_PROCEDURE_LINKAGE_TABLE_", NULL,
-                                   Symbol_table::PREDEFINED,
-                                   this->plt_,
-                                   0, 0, elfcpp::STT_OBJECT,
-                                   elfcpp::STB_LOCAL,
-                                   elfcpp::STV_HIDDEN, 0,
-                                   false, false);
-    }
+// Make a PLT entry for a local STT_GNU_IFUNC symbol.
 
-  this->plt_->add_entry(gsym);
+template<int size, bool big_endian>
+void
+Target_sparc<size, big_endian>::make_local_ifunc_plt_entry(
+       Symbol_table* symtab,
+       Layout* layout,
+       Sized_relobj_file<size, big_endian>* relobj,
+       unsigned int local_sym_index)
+{
+  if (relobj->local_has_plt_offset(local_sym_index))
+    return;
+  if (this->plt_ == NULL)
+    this->make_plt_section(symtab, layout);
+  unsigned int plt_offset = this->plt_->add_local_ifunc_entry(symtab, layout,
+                                                             relobj,
+                                                             local_sym_index);
+  relobj->set_local_plt_offset(local_sym_index, plt_offset);
 }
 
 // Return the number of entries in the PLT.
@@ -1720,7 +1996,9 @@ Target_sparc<size, big_endian>::Scan::get_reference_flags(unsigned int r_type)
     case elfcpp::R_SPARC_COPY:
     case elfcpp::R_SPARC_GLOB_DAT:
     case elfcpp::R_SPARC_JMP_SLOT:
+    case elfcpp::R_SPARC_JMP_IREL:
     case elfcpp::R_SPARC_RELATIVE:
+    case elfcpp::R_SPARC_IRELATIVE:
     case elfcpp::R_SPARC_TLS_DTPMOD64:
     case elfcpp::R_SPARC_TLS_DTPMOD32:
     case elfcpp::R_SPARC_TLS_DTPOFF64:
@@ -1772,10 +2050,12 @@ Target_sparc<size, big_endian>::Scan::check_non_pic(Relobj* object, unsigned int
        {
          // These are the relocation types supported by glibc for sparc 64-bit.
        case elfcpp::R_SPARC_RELATIVE:
+       case elfcpp::R_SPARC_IRELATIVE:
        case elfcpp::R_SPARC_COPY:
        case elfcpp::R_SPARC_64:
        case elfcpp::R_SPARC_GLOB_DAT:
        case elfcpp::R_SPARC_JMP_SLOT:
+       case elfcpp::R_SPARC_JMP_IREL:
        case elfcpp::R_SPARC_TLS_DTPMOD64:
        case elfcpp::R_SPARC_TLS_DTPOFF64:
        case elfcpp::R_SPARC_TLS_TPOFF64:
@@ -1812,10 +2092,12 @@ Target_sparc<size, big_endian>::Scan::check_non_pic(Relobj* object, unsigned int
        {
          // These are the relocation types supported by glibc for sparc 32-bit.
        case elfcpp::R_SPARC_RELATIVE:
+       case elfcpp::R_SPARC_IRELATIVE:
        case elfcpp::R_SPARC_COPY:
        case elfcpp::R_SPARC_GLOB_DAT:
        case elfcpp::R_SPARC_32:
        case elfcpp::R_SPARC_JMP_SLOT:
+       case elfcpp::R_SPARC_JMP_IREL:
        case elfcpp::R_SPARC_TLS_DTPMOD32:
        case elfcpp::R_SPARC_TLS_DTPOFF32:
        case elfcpp::R_SPARC_TLS_TPOFF32:
@@ -1850,6 +2132,22 @@ Target_sparc<size, big_endian>::Scan::check_non_pic(Relobj* object, unsigned int
   return;
 }
 
+// Return whether we need to make a PLT entry for a relocation of the
+// given type against a STT_GNU_IFUNC symbol.
+
+template<int size, bool big_endian>
+bool
+Target_sparc<size, big_endian>::Scan::reloc_needs_plt_for_ifunc(
+     Sized_relobj_file<size, big_endian>* object,
+     unsigned int r_type)
+{
+  int flags = Scan::get_reference_flags(r_type);
+  if (flags & Symbol::TLS_REF)
+    gold_error(_("%s: unsupported TLS reloc %u for IFUNC symbol"),
+               object->name().c_str(), r_type);
+  return flags != 0;
+}
+
 // Scan a relocation for a local symbol.
 
 template<int size, bool big_endian>
@@ -1865,9 +2163,17 @@ Target_sparc<size, big_endian>::Scan::local(
                        unsigned int r_type,
                        const elfcpp::Sym<size, big_endian>& lsym)
 {
+  bool is_ifunc = lsym.get_st_type() == elfcpp::STT_GNU_IFUNC;
   unsigned int orig_r_type = r_type;
-
   r_type &= 0xff;
+
+  if (is_ifunc
+      && this->reloc_needs_plt_for_ifunc(object, r_type))
+    {
+      unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
+      target->make_local_ifunc_plt_entry(symtab, layout, object, r_sym);
+    }
+
   switch (r_type)
     {
     case elfcpp::R_SPARC_NONE:
@@ -1891,7 +2197,7 @@ Target_sparc<size, big_endian>::Scan::local(
           rela_dyn->add_local_relative(object, r_sym, elfcpp::R_SPARC_RELATIVE,
                                       output_section, data_shndx,
                                       reloc.get_r_offset(),
-                                      reloc.get_r_addend(), false);
+                                      reloc.get_r_addend(), is_ifunc);
         }
       break;
 
@@ -1978,13 +2284,11 @@ Target_sparc<size, big_endian>::Scan::local(
            if (!object->local_has_got_offset(r_sym, GOT_TYPE_STANDARD))
              {
                Reloc_section* rela_dyn = target->rela_dyn_section(layout);
-               unsigned int off;
-
-               off = got->add_constant(0);
+               unsigned int off = got->add_constant(0);
                object->set_local_got_offset(r_sym, GOT_TYPE_STANDARD, off);
                rela_dyn->add_local_relative(object, r_sym,
                                             elfcpp::R_SPARC_RELATIVE,
-                                            got, off, 0, false);
+                                            got, off, 0, is_ifunc);
              }
          }
        else
@@ -2126,7 +2430,9 @@ Target_sparc<size, big_endian>::Scan::local(
     case elfcpp::R_SPARC_COPY:
     case elfcpp::R_SPARC_GLOB_DAT:
     case elfcpp::R_SPARC_JMP_SLOT:
+    case elfcpp::R_SPARC_JMP_IREL:
     case elfcpp::R_SPARC_RELATIVE:
+    case elfcpp::R_SPARC_IRELATIVE:
     case elfcpp::R_SPARC_TLS_DTPMOD64:
     case elfcpp::R_SPARC_TLS_DTPMOD32:
     case elfcpp::R_SPARC_TLS_DTPOFF64:
@@ -2172,6 +2478,7 @@ Target_sparc<size, big_endian>::Scan::global(
                                Symbol* gsym)
 {
   unsigned int orig_r_type = r_type;
+  bool is_ifunc = gsym->type() == elfcpp::STT_GNU_IFUNC;
 
   // A reference to _GLOBAL_OFFSET_TABLE_ implies that we need a got
   // section.  We check here to avoid creating a dynamic reloc against
@@ -2181,6 +2488,12 @@ Target_sparc<size, big_endian>::Scan::global(
     target->got_section(symtab, layout);
 
   r_type &= 0xff;
+
+  // A STT_GNU_IFUNC symbol may require a PLT entry.
+  if (is_ifunc
+      && this->reloc_needs_plt_for_ifunc(object, r_type))
+    target->make_plt_entry(symtab, layout, gsym);
+
   switch (r_type)
     {
     case elfcpp::R_SPARC_NONE:
@@ -2325,6 +2638,27 @@ Target_sparc<size, big_endian>::Scan::global(
                target->copy_reloc(symtab, layout, object,
                                   data_shndx, output_section, gsym, reloc);
               }
+           else if (((size == 64 && r_type == elfcpp::R_SPARC_64)
+                     || (size == 32 && r_type == elfcpp::R_SPARC_32))
+                    && gsym->type() == elfcpp::STT_GNU_IFUNC
+                    && gsym->can_use_relative_reloc(false)
+                    && !gsym->is_from_dynobj()
+                    && !gsym->is_undefined()
+                    && !gsym->is_preemptible())
+             {
+               // Use an IRELATIVE reloc for a locally defined
+               // STT_GNU_IFUNC symbol.  This makes a function
+               // address in a PIE executable match the address in a
+               // shared library that it links against.
+               Reloc_section* rela_dyn =
+                 target->rela_ifunc_section(layout);
+               unsigned int r_type = elfcpp::R_SPARC_IRELATIVE;
+               rela_dyn->add_symbolless_global_addend(gsym, r_type,
+                                                      output_section, object,
+                                                      data_shndx,
+                                                      reloc.get_r_offset(),
+                                                      reloc.get_r_addend());
+             }
             else if ((r_type == elfcpp::R_SPARC_32
                      || r_type == elfcpp::R_SPARC_64)
                      && gsym->can_use_relative_reloc(false))
@@ -2333,7 +2667,7 @@ Target_sparc<size, big_endian>::Scan::global(
                 rela_dyn->add_global_relative(gsym, elfcpp::R_SPARC_RELATIVE,
                                              output_section, object,
                                              data_shndx, reloc.get_r_offset(),
-                                             reloc.get_r_addend(), false);
+                                             reloc.get_r_addend(), is_ifunc);
               }
             else
               {
@@ -2370,24 +2704,68 @@ Target_sparc<size, big_endian>::Scan::global(
 
        got = target->got_section(symtab, layout);
         if (gsym->final_value_is_known())
-          got->add_global(gsym, GOT_TYPE_STANDARD);
+         {
+           // For a STT_GNU_IFUNC symbol we want the PLT address.
+           if (gsym->type() == elfcpp::STT_GNU_IFUNC)
+             got->add_global_plt(gsym, GOT_TYPE_STANDARD);
+           else
+             got->add_global(gsym, GOT_TYPE_STANDARD);
+         }
         else
           {
             // If this symbol is not fully resolved, we need to add a
-            // dynamic relocation for it.
+            // GOT entry with a dynamic relocation.
+           bool is_ifunc = gsym->type() == elfcpp::STT_GNU_IFUNC;
+
+           // Use a GLOB_DAT rather than a RELATIVE reloc if:
+           //
+           // 1) The symbol may be defined in some other module.
+           //
+           // 2) We are building a shared library and this is a
+           // protected symbol; using GLOB_DAT means that the dynamic
+           // linker can use the address of the PLT in the main
+           // executable when appropriate so that function address
+           // comparisons work.
+           //
+           // 3) This is a STT_GNU_IFUNC symbol in position dependent
+           // code, again so that function address comparisons work.
             Reloc_section* rela_dyn = target->rela_dyn_section(layout);
             if (gsym->is_from_dynobj()
                 || gsym->is_undefined()
-                || gsym->is_preemptible())
-              got->add_global_with_rel(gsym, GOT_TYPE_STANDARD, rela_dyn,
-                                      elfcpp::R_SPARC_GLOB_DAT);
+                || gsym->is_preemptible()
+               || (gsym->visibility() == elfcpp::STV_PROTECTED
+                   && parameters->options().shared())
+               || (gsym->type() == elfcpp::STT_GNU_IFUNC
+                   && parameters->options().output_is_position_independent()
+                   && !gsym->is_forced_local()))
+             {
+               unsigned int r_type = elfcpp::R_SPARC_GLOB_DAT;
+
+               // If this symbol is forced local, this relocation will
+               // not work properly.  That's because ld.so on sparc
+               // (and 32-bit powerpc) expects st_value in the r_addend
+               // of relocations for STB_LOCAL symbols.  Curiously the
+               // BFD linker does not promote global hidden symbols to be
+               // STB_LOCAL in the dynamic symbol table like Gold does.
+               gold_assert(!gsym->is_forced_local());
+               got->add_global_with_rel(gsym, GOT_TYPE_STANDARD, rela_dyn,
+                                        r_type);
+             }
             else if (!gsym->has_got_offset(GOT_TYPE_STANDARD))
               {
                unsigned int off = got->add_constant(0);
 
                gsym->set_got_offset(GOT_TYPE_STANDARD, off);
+               if (is_ifunc)
+                 {
+                   // Tell the dynamic linker to use the PLT address
+                   // when resolving relocations.
+                   if (gsym->is_from_dynobj()
+                       && !parameters->options().shared())
+                     gsym->set_needs_dynsym_value();
+                 }
                rela_dyn->add_global_relative(gsym, elfcpp::R_SPARC_RELATIVE,
-                                             got, off, 0, false);
+                                             got, off, 0, is_ifunc);
              }
           }
       }
@@ -2520,7 +2898,9 @@ Target_sparc<size, big_endian>::Scan::global(
     case elfcpp::R_SPARC_COPY:
     case elfcpp::R_SPARC_GLOB_DAT:
     case elfcpp::R_SPARC_JMP_SLOT:
+    case elfcpp::R_SPARC_JMP_IREL:
     case elfcpp::R_SPARC_RELATIVE:
+    case elfcpp::R_SPARC_IRELATIVE:
     case elfcpp::R_SPARC_TLS_DTPMOD64:
     case elfcpp::R_SPARC_TLS_DTPMOD32:
     case elfcpp::R_SPARC_TLS_DTPOFF64:
@@ -2620,8 +3000,11 @@ void
 Target_sparc<size, big_endian>::do_finalize_sections(
     Layout* layout,
     const Input_objects*,
-    Symbol_table*)
+    Symbol_table* symtab)
 {
+  if (this->plt_)
+    this->plt_->emit_pending_ifunc_relocs();
+
   // Fill in some more dynamic tags.
   const Reloc_section* rel_plt = (this->plt_ == NULL
                                  ? NULL
@@ -2633,6 +3016,47 @@ Target_sparc<size, big_endian>::do_finalize_sections(
   // relocs.
   if (this->copy_relocs_.any_saved_relocs())
     this->copy_relocs_.emit(this->rela_dyn_section(layout));
+
+  if (parameters->doing_static_link()
+      && (this->plt_ == NULL || !this->plt_->has_ifunc_section()))
+    {
+      // If linking statically, make sure that the __rela_iplt symbols
+      // were defined if necessary, even if we didn't create a PLT.
+      static const Define_symbol_in_segment syms[] =
+       {
+         {
+           "__rela_iplt_start",        // name
+           elfcpp::PT_LOAD,            // segment_type
+           elfcpp::PF_W,               // segment_flags_set
+           elfcpp::PF(0),              // segment_flags_clear
+           0,                          // value
+           0,                          // size
+           elfcpp::STT_NOTYPE,         // type
+           elfcpp::STB_GLOBAL,         // binding
+           elfcpp::STV_HIDDEN,         // visibility
+           0,                          // nonvis
+           Symbol::SEGMENT_START,      // offset_from_base
+           true                        // only_if_ref
+         },
+         {
+           "__rela_iplt_end",          // name
+           elfcpp::PT_LOAD,            // segment_type
+           elfcpp::PF_W,               // segment_flags_set
+           elfcpp::PF(0),              // segment_flags_clear
+           0,                          // value
+           0,                          // size
+           elfcpp::STT_NOTYPE,         // type
+           elfcpp::STB_GLOBAL,         // binding
+           elfcpp::STV_HIDDEN,         // visibility
+           0,                          // nonvis
+           Symbol::SEGMENT_START,      // offset_from_base
+           true                        // only_if_ref
+         }
+       };
+
+      symtab->define_symbols(layout, 2, syms,
+                            layout->script_options()->saw_sections_clause());
+    }
 }
 
 // Perform a relocation.
@@ -2669,6 +3093,7 @@ Target_sparc<size, big_endian>::Relocate::relocate(
     view -= 4;
 
   typedef Sparc_relocate_functions<size, big_endian> Reloc;
+  const Sized_relobj_file<size, big_endian>* object = relinfo->object;
 
   // Pick the value to use for symbols defined in shared objects.
   Symbol_value<size> symval;
@@ -2677,14 +3102,23 @@ Target_sparc<size, big_endian>::Relocate::relocate(
     {
       elfcpp::Elf_Xword value;
 
-      value = target->plt_section()->address() + gsym->plt_offset();
+      value = target->plt_address_for_global(gsym) + gsym->plt_offset();
 
       symval.set_output_value(value);
 
       psymval = &symval;
     }
+  else if (gsym == NULL && psymval->is_ifunc_symbol())
+    {
+      unsigned int r_sym = elfcpp::elf_r_sym<size>(rela.get_r_info());
+      if (object->local_has_plt_offset(r_sym))
+       {
+         symval.set_output_value(target->plt_address_for_local(object, r_sym)
+                                 + object->local_plt_offset(r_sym));
+         psymval = &symval;
+       }
+    }
 
-  const Sized_relobj_file<size, big_endian>* object = relinfo->object;
   const elfcpp::Elf_Xword addend = rela.get_r_addend();
 
   // Get the GOT offset if needed.  Unlike i386 and x86_64, our GOT
@@ -2995,7 +3429,9 @@ Target_sparc<size, big_endian>::Relocate::relocate(
     case elfcpp::R_SPARC_COPY:
     case elfcpp::R_SPARC_GLOB_DAT:
     case elfcpp::R_SPARC_JMP_SLOT:
+    case elfcpp::R_SPARC_JMP_IREL:
     case elfcpp::R_SPARC_RELATIVE:
+    case elfcpp::R_SPARC_IRELATIVE:
       // These are outstanding tls relocs, which are unexpected when
       // linking.
     case elfcpp::R_SPARC_TLS_DTPMOD64: