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

Matt Turner <[email protected]>
Newsgroups gmane.comp.gnu.binutils
Message-ID <[email protected]>
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
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.