[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