diff options
Diffstat (limited to 'bfd/elfnn-riscv.c')
-rw-r--r-- | bfd/elfnn-riscv.c | 286 |
1 files changed, 279 insertions, 7 deletions
diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c index bd88ed7..f7cdb4e 100644 --- a/bfd/elfnn-riscv.c +++ b/bfd/elfnn-riscv.c @@ -2684,10 +2684,155 @@ riscv_relax_delete_bytes (bfd *abfd, asection *sec, bfd_vma addr, size_t count) return TRUE; } +/* A second format for recording PC-relative hi relocations. This stores the + information required to relax them to GP-relative addresses. */ + +typedef struct riscv_pcgp_hi_reloc riscv_pcgp_hi_reloc; +struct riscv_pcgp_hi_reloc +{ + bfd_vma hi_sec_off; + bfd_vma hi_addend; + bfd_vma hi_addr; + unsigned hi_sym; + asection *sym_sec; + riscv_pcgp_hi_reloc *next; +}; + +typedef struct riscv_pcgp_lo_reloc riscv_pcgp_lo_reloc; +struct riscv_pcgp_lo_reloc +{ + bfd_vma hi_sec_off; + riscv_pcgp_lo_reloc *next; +}; + +typedef struct +{ + riscv_pcgp_hi_reloc *hi; + riscv_pcgp_lo_reloc *lo; +} riscv_pcgp_relocs; + +static bfd_boolean +riscv_init_pcgp_relocs (riscv_pcgp_relocs *p) +{ + p->hi = NULL; + p->lo = NULL; + return TRUE; +} + +static void +riscv_free_pcgp_relocs (riscv_pcgp_relocs *p, + bfd *abfd ATTRIBUTE_UNUSED, + asection *sec ATTRIBUTE_UNUSED) +{ + riscv_pcgp_hi_reloc *c; + riscv_pcgp_lo_reloc *l; + + for (c = p->hi; c != NULL;) + { + riscv_pcgp_hi_reloc *next = c->next; + free (c); + c = next; + } + + for (l = p->lo; l != NULL;) + { + riscv_pcgp_lo_reloc *next = l->next; + free (l); + l = next; + } +} + +static bfd_boolean +riscv_record_pcgp_hi_reloc (riscv_pcgp_relocs *p, bfd_vma hi_sec_off, + bfd_vma hi_addend, bfd_vma hi_addr, + unsigned hi_sym, asection *sym_sec) +{ + riscv_pcgp_hi_reloc *new = bfd_malloc (sizeof(*new)); + if (!new) + return FALSE; + new->hi_sec_off = hi_sec_off; + new->hi_addend = hi_addend; + new->hi_addr = hi_addr; + new->hi_sym = hi_sym; + new->sym_sec = sym_sec; + new->next = p->hi; + p->hi = new; + return TRUE; +} + +static riscv_pcgp_hi_reloc * +riscv_find_pcgp_hi_reloc(riscv_pcgp_relocs *p, bfd_vma hi_sec_off) +{ + riscv_pcgp_hi_reloc *c; + + for (c = p->hi; c != NULL; c = c->next) + if (c->hi_sec_off == hi_sec_off) + return c; + return NULL; +} + +static bfd_boolean +riscv_delete_pcgp_hi_reloc(riscv_pcgp_relocs *p, bfd_vma hi_sec_off) +{ + bfd_boolean out = FALSE; + riscv_pcgp_hi_reloc *c; + + for (c = p->hi; c != NULL; c = c->next) + if (c->hi_sec_off == hi_sec_off) + out = TRUE; + + return out; +} + +static bfd_boolean +riscv_use_pcgp_hi_reloc(riscv_pcgp_relocs *p, bfd_vma hi_sec_off) +{ + bfd_boolean out = FALSE; + riscv_pcgp_hi_reloc *c; + + for (c = p->hi; c != NULL; c = c->next) + if (c->hi_sec_off == hi_sec_off) + out = TRUE; + + return out; +} + +static bfd_boolean +riscv_record_pcgp_lo_reloc (riscv_pcgp_relocs *p, bfd_vma hi_sec_off) +{ + riscv_pcgp_lo_reloc *new = bfd_malloc (sizeof(*new)); + if (!new) + return FALSE; + new->hi_sec_off = hi_sec_off; + new->next = p->lo; + p->lo = new; + return TRUE; +} + +static bfd_boolean +riscv_find_pcgp_lo_reloc (riscv_pcgp_relocs *p, bfd_vma hi_sec_off) +{ + riscv_pcgp_lo_reloc *c; + + for (c = p->lo; c != NULL; c = c->next) + if (c->hi_sec_off == hi_sec_off) + return TRUE; + return FALSE; +} + +static bfd_boolean +riscv_delete_pcgp_lo_reloc (riscv_pcgp_relocs *p ATTRIBUTE_UNUSED, + bfd_vma lo_sec_off ATTRIBUTE_UNUSED, + size_t bytes ATTRIBUTE_UNUSED) +{ + return TRUE; +} + typedef bfd_boolean (*relax_func_t) (bfd *, asection *, asection *, struct bfd_link_info *, Elf_Internal_Rela *, - bfd_vma, bfd_vma, bfd_vma, bfd_boolean *); + bfd_vma, bfd_vma, bfd_vma, bfd_boolean *, + riscv_pcgp_relocs *); /* Relax AUIPC + JALR into JAL. */ @@ -2698,7 +2843,8 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec, bfd_vma symval, bfd_vma max_alignment, bfd_vma reserve_size ATTRIBUTE_UNUSED, - bfd_boolean *again) + bfd_boolean *again, + riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED) { bfd_byte *contents = elf_section_data (sec)->this_hdr.contents; bfd_signed_vma foff = symval - (sec_addr (sec) + rel->r_offset); @@ -2781,7 +2927,8 @@ _bfd_riscv_relax_lui (bfd *abfd, bfd_vma symval, bfd_vma max_alignment, bfd_vma reserve_size, - bfd_boolean *again) + bfd_boolean *again, + riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED) { bfd_byte *contents = elf_section_data (sec)->this_hdr.contents; bfd_vma gp = riscv_global_pointer_value (link_info); @@ -2870,7 +3017,8 @@ _bfd_riscv_relax_tls_le (bfd *abfd, bfd_vma symval, bfd_vma max_alignment ATTRIBUTE_UNUSED, bfd_vma reserve_size ATTRIBUTE_UNUSED, - bfd_boolean *again) + bfd_boolean *again, + riscv_pcgp_relocs *prcel_relocs ATTRIBUTE_UNUSED) { /* See if this symbol is in range of tp. */ if (RISCV_CONST_HIGH_PART (tpoff (link_info, symval)) != 0) @@ -2909,7 +3057,8 @@ _bfd_riscv_relax_align (bfd *abfd, asection *sec, bfd_vma symval, bfd_vma max_alignment ATTRIBUTE_UNUSED, bfd_vma reserve_size ATTRIBUTE_UNUSED, - bfd_boolean *again ATTRIBUTE_UNUSED) + bfd_boolean *again ATTRIBUTE_UNUSED, + riscv_pcgp_relocs *pcrel_relocs ATTRIBUTE_UNUSED) { bfd_byte *contents = elf_section_data (sec)->this_hdr.contents; bfd_vma alignment = 1, pos; @@ -2957,6 +3106,118 @@ _bfd_riscv_relax_align (bfd *abfd, asection *sec, /* Relax PC-relative references to GP-relative references. */ static bfd_boolean +_bfd_riscv_relax_pc (bfd *abfd, + asection *sec, + asection *sym_sec, + struct bfd_link_info *link_info, + Elf_Internal_Rela *rel, + bfd_vma symval, + bfd_vma max_alignment, + bfd_vma reserve_size, + bfd_boolean *again ATTRIBUTE_UNUSED, + riscv_pcgp_relocs *pcgp_relocs) +{ + bfd_vma gp = riscv_global_pointer_value (link_info); + + BFD_ASSERT (rel->r_offset + 4 <= sec->size); + + /* Chain the _LO relocs to their cooresponding _HI reloc to compute the + * actual target address. */ + riscv_pcgp_hi_reloc hi_reloc = {0}; + switch (ELFNN_R_TYPE (rel->r_info)) + { + case R_RISCV_PCREL_LO12_I: + case R_RISCV_PCREL_LO12_S: + { + riscv_pcgp_hi_reloc *hi = riscv_find_pcgp_hi_reloc (pcgp_relocs, + symval - sec_addr(sym_sec)); + if (hi == NULL) + { + riscv_record_pcgp_lo_reloc (pcgp_relocs, symval - sec_addr(sym_sec)); + return TRUE; + } + + hi_reloc = *hi; + symval = hi_reloc.hi_addr; + sym_sec = hi_reloc.sym_sec; + if (!riscv_use_pcgp_hi_reloc(pcgp_relocs, hi->hi_sec_off)) + (*_bfd_error_handler) + (_("%B(%A+0x%lx): Unable to clear RISCV_PCREL_HI20 reloc" + "for cooresponding RISCV_PCREL_LO12 reloc"), + abfd, sec, rel->r_offset); + } + break; + + case R_RISCV_PCREL_HI20: + /* Mergeable symbols and code might later move out of range. */ + if (sym_sec->flags & (SEC_MERGE | SEC_CODE)) + return TRUE; + + /* If the cooresponding lo relocation has already been seen then it's not + * safe to relax this relocation. */ + if (riscv_find_pcgp_lo_reloc (pcgp_relocs, rel->r_offset)) + return TRUE; + + break; + + default: + abort (); + } + + if (gp) + { + /* If gp and the symbol are in the same output section, then + consider only that section's alignment. */ + struct bfd_link_hash_entry *h = + bfd_link_hash_lookup (link_info->hash, RISCV_GP_SYMBOL, FALSE, FALSE, TRUE); + if (h->u.def.section->output_section == sym_sec->output_section) + max_alignment = (bfd_vma) 1 << sym_sec->output_section->alignment_power; + } + + /* Is the reference in range of x0 or gp? + Valid gp range conservatively because of alignment issue. */ + if (VALID_ITYPE_IMM (symval) + || (symval >= gp + && VALID_ITYPE_IMM (symval - gp + max_alignment + reserve_size)) + || (symval < gp + && VALID_ITYPE_IMM (symval - gp - max_alignment - reserve_size))) + { + unsigned sym = hi_reloc.hi_sym; + switch (ELFNN_R_TYPE (rel->r_info)) + { + case R_RISCV_PCREL_LO12_I: + rel->r_info = ELFNN_R_INFO (sym, R_RISCV_GPREL_I); + rel->r_addend += hi_reloc.hi_addend; + return riscv_delete_pcgp_lo_reloc (pcgp_relocs, rel->r_offset, 4); + + case R_RISCV_PCREL_LO12_S: + rel->r_info = ELFNN_R_INFO (sym, R_RISCV_GPREL_S); + rel->r_addend += hi_reloc.hi_addend; + return riscv_delete_pcgp_lo_reloc (pcgp_relocs, rel->r_offset, 4); + + case R_RISCV_PCREL_HI20: + riscv_record_pcgp_hi_reloc (pcgp_relocs, + rel->r_offset, + rel->r_addend, + symval, + ELFNN_R_SYM(rel->r_info), + sym_sec); + /* We can delete the unnecessary AUIPC and reloc. */ + rel->r_info = ELFNN_R_INFO (0, R_RISCV_DELETE); + rel->r_addend = 4; + return riscv_delete_pcgp_hi_reloc (pcgp_relocs, rel->r_offset); + + default: + abort (); + } + } + + return TRUE; +} + +/* Relax PC-relative references to GP-relative references. */ + +static bfd_boolean _bfd_riscv_relax_delete (bfd *abfd, asection *sec, asection *sym_sec ATTRIBUTE_UNUSED, @@ -2965,7 +3226,8 @@ _bfd_riscv_relax_delete (bfd *abfd, bfd_vma symval ATTRIBUTE_UNUSED, bfd_vma max_alignment ATTRIBUTE_UNUSED, bfd_vma reserve_size ATTRIBUTE_UNUSED, - bfd_boolean *again ATTRIBUTE_UNUSED) + bfd_boolean *again ATTRIBUTE_UNUSED, + riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED) { if (!riscv_relax_delete_bytes(abfd, sec, rel->r_offset, rel->r_addend)) return FALSE; @@ -2989,6 +3251,7 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec, bfd_boolean ret = FALSE; unsigned int i; bfd_vma max_alignment, reserve_size = 0; + riscv_pcgp_relocs pcgp_relocs; *again = FALSE; @@ -3000,6 +3263,8 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec, && info->relax_pass == 0)) return TRUE; + riscv_init_pcgp_relocs (&pcgp_relocs); + /* Read this BFD's relocs if we haven't done so already. */ if (data->relocs) relocs = data->relocs; @@ -3037,6 +3302,11 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec, || type == R_RISCV_LO12_I || type == R_RISCV_LO12_S) relax_func = _bfd_riscv_relax_lui; + else if (!bfd_link_pic(info) + && (type == R_RISCV_PCREL_HI20 + || type == R_RISCV_PCREL_LO12_I + || type == R_RISCV_PCREL_LO12_S)) + relax_func = _bfd_riscv_relax_pc; else if (type == R_RISCV_TPREL_HI20 || type == R_RISCV_TPREL_ADD || type == R_RISCV_TPREL_LO12_I @@ -3127,7 +3397,8 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec, symval += rel->r_addend; if (!relax_func (abfd, sec, sym_sec, info, rel, symval, - max_alignment, reserve_size, again)) + max_alignment, reserve_size, again, + &pcgp_relocs)) goto fail; } @@ -3136,6 +3407,7 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec, fail: if (relocs != data->relocs) free (relocs); + riscv_free_pcgp_relocs(&pcgp_relocs, abfd, sec); return ret; } |