[PATCH 05/11] alpha: emit R_ALPHA_IRELATIVE in PIC links

Matt Turner mattst88@gmail.com
Tue Sep 1 02:33:12 GMT 2026


Alpha treats an IFUNC like an ordinary function, so a reference the
linker resolves itself gets the address of the resolver rather than of
the function the resolver selects. In a PIC link that is the
R_ALPHA_RELATIVE written for a GOT entry or a data word whose symbol
binds locally.

Emit R_ALPHA_IRELATIVE in its place. Space is already reserved for the
RELATIVE, in the same section, so nothing else has to change. A
preemptible IFUNC is left alone: the dynamic linker resolves it, and a
PLT entry and a JMP_SLOT are what it needs, as for an STT_FUNC.

Relaxation leaves references to an IFUNC alone, since a direct branch or
a gp-relative address would reach the resolver.

Non-PIC links are handled separately; they have no reserved slot to
reuse.
---
 bfd/elf64-alpha.c                             | 59 +++++++++++++++++--
 ld/testsuite/ld-alpha/ifunc-global-pie.d      |  9 +++
 ld/testsuite/ld-alpha/ifunc-global-relax.d    | 15 +++++
 ld/testsuite/ld-alpha/ifunc-global-shared.d   | 12 ++++
 ld/testsuite/ld-alpha/ifunc-global.s          | 24 ++++++++
 .../ld-alpha/ifunc-local-call-shared.d        |  8 +++
 ld/testsuite/ld-alpha/ifunc-local-call.s      | 17 ++++++
 ld/testsuite/ld-alpha/ifunc-local-relax.d     | 16 +++++
 ld/testsuite/ld-alpha/ifunc-local-shared.d    |  9 +++
 ld/testsuite/ld-alpha/ifunc-local.s           | 25 ++++++++
 10 files changed, 190 insertions(+), 4 deletions(-)
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global-pie.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global-relax.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global-shared.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-call-shared.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-call.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-relax.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-shared.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local.s

diff --git ./bfd/elf64-alpha.c ./bfd/elf64-alpha.c
index fef3c0127aa..484fe9589fb 100644
--- ./bfd/elf64-alpha.c
+++ ./bfd/elf64-alpha.c
@@ -1764,6 +1764,7 @@ static bool
 elf64_alpha_want_plt (struct alpha_elf_link_hash_entry *ah)
 {
   return ((ah->root.type == STT_FUNC
+	  || ah->root.type == STT_GNU_IFUNC
 	  || ah->root.root.type == bfd_link_hash_undefweak
 	  || ah->root.root.type == bfd_link_hash_undefined)
 	  && (ah->flags & ALPHA_ELF_LINK_HASH_LU_PLT) != 0
@@ -1782,6 +1783,31 @@ elf64_alpha_sort_relocs_p (asection *sec)
 }
 
 
+/* True if a relocation refers to an IFUNC defined in a regular object.  H
+   describes the symbol when it is global and SYM when it is local.  An
+   undefined symbol can carry the IFUNC type from a .type directive; it is
+   treated as an ordinary undefined function, as the generic code does when
+   it writes it out.  */
+
+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 && h->root.def_regular;
+
+  return sym != NULL && ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC;
+}
+
+/* True if a relocation of type R_TYPE against an IFUNC can end up as an
+   R_ALPHA_IRELATIVE.  */
+
+static bool
+elf64_alpha_ifunc_reloc_p (unsigned long r_type)
+{
+  return r_type == R_ALPHA_LITERAL || r_type == R_ALPHA_REFQUAD;
+}
+
 /* Handle dynamic relocations when doing an Alpha ELF link.  */
 
 static bool
@@ -3822,6 +3848,12 @@ elf64_alpha_relax_section (bfd *abfd, asection *sec,
 
 	  isym = isymbuf + r_symndx;
 
+	  /* A reference to an IFUNC has to keep going through the GOT: a
+	     direct branch or a gp-relative address would reach the resolver
+	     rather than the function it selects.  */
+	  if (elf64_alpha_ifunc_p (NULL, isym))
+	    continue;
+
 	  /* Given the symbol for a TLSLDM reloc is ignored, this also
 	     means forcing the symbol value to the tp base.  */
 	  if (r_type == R_ALPHA_TLSLDM)
@@ -3865,6 +3897,10 @@ elf64_alpha_relax_section (bfd *abfd, asection *sec,
 		 || h->root.root.type == bfd_link_hash_warning)
 	    h = (struct alpha_elf_link_hash_entry *)h->root.root.u.i.link;
 
+	  /* A reference to an IFUNC has to keep going through the GOT.  */
+	  if (elf64_alpha_ifunc_p (h, NULL))
+	    continue;
+
 	  /* If the symbol is undefined, we can't do anything with it.  */
 	  if (h->root.root.type == bfd_link_hash_undefined)
 	    continue;
@@ -4346,15 +4382,18 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 	      bfd_put_64 (info->output_bfd, value,
 			  alpha_got_slot (gotent, 0));
 
-	      /* If the symbol has been forced local, output a
-		 RELATIVE reloc, otherwise it will be handled in
-		 finish_dynamic_symbol.  */
+	      /* If the symbol has been forced local, output a RELATIVE
+		 reloc, otherwise it will be handled in finish_dynamic_symbol.
+		 An IFUNC gets an IRELATIVE instead.  */
 	      if (bfd_link_pic (info)
 		  && !dynamic_symbol_p
 		  && !undef_weak_ref)
 		elf64_alpha_emit_dynrel (info->output_bfd, info, sgot, srelgot,
 					 gotent->got_offset, 0,
-					 R_ALPHA_RELATIVE, value);
+					 (elf64_alpha_ifunc_p (h, sym)
+					  ? R_ALPHA_IRELATIVE
+					  : R_ALPHA_RELATIVE),
+					 value);
 	    }
 
 	  value = (sgot->output_section->vma
@@ -4505,6 +4544,18 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 		dynindx = 0;
 		dynaddend = value - dtp_base;
 	      }
