[PATCH 2/2] Add sparc IFUNC support to Gold.

David Miller davem@davemloft.net
Wed Apr 11 19:15:00 GMT 2012


This adds sparc IFUNC support to Gold.

Actually, this is the first point at which I've been able to run the
Gold testsuite on sparc at all.  glibc on all my sparc machines has
had IFUNC symbols for more than a year, and the HJ's hack to turn
IFUNC into FUNC only works for shared links, so even the basic static
test wouldn't link.

Not all the ifunc tests pass, 6 still fail, but I know how to fix
that.  The fix will involve adding proper GOTDATA_OP code
transformations, because otherwise the IFUNC functions run and try to
use GOT slots which haven't been resolved yet.  We'll also need to
link the ifunc tests using -fPIC and -fPIE because we can't do the GOT
relative relocation to local symbol optimization with -fpic on sparc.

Besides the weird relative relocation issues (described in patch #1)
the implementation mostly mirrors what i386 and x86_64 do.  One big
difference is that I can't emit the PLT relocs for IFUNC at
add_entry() time.  I have to emit them later, in do_finalize_sections()

This is because the PLT relocs have the plt offset in them, and that
doesn't stabilize until we've generated all of relocs.  This is because
of the segregation between IFUNC and non-iFUNC relocations.

This points out another problem on sparc, we have variable sized
PLT entries.  On 64-bit after 32768 entries, the PLT entries change
in size.

The sparc delayed IFUNC PLT relocation emitter handles this correctly,
since it works with the final PLT index to calculate the correct
offset.

But the ->address_for_global and ->address_for_local abstractions
needs to be adjusted to handle this.  We'll have to pass PLT indices
around instead of offsets to get it right.

But realistically all the normal cases are handled properly, very
few objects require more than 32768 PLT slots :-)

Another testcase that still fails is memory_test.sh because that test
assumes a minimum alignment of 4K, or something like that.  Whereas
sparc's minimum alignment is 8K.  Any suggestions on how to fix that
are welcome.

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/sparc.h |    1 +
 gold/sparc.cc  |  563 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 487 insertions(+), 77 deletions(-)

diff --git a/elfcpp/sparc.h b/elfcpp/sparc.h
index 77c4668..6b561be 100644
--- a/elfcpp/sparc.h
+++ b/elfcpp/sparc.h
@@ -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.
diff --git a/gold/sparc.cc b/gold/sparc.cc
index e1bdc8e..993fc55 100644
--- a/gold/sparc.cc
+++ b/gold/sparc.cc
@@ -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);
     }
 
-  gsym->set_plt_offset(plt_offset);
+  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);
+    }
+}
 
-  ++this->count_;
+// Return where the IFUNC relocations should go in the PLT.  These
+// follow the non-IFUNC relocations.
 
-  // 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);
+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_;
+}
 
-  // 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.
+// Return the PLT address to use for a global symbol.
+
+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);
+    this->make_plt_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_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:
@@ -2333,7 +2646,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 +2683,62 @@ 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
+	    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.
             // dynamic relocation for it.
             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()))
+	      {
+		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 +2871,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 +2973,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 +2989,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 +3066,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 +3075,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 +3402,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:
-- 
1.7.9.5



More information about the Binutils mailing list