[PATCH 2/4] alpha: support IFUNC in static links

Matt Turner <[email protected]>
Newsgroups gmane.comp.gnu.binutils
Message-ID <[email protected]>
A static link that referenced an IFUNC died with

  BFD assertion fail bfd/elf64-alpha.c:4027

There is no dynamic linker in that case, so nothing resolves an IFUNC at
load time.  The C library instead applies the R_ALPHA_IRELATIVE relocations
between __rela_iplt_start and __rela_iplt_end at startup, and the linker
has to put them in .rela.iplt and account for the space.

Create that section when a reference to such an IFUNC is seen, size it from
the GOT entries the symbol keeps, and emit the relocations into it.

A globally visible IFUNC in a shared library was a second way to get this
wrong, overrunning .rela.got and corrupting the heap:

  ld -shared shared_ifunc.o
  BFD assertion fail bfd/elf64-alpha.c:4136
  free(): invalid pointer

elf64_alpha_size_rela_got_1 reserves nothing for a symbol with a PLT entry,
because all of its GOT relocations are supposed to come from
finish_dynamic_symbol and land in .rela.plt.  That held as long as needs_plt
implied the symbol was dynamic, which is what adjust_dynamic_symbol used to
guarantee.  Now that IFUNCs get a PLT entry even when they are not dynamic,
a hidden IFUNC can have a PLT entry and still be non-dynamic, so the
R_ALPHA_LITERAL case in relocate_section emitted an unaccounted IRELATIVE
into .rela.got.  In ld-ifunc/lib.c the hidden alias __GI_library_func2 is
exactly that, and its relocation took the slot sized for global's GLOB_DAT.
Keep out of finish_dynamic_symbol's way by testing needs_plt as well, which
restores the invariant sizing relies on.

The predicates take the local symbol as well as the hash table entry, since
a later change needs to ask the same questions about a symbol that has no
hash table entry.
---
 bfd/elf64-alpha.c | 154 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 145 insertions(+), 9 deletions(-)

diff --git ./bfd/elf64-alpha.c ./bfd/elf64-alpha.c
index 6925bdd0a4d..4e0c2d8901e 100644
--- ./bfd/elf64-alpha.c
+++ ./bfd/elf64-alpha.c
@@ -1768,6 +1768,89 @@ elf64_alpha_sort_relocs_p (asection *sec)
 }
 
 
+/* True if a relocation refers to an IFUNC.  H describes the symbol when it
+   is global and SYM when it is local; a local symbol has no hash table
+   entry, so testing H alone misses it.  */
+
+static bool
+elf64_alpha_ifunc_p (struct alpha_elf_link_hash_entry *h,
+		     Elf_Internal_Sym *sym)
+{
+  if (h != NULL)
+    return h->root.type == STT_GNU_IFUNC;
+
+  return sym != NULL && ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC;
+}
+
+/* True if the IFUNC is one this link resolves itself, i.e. one the dynamic
+   linker will never see.  Its references are resolved at startup by
+   R_ALPHA_IRELATIVE relocations in .rela.iplt, which the C library applies
+   between __rela_iplt_start and __rela_iplt_end.  A local symbol is always
+   defined in this object, so only a global one needs def_regular.  */
+
+static bool
+elf64_alpha_static_ifunc_p (struct alpha_elf_link_hash_entry *h,
+			    Elf_Internal_Sym *sym,
+			    struct bfd_link_info *info)
+{
+  return (elf64_alpha_ifunc_p (h, sym)
+	  && (h == NULL || h->root.def_regular)
+	  && !elf_hash_table (info)->dynamic_sections_created);
+}
+
+/* Reserve space for COUNT R_ALPHA_IRELATIVE relocations in .rela.iplt,
+   creating that section if this is the first one.  */
+
+static bool
+elf64_alpha_reserve_irelative (bfd *abfd, struct bfd_link_info *info,
+			       unsigned int count)
+{
+  struct elf_link_hash_table *htab = elf_hash_table (info);
+  asection *s = htab->irelplt;
+
+  if (s == NULL)
+    {
+      const struct elf_backend_data *bed = get_elf_backend_data (abfd);
+
+      if (htab->dynobj == NULL)
+	htab->dynobj = abfd;
+
+      s = bfd_make_section_anyway_with_flags (htab->dynobj, ".rela.iplt",
+					      (bed->dynamic_sec_flags
+					       | SEC_READONLY));
+      if (s == NULL || !bfd_set_section_alignment (s, 3))
+	return false;
+      htab->irelplt = s;
+    }
+
+  s->size += count * sizeof (Elf64_External_Rela);
+  return true;
+}
+
+/* Reserve .rela.iplt space for the GOT entries of a static IFUNC.  */
+
+static bool
+elf64_alpha_size_irelative_got (struct alpha_elf_link_hash_entry *h,
+				struct bfd_link_info *info)
+{
+  struct alpha_elf_got_entry *gotent;
+  unsigned int entries = 0;
+
+  if (!elf64_alpha_static_ifunc_p (h, NULL, info))
+    return true;
+
+  for (gotent = h->got_entries; gotent != NULL; gotent = gotent->next)
+    if (gotent->use_count > 0 && gotent->reloc_type == R_ALPHA_LITERAL)
+      entries++;
+
+  if (entries > 0
+      && !elf64_alpha_reserve_irelative (elf_hash_table (info)->dynobj, info,
+					 entries))
+    return false;
+
+  return true;
+}
+
 /* Handle dynamic relocations when doing an Alpha ELF link.  */
 
 static bool