+	    else if (elf64_alpha_ifunc_reloc_p (r_type)
+		     && bfd_link_pic (info)
+		     && elf64_alpha_ifunc_p (h, sym)
+		     && (input_section->flags & SEC_ALLOC))
+	      {
+		/* The RELATIVE this would otherwise get would store the
+		   address of the resolver.  Space for it is already
+		   reserved, so the IRELATIVE takes its place.  */
+		dynindx = 0;
+		dyntype = R_ALPHA_IRELATIVE;
+		dynaddend = value;
+	      }
 	    else if (bfd_link_pic (info)
 		     && r_symndx != STN_UNDEF
 		     && (input_section->flags & SEC_ALLOC)
diff --git ./ld/testsuite/ld-alpha/ifunc-global-pie.d ./ld/testsuite/ld-alpha/ifunc-global-pie.d
new file mode 100644
index 00000000000..c7b14f1030f
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-pie.d
@@ -0,0 +1,9 @@
+#source: ifunc-global.s
+#ld: -pie -melf64alpha
+#readelf: -Wr
+
+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-global-relax.d ./ld/testsuite/ld-alpha/ifunc-global-relax.d
new file mode 100644
index 00000000000..c42f1ab73b8
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-relax.d
@@ -0,0 +1,15 @@
+#source: ifunc-global.s
+#ld: -relax -melf64alpha
+#objdump: -dj.text
+
+# The call must keep going through the GOT; relaxing it into a bsr would
+# reach the resolver rather than the function it selects.
+.*: +file format elf64-alpha.*
+
+Disassembly of section \.text:
+#...
+[0-9a-f]+ <_start>:
+#...
+ +[0-9a-f]+:	[0-9a-f ]+	ldq	t12,-?[0-9]+\(gp\)
+ +[0-9a-f]+:	[0-9a-f ]+	jsr	ra,\(t12\),[0-9a-f]+ <_start\+0x10>
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-global-shared.d ./ld/testsuite/ld-alpha/ifunc-global-shared.d
new file mode 100644
index 00000000000..55508f0b0b7
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-shared.d
@@ -0,0 +1,12 @@
+#source: ifunc-global.s
+#ld: -shared -melf64alpha
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_REFQUAD .*global_ifunc \+ 0
+#...
+Relocation section '\.rela\.plt' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_JMP_SLOT .*global_ifunc \+ 0
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-global.s ./ld/testsuite/ld-alpha/ifunc-global.s
new file mode 100644
index 00000000000..f94806b10b1
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global.s
@@ -0,0 +1,24 @@
+	.text
+
+	# A globally visible IFUNC, both called and referenced, so that both
+	# its GOT entry and the data word holding its address need an
+	# IRELATIVE.
+	.globl	global_ifunc
+	.type	global_ifunc, @gnu_indirect_function
+global_ifunc:
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldq	$27, global_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	global_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..01fb33e17bb
--- /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
+
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[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..165749221bd
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-call.s
@@ -0,0 +1,17 @@
+	.text
+
+	# An IFUNC with local binding, called but not otherwise referenced,
+	# so that only its GOT entry needs an IRELATIVE.
+	.type	local_ifunc, @gnu_indirect_function
+local_ifunc:
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldq	$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-relax.d ./ld/testsuite/ld-alpha/ifunc-local-relax.d
new file mode 100644
index 00000000000..88b9029c64c
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-relax.d
@@ -0,0 +1,16 @@
+#source: ifunc-local.s
+#ld: -relax -melf64alpha
+#objdump: -dj.text
+
+# As for a global IFUNC, the call must keep going through the GOT.  A local
+# symbol reaches the check in elf64_alpha_relax_section by its symbol table
+# entry rather than by a hash table entry.
+.*: +file format elf64-alpha.*
+
+Disassembly of section \.text:
+#...
+[0-9a-f]+ <_start>:
+#...
+ +[0-9a-f]+:	[0-9a-f ]+	ldq	t12,-?[0-9]+\(gp\)
+ +[0-9a-f]+:	[0-9a-f ]+	jsr	ra,\(t12\),[0-9a-f]+ <_start\+0x10>
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-local-shared.d ./ld/testsuite/ld-alpha/ifunc-local-shared.d
new file mode 100644
index 00000000000..c21226bf729
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-shared.d
@@ -0,0 +1,9 @@
+#source: ifunc-local.s
+#ld: -shared -melf64alpha
+#readelf: -Wr
+
+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-local.s ./ld/testsuite/ld-alpha/ifunc-local.s
new file mode 100644
index 00000000000..cba6f3f2e98
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local.s
@@ -0,0 +1,25 @@
+	.text
+
+	# An IFUNC with local binding: no .globl, so it has no entry in the
+	# linker's hash table.  It is both called and referenced, so that
+	# both its GOT entry and the data word holding its address need an
+	# IRELATIVE.  The data word alone is the shape of glibc's configure
+	# probe for linker IFUNC support.
+	.type	local_ifunc, @gnu_indirect_function
+local_ifunc:
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldq	$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
-- 
2.54.0



More information about the Binutils mailing list