[PATCH v2 4/4] alpha: handle IFUNC symbols with local binding

Matt Turner mattst88@gmail.com
Sun Aug 9 21:41:21 GMT 2026


The IFUNC support tested the symbol's hash table entry to decide whether a
reference needed an R_ALPHA_IRELATIVE relocation.  A symbol with local
binding has no hash table entry, so every one of those tests failed and a
local IFUNC was treated as an ordinary symbol.

The result was silently wrong rather than diagnosed.  Taking the address of
a local IFUNC gave R_ALPHA_RELATIVE holding the resolver's address in a
position-independent link, and in a non-PIC one gave no relocation at all,
the resolver's address simply written into the data.  A call to one went
through a GOT entry that nothing reserved .rela.iplt space for in a static
link, and that got an R_ALPHA_RELATIVE in a shared one.  Either way the
resolver never ran, and the call entered the resolver instead of the
implementation it selects.

Look at the symbol table when there is no hash table entry.  check_relocs
fetches the local symbol so that it can see STT_GNU_IFUNC and reserve the
.rela.iplt space a static link needs, sizing walks the GOT lists for local
symbols that are IFUNCs, since there is no hash table entry to traverse,
and relocate_section emits IRELATIVE for the local cases as it already did
for the global ones.

glibc's configure test for linker IFUNC support declares its symbol without
.globl, so this is also what made --enable-multi-arch report that the
assembler and linker do not support STT_GNU_IFUNC.

The ld-ifunc tests do not reach any of this: the ones that cover local
binding are all written for x86.  Add alpha tests for a data reference to
a local IFUNC and for a call to one, in a shared object, in a static link
and in a dynamic executable.  They use only the assembler and the linker,
so they run in a cross build, and each fails without this change.
---
v2: Rebased on 2/4.  Added ld-alpha/ifunc-dynamic, covering a local
    IFUNC referenced and called from a dynamic executable.

 bfd/elf64-alpha.c                             | 93 ++++++++++++++++---
 ld/testsuite/ld-alpha/ifunc-dynamic.d         | 10 ++
 ld/testsuite/ld-alpha/ifunc-dynamic.s         | 29 ++++++
 .../ld-alpha/ifunc-local-call-shared.d        |  8 ++
 ld/testsuite/ld-alpha/ifunc-local-call.d      |  8 ++
 ld/testsuite/ld-alpha/ifunc-local-call.s      | 23 +++++
 ld/testsuite/ld-alpha/ifunc-local.d           |  8 ++
 ld/testsuite/ld-alpha/ifunc-local.s           | 18 ++++
 8 files changed, 185 insertions(+), 12 deletions(-)
 create mode 100644 ld/testsuite/ld-alpha/ifunc-dynamic.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-dynamic.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-call-shared.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-call.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-call.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local.s

diff --git ./bfd/elf64-alpha.c ./bfd/elf64-alpha.c
index 6f55facc240..b75dfa2d6b5 100644
--- ./bfd/elf64-alpha.c
+++ ./bfd/elf64-alpha.c
@@ -1869,6 +1869,62 @@ elf64_alpha_size_irelative_got (struct alpha_elf_link_hash_entry *h,
   return true;
 }
 
