@@ -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)
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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