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

Matt Turner mattst88@gmail.com
Sun Aug 9 19:23:37 GMT 2026


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



More information about the Binutils mailing list