[PATCH 2/4] alpha: support IFUNC in static links
Matt Turner <[email protected]>
| Newsgroups | gmane.comp.gnu.binutils |
|---|---|
| Message-ID | <[email protected]> |
A static link that referenced an IFUNC died with
BFD assertion fail bfd/elf64-alpha.c:4027
There is no dynamic linker in that case, so nothing resolves an IFUNC at
load time. The C library instead applies the R_ALPHA_IRELATIVE relocations
between __rela_iplt_start and __rela_iplt_end at startup, and the linker
has to put them in .rela.iplt and account for the space.
Create that section when a reference to such an IFUNC is seen, size it from
the GOT entries the symbol keeps, and emit the relocations into it.
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.
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.
---
bfd/elf64-alpha.c | 154 +++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 145 insertions(+), 9 deletions(-)
diff --git ./bfd/elf64-alpha.c ./bfd/elf64-alpha.c
index 6925bdd0a4d..4e0c2d8901e 100644
--- ./bfd/elf64-alpha.c
+++ ./bfd/elf64-alpha.c
@@ -1768,6 +1768,89 @@ 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 the IFUNC is one this link resolves itself, i.e. one the dynamic
+ linker will never see. Its references are resolved at startup by
+ R_ALPHA_IRELATIVE relocations in .rela.iplt, which the C library applies
+ between __rela_iplt_start and __rela_iplt_end. 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)
+ && !elf_hash_table (info)->dynamic_sections_created);
+}
+
+/* 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 static 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;
+
+ 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 +1920,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 +1967,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 +2950,10 @@ 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. */
+ else
+ /* We are not dynamic, so the only relocations we can need are the
+ IRELATIVEs resolving our own IFUNCs at startup. */
+ 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 +4464,27 @@ 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, so emitting here would
+ overrun .rela.got. */
+ if (elf64_alpha_static_ifunc_p (h, NULL, info))
+ 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 +4689,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;
--
2.54.0