[PATCH v2 2/4] alpha: resolve IFUNCs this link resolves itself out of .rela.iplt
Matt Turner <[email protected]>
| Newsgroups | gmane.comp.gnu.binutils |
|---|---|
| Message-ID | <[email protected]> |
An IFUNC that the dynamic linker will not resolve has to be resolved by
this link instead, out of the R_ALPHA_IRELATIVE relocations in .rela.iplt.
That is the case in a static link, where there is no dynamic linker at
all, and in one that is not position-independent, where an IFUNC defined
in the objects being linked cannot be preempted and so never becomes a
dynamic symbol. Neither worked.
A static link that referenced an IFUNC died with
BFD assertion fail bfd/elf64-alpha.c:4027
and a non-PIE executable that referenced an IFUNC defined in one of its
own objects died with
BFD assertion fail bfd/elf64-alpha.c:4027
collect2: fatal error: ld terminated with signal 11 [Segmentation fault]
because the sizing code reserves nothing for a symbol the dynamic linker
will not see, so relocate_section was handed the dynamic reloc section for
the input section, which is a null pointer since nothing ever created one.
Create .rela.iplt when a reference to such an IFUNC is seen, size it from
the GOT entries the symbol keeps, and emit the relocations into it. The
linker script places the section with the other dynamic relocations, so
where there is a dynamic linker it applies them along with the rest of
.rela.dyn, and where there is not the C library applies them at startup
between __rela_iplt_start and __rela_iplt_end.
A globally visible IFUNC in a shared library was a second way to get this
wrong, overrunning .rela.got and corrupting the heap:
ld -shared shared_ifunc.o
BFD assertion fail bfd/elf64-alpha.c:4136
free(): invalid pointer
elf64_alpha_size_rela_got_1 reserves nothing for a symbol with a PLT entry,
because all of its GOT relocations are supposed to come from
finish_dynamic_symbol and land in .rela.plt. That held as long as needs_plt
implied the symbol was dynamic, which is what adjust_dynamic_symbol used to
guarantee. Now that IFUNCs get a PLT entry even when they are not dynamic,
a hidden IFUNC can have a PLT entry and still be non-dynamic, so the
R_ALPHA_LITERAL case in relocate_section emitted an unaccounted IRELATIVE
into .rela.got. In ld-ifunc/lib.c the hidden alias __GI_library_func2 is
exactly that, and its relocation took the slot sized for global's GLOB_DAT.
Keep out of finish_dynamic_symbol's way by testing needs_plt as well, which
restores the invariant sizing relies on, in the .rela.iplt sizing for the
same reason.
The predicates take the local symbol as well as the hash table entry, since
a later change needs to ask the same questions about a symbol that has no
hash table entry.
Add a test for a dynamic executable that references and calls an IFUNC,
which fails without this change.
---
v2: Also handle a non-PIE dynamic executable, which crashed the linker
the same way a static link did: relocate_section asked whether the
link had dynamic sections rather than whether it resolves the IFUNC
itself, and so disagreed with what check_relocs reserved. Size the
.rela.iplt GOT entries for that case too, and keep out of
finish_dynamic_symbol's way for a symbol with a PLT entry. Retitled
accordingly, and added ld-alpha/ifunc-global-dynamic.
bfd/elf64-alpha.c | 177 ++++++++++++++++++-
ld/testsuite/ld-alpha/ifunc-global-dynamic.d | 13 ++
ld/testsuite/ld-alpha/ifunc-global-dynamic.s | 30 ++++
3 files changed, 211 insertions(+), 9 deletions(-)
create mode 100644 ld/testsuite/ld-alpha/ifunc-global-dynamic.d
create mode 100644 ld/testsuite/ld-alpha/ifunc-global-dynamic.s
diff --git ./bfd/elf64-alpha.c ./bfd/elf64-alpha.c
index 6925bdd0a4d..8ed997900c0 100644
--- ./bfd/elf64-alpha.c
+++ ./bfd/elf64-alpha.c
@@ -1768,6 +1768,107 @@ elf64_alpha_sort_relocs_p (asection *sec)
}
+/* True if a relocation refers to an IFUNC. H describes the symbol when it
+ is global and SYM when it is local; a local symbol has no hash table
+ entry, so testing H alone misses it. */
+
+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;
+
+ return sym != NULL && ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC;
+}
+
+/* True if this link resolves its own IFUNCs rather than leaving them to the
+ dynamic linker: either there is no dynamic linker at all, or the link is
+ not position-independent, in which case an IFUNC defined here cannot be
+ preempted and so never becomes a dynamic symbol. */
+
+static bool
+elf64_alpha_self_resolved_ifuncs_p (struct bfd_link_info *info)
+{
+ return (!bfd_link_pic (info)
+ || !elf_hash_table (info)->dynamic_sections_created);
+}
+
+/* True if the IFUNC is one this link resolves itself. Its references are
+ resolved out of .rela.iplt, by the C library at startup between
+ __rela_iplt_start and __rela_iplt_end when there is no dynamic linker,
+ and by the dynamic linker along with the rest of .rela.dyn when there is
+ one. A local symbol is always defined in this object, so only a global
+ one needs def_regular. */
+
+static bool
+elf64_alpha_static_ifunc_p (struct alpha_elf_link_hash_entry *h,
+ Elf_Internal_Sym *sym,
+ struct bfd_link_info *info)
+{
+ return (elf64_alpha_ifunc_p (h, sym)
+ && (h == NULL || h->root.def_regular)
+ && elf64_alpha_self_resolved_ifuncs_p (info));
+}
+
+/* Reserve space for COUNT R_ALPHA_IRELATIVE relocations in .rela.iplt,
+ creating that section if this is the first one. */
+
+static bool
+elf64_alpha_reserve_irelative (bfd *abfd, struct bfd_link_info *info,
+ unsigned int count)
+{
+ struct elf_link_hash_table *htab = elf_hash_table (info);
+ asection *s = htab->irelplt;
+
+ if (s == NULL)
+ {
+ const struct elf_backend_data *bed = get_elf_backend_data (abfd);
+
+ if (htab->dynobj == NULL)
+ htab->dynobj = abfd;
+
+ s = bfd_make_section_anyway_with_flags (htab->dynobj, ".rela.iplt",
+ (bed->dynamic_sec_flags
+ | SEC_READONLY));
+ if (s == NULL || !bfd_set_section_alignment (s, 3))
+ return false;
+ htab->irelplt = s;
+ }
+
+ s->size += count * sizeof (Elf64_External_Rela);
+ return true;
+}
+
+/* Reserve .rela.iplt space for the GOT entries of a self-resolved IFUNC. */
+
+static bool
+elf64_alpha_size_irelative_got (struct alpha_elf_link_hash_entry *h,
+ struct bfd_link_info *info)
+{
+ struct alpha_elf_got_entry *gotent;
+ unsigned int entries = 0;
+
+ if (!elf64_alpha_static_ifunc_p (h, NULL, info))
+ return true;
+
+ /* A symbol with a PLT entry has all of the relocations for its GOT
+ entries emitted by finish_dynamic_symbol, into .rela.plt. */
+ if (h->root.needs_plt)
+ return true;
+
+ for (gotent = h->got_entries; gotent != NULL; gotent = gotent->next)
+ if (gotent->use_count > 0 && gotent->reloc_type == R_ALPHA_LITERAL)
+ 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
@@ -1837,6 +1938,19 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
|| h->root.root.type == bfd_link_hash_defweak))
maybe_dynamic = true;
+ /* A reference to an IFUNC the dynamic linker will never see has to be
+ 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
+ && !bfd_link_pic (info)
+ && !maybe_dynamic
+ && (sec->flags & SEC_ALLOC)
+ && !elf64_alpha_reserve_irelative (abfd, info, 0))
+ return false;
+
need = 0;
gotent_flags = 0;
r_type = ELF64_R_TYPE (rel->r_info);
@@ -1871,7 +1985,18 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
case R_ALPHA_REFLONG:
case R_ALPHA_REFQUAD:
- if (bfd_link_pic (info) || maybe_dynamic)
+ if (h != NULL
+ && h->root.type == STT_GNU_IFUNC
+ && !bfd_link_pic (info)
+ && !maybe_dynamic
+ && (sec->flags & SEC_ALLOC))
+ {
+ /* Resolved at startup through .rela.iplt rather than by the
+ dynamic linker. */
+ if (!elf64_alpha_reserve_irelative (abfd, info, 1))
+ return false;
+ }
+ else if (bfd_link_pic (info) || maybe_dynamic)
need = NEED_DYNREL;
break;
@@ -2843,7 +2968,13 @@ elf64_alpha_late_size_sections (struct bfd_link_info *info)
elf64_alpha_size_rela_got_section (info);
elf64_alpha_size_plt_section (info);
}
- /* else we're not dynamic and by definition we don't need such things. */
+
+ /* The sizing above reserves nothing for a symbol the dynamic linker will
+ not see, so an IFUNC this link resolves itself needs .rela.iplt space
+ 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);
/* The check_relocs and adjust_dynamic_symbol entry points have
determined the sizes of the various dynamic sections. Allocate
@@ -4354,10 +4485,29 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
/* If the symbol has been forced local, output a
RELATIVE reloc, otherwise it will be handled in
finish_dynamic_symbol. Use IRELATIVE for local
- IFUNC symbols. */
- if (bfd_link_pic (info)
- && !dynamic_symbol_p
- && !undef_weak_ref)
+ IFUNC symbols. A static IFUNC has no dynamic linker to
+ resolve it, so it needs an IRELATIVE in .rela.iplt whether
+ or not this is a shared object.
+
+ A symbol with a PLT entry is handled entirely by
+ finish_dynamic_symbol, which fills the GOT entry and emits
+ the relocation for it into .rela.plt. Since IFUNCs get a
+ PLT entry even when they are not dynamic, testing
+ dynamic_symbol_p alone is not enough to keep out of its
+ way; sizing skips such symbols in
+ 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)
+ && !(h != NULL && h->root.needs_plt))
+ elf64_alpha_emit_dynrel (info->output_bfd, info, sgot,
+ elf_hash_table (info)->irelplt,
+ gotent->got_offset, 0,
+ R_ALPHA_IRELATIVE, value);
+ else if (bfd_link_pic (info)
+ && !dynamic_symbol_p
+ && !undef_weak_ref
+ && !(h != NULL && h->root.needs_plt))
{
long r_type_dyn = R_ALPHA_RELATIVE;
@@ -4562,9 +4712,18 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
goto default_reloc;
if (input_section->flags & SEC_ALLOC)
- elf64_alpha_emit_dynrel (info->output_bfd, info, input_section,
- srel, rel->r_offset, dynindx,
- dyntype, dynaddend);
+ {
+ asection *s = srel;
+
+ /* A static IFUNC is resolved out of .rela.iplt. */
+ if (dyntype == R_ALPHA_IRELATIVE
+ && elf64_alpha_static_ifunc_p (h, NULL, info))
+ s = elf_hash_table (info)->irelplt;
+
+ elf64_alpha_emit_dynrel (info->output_bfd, info, input_section,
+ s, rel->r_offset, dynindx,
+ dyntype, dynaddend);
+ }
}
goto default_reloc;
diff --git ./ld/testsuite/ld-alpha/ifunc-global-dynamic.d ./ld/testsuite/ld-alpha/ifunc-global-dynamic.d
new file mode 100644
index 00000000000..c255cfa8e86
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-dynamic.d
@@ -0,0 +1,13 @@
+#source: ifunc-global-dynamic.s
+#ld: -melf64alpha tmpdir/libtlslib.so
+#readelf: -Wr
+#target: alpha*-*-*
+
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+
+Relocation section '\.rela\.plt' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-global-dynamic.s ./ld/testsuite/ld-alpha/ifunc-global-dynamic.s
new file mode 100644
index 00000000000..96a60fb895f
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-dynamic.s
@@ -0,0 +1,30 @@
+ .text
+
+ .globl impl
+ .type impl, @function
+impl:
+ ret
+
+ # A globally visible IFUNC, both called and referenced. It gets a
+ # PLT entry, so the IRELATIVE for its GOT entry comes from .rela.plt
+ # and only the data word needs one of its own.
+ .globl global_ifunc
+ .type global_ifunc, %gnu_indirect_function
+global_ifunc:
+ lda $0, impl
+ ret
+
+ .globl _start
+ .ent _start
+_start:
+ ldgp $29, 0($27)
+ lda $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
--
2.54.0