+/* The same for local IFUNCs, which have no hash table entry for
+   elf64_alpha_size_irelative_got to be called on.  */
+
+static bool
+elf64_alpha_size_irelative_local_got (struct bfd_link_info *info)
+{
+  struct alpha_elf_link_hash_table *htab = alpha_elf_hash_table (info);
+  unsigned int entries = 0;
+  bfd *i;
+
+  for (i = htab->got_list; i != NULL; i = alpha_elf_tdata (i)->got_link_next)
+    {
+      bfd *j;
+
+      for (j = i; j != NULL; j = alpha_elf_tdata (j)->in_got_link_next)
+	{
+	  struct alpha_elf_got_entry **local_got_entries, *gotent;
+	  int k, n;
+
+	  local_got_entries = alpha_elf_tdata (j)->local_got_entries;
+	  if (local_got_entries == NULL)
+	    continue;
+
+	  for (k = 0, n = elf_symtab_hdr (j).sh_info; k < n; ++k)
+	    {
+	      Elf_Internal_Sym *isym;
+	      unsigned int this_entries = 0;
+
+	      for (gotent = local_got_entries[k]; gotent != NULL;
+		   gotent = gotent->next)
+		if (gotent->use_count > 0
+		    && gotent->reloc_type == R_ALPHA_LITERAL)
+		  this_entries++;
+
+	      if (this_entries == 0)
+		continue;
+
+	      isym = bfd_sym_from_r_symndx (&elf_hash_table (info)->sym_cache,
+					    j, k);
+	      if (isym == NULL)
+		return false;
+
+	      if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+		entries += this_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
@@ -1905,14 +1961,25 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
       unsigned long r_symndx, r_type;
       struct alpha_elf_link_hash_entry *h;
+      Elf_Internal_Sym *isym;
       unsigned int gotent_flags;
       bool maybe_dynamic;
       unsigned int need;
       bfd_vma addend;
 
       r_symndx = ELF64_R_SYM (rel->r_info);
+      isym = NULL;
       if (r_symndx < symtab_hdr->sh_info)
-	h = NULL;
+	{
+	  h = NULL;
+
+	  /* A local IFUNC needs the same treatment as a global one, and
+	     only the symbol table says that it is one.  */
+	  isym = bfd_sym_from_r_symndx (&elf_hash_table (info)->sym_cache,
+					abfd, r_symndx);
+	  if (isym == NULL)
+	    return false;
+	}
       else
 	{
 	  h = sym_hashes[r_symndx - symtab_hdr->sh_info];
@@ -1942,9 +2009,8 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	 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
+      if (elf64_alpha_ifunc_p (h, isym)
+	  && (h == NULL || h->root.def_regular)
 	  && !bfd_link_pic (info)
 	  && !maybe_dynamic
 	  && (sec->flags & SEC_ALLOC)
@@ -1985,8 +2051,7 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
 	case R_ALPHA_REFLONG:
 	case R_ALPHA_REFQUAD:
-	  if (h != NULL
-	      && h->root.type == STT_GNU_IFUNC
+	  if (elf64_alpha_ifunc_p (h, isym)
 	      && !bfd_link_pic (info)
 	      && !maybe_dynamic
 	      && (sec->flags & SEC_ALLOC))
@@ -2974,7 +3039,12 @@ elf64_alpha_late_size_sections (struct bfd_link_info *info)
      for the GOT entries that R_ALPHA_IRELATIVE fills in at startup.  This
      is the only relocation a link with no dynamic sections can need.  */
   if (elf64_alpha_self_resolved_ifuncs_p (info))
-    alpha_elf_link_hash_traverse (htab, elf64_alpha_size_irelative_got, info);
+    {
+      alpha_elf_link_hash_traverse (htab, elf64_alpha_size_irelative_got,
+				    info);
+      if (!elf64_alpha_size_irelative_local_got (info))
+	return false;
+    }
 
   /* The check_relocs and adjust_dynamic_symbol entry points have
      determined the sizes of the various dynamic sections.  Allocate
@@ -4510,7 +4580,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 		 elf64_alpha_size_rela_got_1 and in
 		 elf64_alpha_size_irelative_got, so emitting here would
 		 overrun .rela.got or .rela.iplt.  */
-	      if (elf64_alpha_static_ifunc_p (h, NULL, info)
+	      if (elf64_alpha_static_ifunc_p (h, sym, info)
 		  && !(h != NULL && h->root.needs_plt))
 		elf64_alpha_emit_dynrel (info->output_bfd, info, sgot,
 					 elf_hash_table (info)->irelplt,
@@ -4523,7 +4593,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 		{
 		  long r_type_dyn = R_ALPHA_RELATIVE;
 
-		  if (h != NULL && h->root.type == STT_GNU_IFUNC)
+		  if (elf64_alpha_ifunc_p (h, sym))
 		    r_type_dyn = R_ALPHA_IRELATIVE;
 
 		  elf64_alpha_emit_dynrel (info->output_bfd, info, sgot, srelgot,
@@ -4689,8 +4759,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 		dynindx = 0;
 		dynaddend = value - dtp_base;
 	      }
-	    else if (h != NULL
-		     && h->root.type == STT_GNU_IFUNC
+	    else if (elf64_alpha_ifunc_p (h, sym)
 		     && (input_section->flags & SEC_ALLOC))
 	      {
 		dynindx = 0;
@@ -4729,7 +4798,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 
 		/* A static IFUNC is resolved out of .rela.iplt.  */
 		if (dyntype == R_ALPHA_IRELATIVE
-		    && elf64_alpha_static_ifunc_p (h, NULL, info))
+		    && elf64_alpha_static_ifunc_p (h, sym, info))
 		  s = elf_hash_table (info)->irelplt;
 
 		elf64_alpha_emit_dynrel (info->output_bfd, info, input_section,
diff --git ./ld/testsuite/ld-alpha/ifunc-dynamic.d ./ld/testsuite/ld-alpha/ifunc-dynamic.d
new file mode 100644
index 00000000000..d013fc280d6
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-dynamic.d
@@ -0,0 +1,10 @@
+#source: ifunc-dynamic.s
+#ld: -melf64alpha tmpdir/libtlslib.so
+#readelf: -Wr
+#target: alpha*-*-*
+
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-dynamic.s ./ld/testsuite/ld-alpha/ifunc-dynamic.s
new file mode 100644
index 00000000000..5c0d3013b6c
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-dynamic.s
@@ -0,0 +1,29 @@
+	.text
+
+	.globl	impl
+	.type	impl, @function
+impl:
+	ret
+
+	# An IFUNC with local binding, both called and referenced, so that
+	# it needs an IRELATIVE for its GOT entry and another for the data
+	# word holding its address.
+	.type	local_ifunc, %gnu_indirect_function
+local_ifunc:
+	lda	$0, impl
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	lda	$27, local_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	local_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-local-call-shared.d ./ld/testsuite/ld-alpha/ifunc-local-call-shared.d
new file mode 100644
index 00000000000..7ec2d575fa1
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-call-shared.d
@@ -0,0 +1,8 @@
+#source: ifunc-local-call.s
+#ld: -shared -melf64alpha
+#readelf: -Wr
+#target: alpha*-*-*
+
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-local-call.d ./ld/testsuite/ld-alpha/ifunc-local-call.d
new file mode 100644
index 00000000000..2746ba4f93c
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-call.d
@@ -0,0 +1,8 @@
+#source: ifunc-local-call.s
+#ld: -melf64alpha
+#readelf: -Wr
+#target: alpha*-*-*
+
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-local-call.s ./ld/testsuite/ld-alpha/ifunc-local-call.s
new file mode 100644
index 00000000000..ce9a109dfd7
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-call.s
@@ -0,0 +1,23 @@
+	.text
+
+	.globl	impl
+	.type	impl, @function
+impl:
+	ret
+
+	# An IFUNC with local binding, called rather than merely referenced,
+	# so the call goes through a GOT entry.
+	.type	local_ifunc, %gnu_indirect_function
+local_ifunc:
+	lda	$0, impl
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	lda	$27, local_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	_start
diff --git ./ld/testsuite/ld-alpha/ifunc-local.d ./ld/testsuite/ld-alpha/ifunc-local.d
new file mode 100644
index 00000000000..c3f604dc40a
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local.d
@@ -0,0 +1,8 @@
+#source: ifunc-local.s
+#ld: -shared -melf64alpha
+#readelf: -Wr
+#target: alpha*-*-*
+
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-local.s ./ld/testsuite/ld-alpha/ifunc-local.s
new file mode 100644
index 00000000000..9b610ad77b4
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local.s
@@ -0,0 +1,18 @@
+	.text
+
+	.globl	impl
+	.type	impl, @function
+impl:
+	ret
+
+	# An IFUNC with local binding: no .globl, so it has no entry in the
+	# linker's hash table.
+	.type	local_ifunc, %gnu_indirect_function
+local_ifunc:
+	lda	$0, impl
+	ret
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	local_ifunc
-- 
2.54.0



More information about the Binutils mailing list