diff options
-rw-r--r-- | bfd/elfnn-aarch64.c | 429 | ||||
-rw-r--r-- | bfd/elfxx-aarch64.h | 11 | ||||
-rw-r--r-- | ld/emulparams/aarch64elf.sh | 2 | ||||
-rw-r--r-- | ld/emulparams/aarch64fbsd.sh | 2 | ||||
-rw-r--r-- | ld/emulparams/aarch64linux.sh | 2 |
5 files changed, 445 insertions, 1 deletions
diff --git a/bfd/elfnn-aarch64.c b/bfd/elfnn-aarch64.c index 55871db..81f1a64 100644 --- a/bfd/elfnn-aarch64.c +++ b/bfd/elfnn-aarch64.c @@ -2706,6 +2706,19 @@ struct elf_aarch64_link_hash_table /* Used by local STT_GNU_IFUNC symbols. */ htab_t loc_hash_table; void * loc_hash_memory; + + /* Array of relative relocs to be emitted in DT_RELR format. */ + bfd_size_type relr_alloc; + bfd_size_type relr_count; + struct relr_entry + { + asection *sec; + bfd_vma off; + } *relr; + /* Sorted output addresses of above relative relocs. */ + bfd_vma *relr_sorted; + /* Layout recomputation count. */ + bfd_size_type relr_layout_iter; }; /* Create an entry in an AArch64 ELF linker hash table. */ @@ -5971,6 +5984,18 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto, || !(bfd_link_pie (info) || SYMBOLIC_BIND (info, h)) || !h->def_regular)) outrel.r_info = ELFNN_R_INFO (h->dynindx, r_type); + else if (info->enable_dt_relr + && input_section->alignment_power != 0 + && rel->r_offset % 2 == 0) + { + /* Don't emit a relative relocation that is packed, only + apply the addend. */ + if (globals->no_apply_dynamic_relocs) + return bfd_reloc_ok; + return _bfd_final_link_relocate (howto, input_bfd, input_section, + contents, rel->r_offset, value, + signed_addend); + } else { int symbol; @@ -6237,7 +6262,8 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto, addend, weak_undef_p); } - if (relative_reloc) + /* Emit relative relocations, but not if they are packed (DT_RELR). */ + if (relative_reloc && !info->enable_dt_relr) { asection *s; Elf_Internal_Rela outrel; @@ -9169,6 +9195,363 @@ elfNN_aarch64_allocate_local_ifunc_dynrelocs (void **slot, void *inf) return elfNN_aarch64_allocate_ifunc_dynrelocs (h, inf); } +/* Record a relative relocation that will be emitted packed (DT_RELR). + Called after relocation sections are sized, so undo the size accounting + for this relocation. */ + +static bool +record_relr (struct elf_aarch64_link_hash_table *htab, asection *sec, + bfd_vma off, asection *sreloc) +{ + /* Undo the relocation section size accounting. */ + BFD_ASSERT (sreloc->size >= RELOC_SIZE (htab)); + sreloc->size -= RELOC_SIZE (htab); + /* The packing format uses the last bit of the address so that + must be aligned. We don't pack relocations that may not be + aligned even though the final output address could end up + aligned, to avoid complex sizing logic for a rare case. */ + BFD_ASSERT (off % 2 == 0 && sec->alignment_power > 0); + if (htab->relr_count >= htab->relr_alloc) + { + if (htab->relr_alloc == 0) + htab->relr_alloc = 4096; + else + htab->relr_alloc *= 2; + htab->relr = bfd_realloc (htab->relr, + htab->relr_alloc * sizeof (*htab->relr)); + if (htab->relr == NULL) + return false; + } + htab->relr[htab->relr_count].sec = sec; + htab->relr[htab->relr_count].off = off; + htab->relr_count++; + return true; +} + +/* Follow elfNN_aarch64_allocate_dynrelocs, but only record relative + relocations against the GOT and undo their previous size accounting. */ + +static bool +record_relr_dyn_got_relocs (struct elf_link_hash_entry *h, void *inf) +{ + + if (h->root.type == bfd_link_hash_indirect) + return true; + if (h->root.type == bfd_link_hash_warning) + h = (struct elf_link_hash_entry *) h->root.u.i.link; + if (h->type == STT_GNU_IFUNC && h->def_regular) + return true; + if (h->got.refcount <= 0) + return true; + if (elf_aarch64_hash_entry (h)->got_type != GOT_NORMAL) + return true; + + struct bfd_link_info *info = (struct bfd_link_info *) inf; + struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table (info); + + if ((ELF_ST_VISIBILITY (h->other) == STV_DEFAULT + || h->root.type != bfd_link_hash_undefweak) + && bfd_link_pic (info) + /* Undefined weak symbol in static PIE resolves to 0 without + any dynamic relocations. */ + && !UNDEFWEAK_NO_DYNAMIC_RELOC (info, h)) + { + bool relative_reloc = SYMBOL_REFERENCES_LOCAL (info, h) + && !bfd_is_abs_symbol (&h->root); + if (relative_reloc) + if (!record_relr (htab, htab->root.sgot, h->got.offset, + htab->root.srelgot)) + return false; + } + return true; +} + +/* Record packed relative relocs against the GOT for local symbols. + Undo the size accounting of elfNN_aarch64_late_size_sections. */ + +static bool +record_relr_local_got_relocs (bfd *input_bfd, struct bfd_link_info *info) +{ + struct elf_aarch64_local_symbol *locals; + Elf_Internal_Shdr *symtab_hdr; + struct elf_aarch64_link_hash_table *htab; + + if (!bfd_link_pic (info)) + return true; + + locals = elf_aarch64_locals (input_bfd); + if (locals == NULL) + return true; + + symtab_hdr = &elf_symtab_hdr (input_bfd); + htab = elf_aarch64_hash_table (info); + for (unsigned int i = 0; i < symtab_hdr->sh_info; i++) + { + bfd_vma off = locals[i].got_offset; + if (locals[i].got_refcount <= 0) + continue; + if ((locals[i].got_type & GOT_NORMAL) == 0) + continue; + + /* FIXME: If the local symbol is in SHN_ABS then emitting + a relative relocation is not correct, but it seems to + be wrong in elfNN_aarch64_final_link_relocate too. */ + if (!record_relr (htab, htab->root.sgot, off, htab->root.srelgot)) + return false; + } + return true; +} + +/* Follows the logic of elfNN_aarch64_relocate_section to decide which + relocations will become relative and possible to pack. Ignore + relocations against the GOT, those are handled separately per-symbol. + Undo the size accounting of the packed relocations and record them + so the relr section can be sized later. */ + +static bool +record_relr_non_got_relocs (bfd *input_bfd, struct bfd_link_info *info, + asection *sec) +{ + const Elf_Internal_Rela *relocs; + const Elf_Internal_Rela *rel; + const Elf_Internal_Rela *rel_end; + asection *sreloc; + struct elf_aarch64_link_hash_table *htab; + Elf_Internal_Shdr *symtab_hdr; + struct elf_link_hash_entry **sym_hashes; + + if (sec->reloc_count == 0) + return true; + if ((sec->flags & (SEC_RELOC | SEC_ALLOC | SEC_DEBUGGING)) + != (SEC_RELOC | SEC_ALLOC)) + return true; + if (sec->alignment_power == 0) + return true; + sreloc = elf_section_data (sec)->sreloc; + if (sreloc == NULL) + return true; + htab = elf_aarch64_hash_table (info); + symtab_hdr = &elf_symtab_hdr (input_bfd); + sym_hashes = elf_sym_hashes (input_bfd); + relocs = _bfd_elf_link_info_read_relocs (input_bfd, info, sec, NULL, NULL, + info->keep_memory); + BFD_ASSERT (relocs != NULL); + rel_end = relocs + sec->reloc_count; + for (rel = relocs; rel < rel_end; rel++) + { + unsigned int r_symndx = ELFNN_R_SYM (rel->r_info); + unsigned int r_type = ELFNN_R_TYPE (rel->r_info); + + bfd_reloc_code_real_type bfd_r_type + = elfNN_aarch64_bfd_reloc_from_type (input_bfd, r_type); + /* Handle relocs that can become R_AARCH64_RELATIVE, + but not ones against the GOT as those are handled + separately per-symbol. */ + if (bfd_r_type != BFD_RELOC_AARCH64_NN) + continue; + /* Can only pack relocation against an aligned address. */ + if (rel->r_offset % 2 != 0) + continue; + + struct elf_link_hash_entry *h = NULL; + asection *def_sec = NULL; + bool resolved_to_zero = false; + if (r_symndx < symtab_hdr->sh_info) + { + /* A local symbol. */ + Elf_Internal_Sym *isym; + isym = bfd_sym_from_r_symndx (&htab->root.sym_cache, + input_bfd, r_symndx); + BFD_ASSERT (isym != NULL); + if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC) + continue; + def_sec = bfd_section_from_elf_index (input_bfd, isym->st_shndx); + } + else + { + h = sym_hashes[r_symndx - symtab_hdr->sh_info]; + while (h->root.type == bfd_link_hash_indirect + || h->root.type == bfd_link_hash_warning) + h = (struct elf_link_hash_entry *) h->root.u.i.link; + + /* Filter out symbols that cannot have a relative reloc. */ + if (h->dyn_relocs == NULL) + continue; + if (bfd_is_abs_symbol (&h->root)) + continue; + if (h->type == STT_GNU_IFUNC) + continue; + + if (h->root.type == bfd_link_hash_defined + || h->root.type == bfd_link_hash_defweak) + def_sec = h->root.u.def.section; + resolved_to_zero = UNDEFWEAK_NO_DYNAMIC_RELOC (info, h); + } + if (def_sec != NULL && discarded_section (def_sec)) + continue; + /* Same logic as in elfNN_aarch64_final_link_relocate. + Except conditionals trimmed that cannot result a reltive reloc. */ + if (bfd_link_pic (info) + && (h == NULL + || (ELF_ST_VISIBILITY (h->other) == STV_DEFAULT + && !resolved_to_zero) + || h->root.type != bfd_link_hash_undefweak)) + { + if (h != NULL + && h->dynindx != -1 + && (!(bfd_link_pie (info) || SYMBOLIC_BIND (info, h)) + || !h->def_regular)) + continue; + if (!record_relr (htab, sec, rel->r_offset, sreloc)) + return false; + } + } + return true; +} + +static int +cmp_relr_addr (const void *p, const void *q) +{ + const bfd_vma *a = p; + const bfd_vma *b = q; + return *a < *b ? -1 : *a > *b ? 1 : 0; +} + +/* Produce a malloc'd sorted array of reloc addresses in htab->relr_sorted. + Returns false on allocation failure. */ + +static bool +sort_relr (struct bfd_link_info *info, + struct elf_aarch64_link_hash_table *htab) +{ + if (htab->relr_count == 0) + return true; + + bfd_vma *addr = htab->relr_sorted; + if (addr == NULL) + { + addr = bfd_malloc (htab->relr_count * sizeof (*addr)); + if (addr == NULL) + return false; + htab->relr_sorted = addr; + } + + for (bfd_size_type i = 0; i < htab->relr_count; i++) + { + bfd_vma off = _bfd_elf_section_offset (info->output_bfd, info, + htab->relr[i].sec, + htab->relr[i].off); + addr[i] = htab->relr[i].sec->output_section->vma + + htab->relr[i].sec->output_offset + + off; + } + qsort (addr, htab->relr_count, sizeof (*addr), cmp_relr_addr); + return true; +} + +/* Size .relr.dyn whenever the layout changes, the number of packed + relocs are unchanged but the packed representation can. */ + +bool +elfNN_aarch64_size_relative_relocs (struct bfd_link_info *info, + bool *need_layout) +{ + struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table (info); + asection *srelrdyn = htab->root.srelrdyn; + *need_layout = false; + + if (!sort_relr (info, htab)) + return false; + bfd_vma *addr = htab->relr_sorted; + + BFD_ASSERT (srelrdyn != NULL); + bfd_size_type oldsize = srelrdyn->size; + srelrdyn->size = 0; + for (bfd_size_type i = 0; i < htab->relr_count; ) + { + bfd_vma base = addr[i]; + i++; + srelrdyn->size += 8; + base += 8; + for (;;) + { + bfd_size_type start_i = i; + while (i < htab->relr_count + && addr[i] - base < 63 * 8 + && (addr[i] - base) % 8 == 0) + i++; + if (i == start_i) + break; + srelrdyn->size += 8; + base += 63 * 8; + } + } + if (srelrdyn->size != oldsize) + { + *need_layout = true; + /* Stop after a few iterations in case the layout does not converge, + we can do this when the size would shrink. */ + if (htab->relr_layout_iter++ > 5 && srelrdyn->size < oldsize) + { + srelrdyn->size = oldsize; + *need_layout = false; + } + } + return true; +} + +/* Emit the .relr.dyn section after it is sized and the layout is fixed. */ + +bool +elfNN_aarch64_finish_relative_relocs (struct bfd_link_info *info) +{ + struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table (info); + asection *srelrdyn = htab->root.srelrdyn; + bfd *dynobj = htab->root.dynobj; + + if (srelrdyn == NULL || srelrdyn->size == 0) + return true; + srelrdyn->contents = bfd_alloc (dynobj, srelrdyn->size); + if (srelrdyn->contents == NULL) + return false; + bfd_vma *addr = htab->relr_sorted; + bfd_byte *loc = srelrdyn->contents; + for (bfd_size_type i = 0; i < htab->relr_count; ) + { + bfd_vma base = addr[i]; + i++; + bfd_put_64 (dynobj, base, loc); + loc += 8; + base += 8; + for (;;) + { + bfd_vma bits = 0; + while (i < htab->relr_count) + { + bfd_vma delta = addr[i] - base; + if (delta >= 63 * 8 || delta % 8 != 0) + break; + bits |= (bfd_vma) 1 << (delta / 8); + i++; + } + if (bits == 0) + break; + bfd_put_64 (dynobj, (bits << 1) | 1, loc); + loc += 8; + base += 63 * 8; + } + } + free (addr); + htab->relr_sorted = NULL; + /* Pad any excess with 1's, a do-nothing encoding. */ + while (loc < srelrdyn->contents + srelrdyn->size) + { + bfd_put_64 (dynobj, 1, loc); + loc += 8; + } + return true; +} + /* This is the most important function of all . Innocuosly named though ! */ @@ -9344,6 +9727,27 @@ elfNN_aarch64_late_size_sections (bfd *output_bfd ATTRIBUTE_UNUSED, } } + /* Record the relative relocations that will be packed and undo the + size allocation for them in .rela.*. The size of .relr.dyn will be + computed later iteratively since it depends on the final layout. */ + if (info->enable_dt_relr && !bfd_link_relocatable (info)) + { + elf_link_hash_traverse (&htab->root, record_relr_dyn_got_relocs, info); + + for (ibfd = info->input_bfds; ibfd != NULL; ibfd = ibfd->link.next) + { + if (!is_aarch64_elf (ibfd)) + continue; + + for (s = ibfd->sections; s != NULL; s = s->next) + if (!record_relr_non_got_relocs (ibfd, info, s)) + return false; + + if (!record_relr_local_got_relocs (ibfd, info)) + return false; + } + } + /* Init mapping symbols information to use later to distingush between code and data while scanning for errata. */ if (htab->fix_erratum_835769 || htab->fix_erratum_843419) @@ -9383,6 +9787,19 @@ elfNN_aarch64_late_size_sections (bfd *output_bfd ATTRIBUTE_UNUSED, if (s != htab->root.srelplt) s->reloc_count = 0; } + else if (s == htab->root.srelrdyn) + { + /* Remove .relr.dyn based on relr_count, not size, since + it is not sized yet. */ + if (htab->relr_count == 0) + s->flags |= SEC_EXCLUDE; + else + /* Force dynamic tags for relocs even if there are no + .rela* relocs, required for setting DT_TEXTREL. */ + relocs = true; + /* Allocate contents later. */ + continue; + } else { /* It's not one of our sections, so don't allocate space. */ @@ -9741,6 +10158,9 @@ elfNN_aarch64_finish_dynamic_symbol (bfd *output_bfd, if (!(h->def_regular || ELF_COMMON_DEF_P (h))) return false; BFD_ASSERT ((h->got.offset & 1) != 0); + /* Don't emit relative relocs if they are packed. */ + if (info->enable_dt_relr) + goto skip_got_reloc; rela.r_info = ELFNN_R_INFO (0, AARCH64_R (RELATIVE)); rela.r_addend = (h->root.u.def.value + h->root.u.def.section->output_section->vma @@ -9760,6 +10180,7 @@ elfNN_aarch64_finish_dynamic_symbol (bfd *output_bfd, loc += htab->root.srelgot->reloc_count++ * RELOC_SIZE (htab); bfd_elfNN_swap_reloca_out (output_bfd, &rela, loc); } +skip_got_reloc: if (h->needs_copy) { @@ -10395,6 +10816,12 @@ const struct elf_size_info elfNN_aarch64_size_info = #define elf_backend_merge_gnu_properties \ elfNN_aarch64_merge_gnu_properties +#define elf_backend_size_relative_relocs \ + elfNN_aarch64_size_relative_relocs + +#define elf_backend_finish_relative_relocs \ + elfNN_aarch64_finish_relative_relocs + #define elf_backend_can_refcount 1 #define elf_backend_can_gc_sections 1 #define elf_backend_plt_readonly 1 diff --git a/bfd/elfxx-aarch64.h b/bfd/elfxx-aarch64.h index 6c084f7..f21e5ee 100644 --- a/bfd/elfxx-aarch64.h +++ b/bfd/elfxx-aarch64.h @@ -96,6 +96,17 @@ extern bool elf32_aarch64_size_stubs extern bool elf32_aarch64_build_stubs (struct bfd_link_info *); +/* AArch64 relative relocation packing support for ELF64. */ +extern bool elf64_aarch64_size_relative_relocs + (struct bfd_link_info *, bool *); +extern bool elf64_aarch64_finish_relative_relocs + (struct bfd_link_info *); +/* AArch64 relative relocation packing support for ELF32. */ +extern bool elf32_aarch64_size_relative_relocs + (struct bfd_link_info *, bool *); +extern bool elf32_aarch64_finish_relative_relocs + (struct bfd_link_info *); + /* Take the PAGE component of an address or offset. */ #define PG(x) ((x) & ~ (bfd_vma) 0xfff) #define PG_OFFSET(x) ((x) & (bfd_vma) 0xfff) diff --git a/ld/emulparams/aarch64elf.sh b/ld/emulparams/aarch64elf.sh index 8f68e51..72616b5 100644 --- a/ld/emulparams/aarch64elf.sh +++ b/ld/emulparams/aarch64elf.sh @@ -1,3 +1,5 @@ +source_sh ${srcdir}/emulparams/dt-relr.sh + ARCH=aarch64 MACHINE= NOP=0x1f2003d5 diff --git a/ld/emulparams/aarch64fbsd.sh b/ld/emulparams/aarch64fbsd.sh index 0bcab7a..db68d43 100644 --- a/ld/emulparams/aarch64fbsd.sh +++ b/ld/emulparams/aarch64fbsd.sh @@ -1,3 +1,5 @@ +source_sh ${srcdir}/emulparams/dt-relr.sh + ARCH=aarch64 MACHINE= NOP=0x1f2003d5 diff --git a/ld/emulparams/aarch64linux.sh b/ld/emulparams/aarch64linux.sh index 7b4a0e8..6fe82a7 100644 --- a/ld/emulparams/aarch64linux.sh +++ b/ld/emulparams/aarch64linux.sh @@ -1,3 +1,5 @@ +source_sh ${srcdir}/emulparams/dt-relr.sh + ARCH=aarch64 MACHINE= NOP=0x1f2003d5 |