@@ -1837,6 +1920,19 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 		|| h->root.root.type == bfd_link_hash_defweak))
 	maybe_dynamic = true;
 
+      /* A reference to an IFUNC the dynamic linker will never see has to be
+	 resolved at startup out of .rela.iplt.  Create that section as soon
+	 as we see such a reference, both so it is mapped to an output
+	 section and so late_size_sections runs at all.  */
+      if (h != NULL
+	  && h->root.type == STT_GNU_IFUNC
+	  && h->root.def_regular
+	  && !bfd_link_pic (info)
+	  && !maybe_dynamic
+	  && (sec->flags & SEC_ALLOC)
+	  && !elf64_alpha_reserve_irelative (abfd, info, 0))
+	return false;
+
       need = 0;
       gotent_flags = 0;
       r_type = ELF64_R_TYPE (rel->r_info);
@@ -1871,7 +1967,18 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
 	case R_ALPHA_REFLONG:
 	case R_ALPHA_REFQUAD:
-	  if (bfd_link_pic (info) || maybe_dynamic)
+	  if (h != NULL
+	      && h->root.type == STT_GNU_IFUNC
+	      && !bfd_link_pic (info)
+	      && !maybe_dynamic
+	      && (sec->flags & SEC_ALLOC))
+	    {
+	      /* Resolved at startup through .rela.iplt rather than by the
+		 dynamic linker.  */
+	      if (!elf64_alpha_reserve_irelative (abfd, info, 1))
+		return false;
+	    }
+	  else if (bfd_link_pic (info) || maybe_dynamic)
 	    need = NEED_DYNREL;
 	  break;
 
@@ -2843,7 +2950,10 @@ elf64_alpha_late_size_sections (struct bfd_link_info *info)
       elf64_alpha_size_rela_got_section (info);
       elf64_alpha_size_plt_section (info);
     }
-  /* else we're not dynamic and by definition we don't need such things.  */
+  else
+    /* We are not dynamic, so the only relocations we can need are the
+       IRELATIVEs resolving our own IFUNCs at startup.  */
+    alpha_elf_link_hash_traverse (htab, elf64_alpha_size_irelative_got, info);
 
   /* The check_relocs and adjust_dynamic_symbol entry points have
      determined the sizes of the various dynamic sections.  Allocate
@@ -4354,10 +4464,27 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 	      /* If the symbol has been forced local, output a
 		 RELATIVE reloc, otherwise it will be handled in
 		 finish_dynamic_symbol.  Use IRELATIVE for local
-		 IFUNC symbols.  */
-	      if (bfd_link_pic (info)
-		  && !dynamic_symbol_p
-		  && !undef_weak_ref)
+		 IFUNC symbols.  A static IFUNC has no dynamic linker to
+		 resolve it, so it needs an IRELATIVE in .rela.iplt whether
+		 or not this is a shared object.
+
+		 A symbol with a PLT entry is handled entirely by
+		 finish_dynamic_symbol, which fills the GOT entry and emits
+		 the relocation for it into .rela.plt.  Since IFUNCs get a
+		 PLT entry even when they are not dynamic, testing
+		 dynamic_symbol_p alone is not enough to keep out of its
+		 way; sizing skips such symbols in
+		 elf64_alpha_size_rela_got_1, so emitting here would
+		 overrun .rela.got.  */
+	      if (elf64_alpha_static_ifunc_p (h, NULL, info))
+		elf64_alpha_emit_dynrel (info->output_bfd, info, sgot,
+					 elf_hash_table (info)->irelplt,
+					 gotent->got_offset, 0,
+					 R_ALPHA_IRELATIVE, value);
+	      else if (bfd_link_pic (info)
+		       && !dynamic_symbol_p
+		       && !undef_weak_ref
+		       && !(h != NULL && h->root.needs_plt))
 		{
 		  long r_type_dyn = R_ALPHA_RELATIVE;
 
@@ -4562,9 +4689,18 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 	      goto default_reloc;
 
 	    if (input_section->flags & SEC_ALLOC)
-	      elf64_alpha_emit_dynrel (info->output_bfd, info, input_section,
-				       srel, rel->r_offset, dynindx,
-				       dyntype, dynaddend);
+	      {
+		asection *s = srel;
+
+		/* A static IFUNC is resolved out of .rela.iplt.  */
+		if (dyntype == R_ALPHA_IRELATIVE
+		    && elf64_alpha_static_ifunc_p (h, NULL, info))
+		  s = elf_hash_table (info)->irelplt;
+
+		elf64_alpha_emit_dynrel (info->output_bfd, info, input_section,
+					 s, rel->r_offset, dynindx,
+					 dyntype, dynaddend);
+	      }
 	  }
 	  goto default_reloc;
 
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.