From 4f996613fabd8a4445f7ed4134fa764294069951 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 14 Apr 1994 17:33:22 +0000 Subject: [PATCH] * coff-mips.c (mips_howto_table): Add dummy entries to account for numbering changes in include/coff/mips.h. Add entries for MIPS_R_RELHI and MIPS_R_RELLO. (mips_ecoff_swap_reloc_in): Handle an extra bit for the reloc type when little endian. Treat internal MIPS_R_RELLO or MIPS_R_RELHI relocs like MIPS_R_SWITCH, and convert r_offset from 24 to 32 bits. (mips_ecoff_swap_reloc_out): Likewise. (mips_adjust_reloc_in): Handle internal MIPS_R_RELLO or MIPS_R_RELHI relocs like MIPS_R_SWITCH. (mips_adjust_reloc_out): Likewise. (mips_relhi_addr, mips_relhi_addend): New static variables. (mips_relhi_reloc, mips_rello_reloc): New functions. (mips_bfd_reloc_type_lookup): Turn BFD_RELOC_PCREL_HI16_S into MIPS_R_RELHI and turn BFD_RELOC_PCREL_LO16 into MIPS_R_RELLO. (mips_relocate_hi): Rename from mips_relocate_refhi, and add pcrel argument. Changed all callers. (mips_relocate_section): Rename got_reflo to got_lo and reflo_int_rel to lo_int_rel. Handle MIPS_R_RELLO and MIPS_R_RELHI relocs. (mips_relax_section): Adjust MIPS_R_RELHI/MIPS_R_RELLO pairs when expanding a PC relative call. --- bfd/ChangeLog | 29 +++ bfd/coff-mips.c | 596 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 505 insertions(+), 120 deletions(-) diff --git a/bfd/ChangeLog b/bfd/ChangeLog index 1a196dd334b..c0d641b6c53 100644 --- a/bfd/ChangeLog +++ b/bfd/ChangeLog @@ -1,3 +1,32 @@ +Thu Apr 14 13:05:10 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * coff-mips.c (mips_howto_table): Add dummy entries to account for + numbering changes in include/coff/mips.h. Add entries for + MIPS_R_RELHI and MIPS_R_RELLO. + (mips_ecoff_swap_reloc_in): Handle an extra bit for the reloc type + when little endian. Treat internal MIPS_R_RELLO or MIPS_R_RELHI + relocs like MIPS_R_SWITCH, and convert r_offset from 24 to 32 + bits. + (mips_ecoff_swap_reloc_out): Likewise. + (mips_adjust_reloc_in): Handle internal MIPS_R_RELLO or + MIPS_R_RELHI relocs like MIPS_R_SWITCH. + (mips_adjust_reloc_out): Likewise. + (mips_relhi_addr, mips_relhi_addend): New static variables. + (mips_relhi_reloc, mips_rello_reloc): New functions. + (mips_bfd_reloc_type_lookup): Turn BFD_RELOC_PCREL_HI16_S into + MIPS_R_RELHI and turn BFD_RELOC_PCREL_LO16 into MIPS_R_RELLO. + (mips_relocate_hi): Rename from mips_relocate_refhi, and add pcrel + argument. Changed all callers. + (mips_relocate_section): Rename got_reflo to got_lo and + reflo_int_rel to lo_int_rel. Handle MIPS_R_RELLO and MIPS_R_RELHI + relocs. + (mips_relax_section): Adjust MIPS_R_RELHI/MIPS_R_RELLO pairs when + expanding a PC relative call. + + * reloc.c (bfd_reloc_code_real_type): Add BFD_RELOC_PCREL_HI16_S + and BFD_RELOC_PCREL_LO16. + * bfd-in2.h: Rebuilt. + Tue Apr 12 13:36:20 1994 Jeffrey A. Law (law@snake.cs.utah.edu) * som.c (som_write_fixups): Always emit at least diff --git a/bfd/coff-mips.c b/bfd/coff-mips.c index 34822cf5a9f..3ad2f0a1b67 100644 --- a/bfd/coff-mips.c +++ b/bfd/coff-mips.c @@ -72,6 +72,20 @@ static bfd_reloc_status_type mips_gprel_reloc PARAMS ((bfd *abfd, asection *section, bfd *output_bfd, char **error)); +static bfd_reloc_status_type mips_relhi_reloc PARAMS ((bfd *abfd, + arelent *reloc, + asymbol *symbol, + PTR data, + asection *section, + bfd *output_bfd, + char **error)); +static bfd_reloc_status_type mips_rello_reloc PARAMS ((bfd *abfd, + arelent *reloc, + asymbol *symbol, + PTR data, + asection *section, + bfd *output_bfd, + char **error)); static bfd_reloc_status_type mips_switch_reloc PARAMS ((bfd *abfd, arelent *reloc, asymbol *symbol, @@ -79,13 +93,14 @@ static bfd_reloc_status_type mips_switch_reloc PARAMS ((bfd *abfd, asection *section, bfd *output_bfd, char **error)); -static void mips_relocate_refhi PARAMS ((struct internal_reloc *refhi, - struct internal_reloc *reflo, - bfd *input_bfd, - asection *input_section, - bfd_byte *contents, - size_t adjust, - bfd_vma relocation)); +static void mips_relocate_hi PARAMS ((struct internal_reloc *refhi, + struct internal_reloc *reflo, + bfd *input_bfd, + asection *input_section, + bfd_byte *contents, + size_t adjust, + bfd_vma relocation, + boolean pcrel)); static boolean mips_relocate_section PARAMS ((bfd *, struct bfd_link_info *, bfd *, asection *, bfd_byte *, PTR)); @@ -253,6 +268,11 @@ static reloc_howto_type mips_howto_table[] = 0xffff, /* dst_mask */ false), /* pcrel_offset */ + { 8 }, + { 9 }, + { 10 }, + { 11 }, + /* This reloc is a Cygnus extension used when generating position independent code for embedded systems. It represents a 16 bit PC relative reloc rightshifted twice as used in the MIPS branch @@ -271,6 +291,52 @@ static reloc_howto_type mips_howto_table[] = 0xffff, /* dst_mask */ true), /* pcrel_offset */ + /* This reloc is a Cygnus extension used when generating position + independent code for embedded systems. It represents the high 16 + bits of a PC relative reloc. The next reloc must be + MIPS_R_RELLO, and the addend is formed from the addends of the + two instructions, just as in MIPS_R_REFHI and MIPS_R_REFLO. The + final value is actually PC relative to the location of the + MIPS_R_RELLO reloc, not the MIPS_R_RELHI reloc. */ + HOWTO (MIPS_R_RELHI, /* type */ + 16, /* rightshift */ + 2, /* size (0 = byte, 1 = short, 2 = long) */ + 16, /* bitsize */ + true, /* pc_relative */ + 0, /* bitpos */ + complain_overflow_bitfield, /* complain_on_overflow */ + mips_relhi_reloc, /* special_function */ + "RELHI", /* name */ + true, /* partial_inplace */ + 0xffff, /* src_mask */ + 0xffff, /* dst_mask */ + true), /* pcrel_offset */ + + /* This reloc is a Cygnus extension used when generating position + independent code for embedded systems. It represents the low 16 + bits of a PC relative reloc. */ + HOWTO (MIPS_R_RELLO, /* type */ + 0, /* rightshift */ + 2, /* size (0 = byte, 1 = short, 2 = long) */ + 16, /* bitsize */ + true, /* pc_relative */ + 0, /* bitpos */ + complain_overflow_dont, /* complain_on_overflow */ + mips_rello_reloc, /* special_function */ + "RELLO", /* name */ + true, /* partial_inplace */ + 0xffff, /* src_mask */ + 0xffff, /* dst_mask */ + true), /* pcrel_offset */ + + { 15 }, + { 16 }, + { 17 }, + { 18 }, + { 19 }, + { 20 }, + { 21 }, + /* This reloc is a Cygnus extension used when generating position independent code for embedded systems. It represents an entry in a switch table, which is the difference between two symbols in @@ -376,21 +442,29 @@ mips_ecoff_swap_reloc_in (abfd, ext_ptr, intern) << RELOC_BITS1_SYMNDX_SH_LEFT_LITTLE) | ((int) ext->r_bits[2] << RELOC_BITS2_SYMNDX_SH_LEFT_LITTLE)); - intern->r_type = ((ext->r_bits[3] & RELOC_BITS3_TYPE_LITTLE) - >> RELOC_BITS3_TYPE_SH_LITTLE); + intern->r_type = (((ext->r_bits[3] & RELOC_BITS3_TYPE_LITTLE) + >> RELOC_BITS3_TYPE_SH_LITTLE) + | ((ext->r_bits[3] & RELOC_BITS3_TYPEHI_LITTLE) + << RELOC_BITS3_TYPEHI_SH_LITTLE)); intern->r_extern = (ext->r_bits[3] & RELOC_BITS3_EXTERN_LITTLE) != 0; } - /* If this is a MIPS_R_SWITCH reloc, r_symndx is actually the offset - from the reloc address to the base of the difference (see + /* If this is a MIPS_R_SWITCH reloc, or an internal MIPS_R_RELHI or + MIPS_R_RELLO reloc, r_symndx is actually the offset from the + reloc address to the base of the difference (see include/coff/mips.h for more details). We copy symndx into the r_offset field so as not to confuse ecoff_slurp_reloc_table in ecoff.c. In adjust_reloc_in we then copy r_offset into the reloc addend. */ - if (intern->r_type == MIPS_R_SWITCH) + if (intern->r_type == MIPS_R_SWITCH + || (! intern->r_extern + && (intern->r_type == MIPS_R_RELLO + || intern->r_type == MIPS_R_RELHI))) { BFD_ASSERT (! intern->r_extern); intern->r_offset = intern->r_symndx; + if (intern->r_offset & 0x800000) + intern->r_offset -= 0x1000000; intern->r_symndx = RELOC_SECTION_TEXT; } } @@ -409,15 +483,19 @@ mips_ecoff_swap_reloc_out (abfd, intern, dst) BFD_ASSERT (intern->r_extern || (intern->r_symndx >= 0 && intern->r_symndx <= 12)); - /* If this is a MIPS_R_SWITCH reloc, we actually want to write the - contents of r_offset out as the symbol index. This undoes the - change made by mips_ecoff_swap_reloc_in. */ - if (intern->r_type != MIPS_R_SWITCH) + /* If this is a MIPS_R_SWITCH reloc, or an internal MIPS_R_RELLO or + MIPS_R_RELHI reloc, we actually want to write the contents of + r_offset out as the symbol index. This undoes the change made by + mips_ecoff_swap_reloc_in. */ + if (intern->r_type != MIPS_R_SWITCH + && (intern->r_extern + || (intern->r_type != MIPS_R_RELHI + && intern->r_type != MIPS_R_RELLO))) r_symndx = intern->r_symndx; else { BFD_ASSERT (intern->r_symndx == RELOC_SECTION_TEXT); - r_symndx = intern->r_offset; + r_symndx = intern->r_offset & 0xffffff; } bfd_h_put_32 (abfd, intern->r_vaddr, (bfd_byte *) ext->r_vaddr); @@ -437,6 +515,8 @@ mips_ecoff_swap_reloc_out (abfd, intern, dst) ext->r_bits[2] = r_symndx >> RELOC_BITS2_SYMNDX_SH_LEFT_LITTLE; ext->r_bits[3] = (((intern->r_type << RELOC_BITS3_TYPE_SH_LITTLE) & RELOC_BITS3_TYPE_LITTLE) + | ((intern->r_type >> RELOC_BITS3_TYPEHI_SH_LITTLE + & RELOC_BITS3_TYPEHI_LITTLE)) | (intern->r_extern ? RELOC_BITS3_EXTERN_LITTLE : 0)); } } @@ -464,12 +544,16 @@ mips_adjust_reloc_in (abfd, intern, rptr) if (intern->r_type == MIPS_R_IGNORE) rptr->sym_ptr_ptr = bfd_abs_section.symbol_ptr_ptr; - /* If this is a MIPS_R_SWITCH reloc, we want the addend field of the - BFD relocto hold the value which was originally in the symndx - field of the internal MIPS ECOFF reloc. This value was copied - into intern->r_offset by mips_swap_reloc_in, and here we copy it - into the addend field. */ - if (intern->r_type == MIPS_R_SWITCH) + /* If this is a MIPS_R_SWITCH reloc, or an internal MIPS_R_RELHI or + MIPS_R_RELLO reloc, we want the addend field of the BFD relocto + hold the value which was originally in the symndx field of the + internal MIPS ECOFF reloc. This value was copied into + intern->r_offset by mips_swap_reloc_in, and here we copy it into + the addend field. */ + if (intern->r_type == MIPS_R_SWITCH + || (! intern->r_extern + && (intern->r_type == MIPS_R_RELHI + || intern->r_type == MIPS_R_RELLO))) rptr->addend = intern->r_offset; rptr->howto = &mips_howto_table[intern->r_type]; @@ -484,11 +568,15 @@ mips_adjust_reloc_out (abfd, rel, intern) const arelent *rel; struct internal_reloc *intern; { - /* For a MIPS_R_SWITCH reloc we must copy rel->addend into + /* For a MIPS_R_SWITCH reloc, or an internal MIPS_R_RELHI or + MIPS_R_RELLO reloc, we must copy rel->addend into intern->r_offset. This will then be written out as the symbol index by mips_ecoff_swap_reloc_out. This operation parallels the action of mips_adjust_reloc_in. */ - if (intern->r_type == MIPS_R_SWITCH) + if (intern->r_type == MIPS_R_SWITCH + || (! intern->r_extern + && (intern->r_type == MIPS_R_RELHI + || intern->r_type == MIPS_R_RELLO))) intern->r_offset = rel->addend; } @@ -790,6 +878,166 @@ mips_gprel_reloc (abfd, return bfd_reloc_ok; } +/* Do a RELHI relocation. We do this in conjunction with a RELLO + reloc, just as REFHI and REFLO are done together. RELHI and RELLO + are Cygnus extensions used when generating position independent + code for embedded systems. */ + +static bfd_byte *mips_relhi_addr; +static bfd_vma mips_relhi_addend; + +static bfd_reloc_status_type +mips_relhi_reloc (abfd, + reloc_entry, + symbol, + data, + input_section, + output_bfd, + error_message) + bfd *abfd; + arelent *reloc_entry; + asymbol *symbol; + PTR data; + asection *input_section; + bfd *output_bfd; + char **error_message; +{ + bfd_reloc_status_type ret; + bfd_vma relocation; + + /* If this is a reloc against a section symbol, then it is correct + in the object file. The only time we want to change this case is + when we are relaxing, and that is handled entirely by + mips_relocate_section and never calls this function. */ + if ((symbol->flags & BSF_SECTION_SYM) != 0) + { + if (output_bfd != (bfd *) NULL) + reloc_entry->address += input_section->output_offset; + return bfd_reloc_ok; + } + + /* This is an external symbol. If we're relocating, we don't want + to change anything. */ + if (output_bfd != (bfd *) NULL) + { + reloc_entry->address += input_section->output_offset; + return bfd_reloc_ok; + } + + ret = bfd_reloc_ok; + if (symbol->section == &bfd_und_section + && output_bfd == (bfd *) NULL) + ret = bfd_reloc_undefined; + + if (bfd_is_com_section (symbol->section)) + relocation = 0; + else + relocation = symbol->value; + + relocation += symbol->section->output_section->vma; + relocation += symbol->section->output_offset; + relocation += reloc_entry->addend; + + if (reloc_entry->address > input_section->_cooked_size) + return bfd_reloc_outofrange; + + /* Save the information, and let RELLO do the actual relocation. */ + mips_relhi_addr = (bfd_byte *) data + reloc_entry->address; + mips_relhi_addend = relocation; + + if (output_bfd != (bfd *) NULL) + reloc_entry->address += input_section->output_offset; + + return ret; +} + +/* Do a RELLO relocation. This is a straightforward 16 bit PC + relative relocation; this function exists in order to do the RELHI + relocation described above. */ + +static bfd_reloc_status_type +mips_rello_reloc (abfd, + reloc_entry, + symbol, + data, + input_section, + output_bfd, + error_message) + bfd *abfd; + arelent *reloc_entry; + asymbol *symbol; + PTR data; + asection *input_section; + bfd *output_bfd; + char **error_message; +{ + if (mips_relhi_addr != (bfd_byte *) NULL) + { + unsigned long insn; + unsigned long val; + unsigned long vallo; + + /* Do the RELHI relocation. Note that we actually don't need to + know anything about the RELLO itself, except where to find + the low 16 bits of the addend needed by the RELHI. */ + insn = bfd_get_32 (abfd, mips_relhi_addr); + vallo = (bfd_get_32 (abfd, (bfd_byte *) data + reloc_entry->address) + & 0xffff); + val = ((insn & 0xffff) << 16) + vallo; + val += mips_relhi_addend; + + /* If the symbol is defined, make val PC relative. If the + symbol is not defined we don't want to do this, because we + don't want the value in the object file to incorporate the + address of the reloc. */ + if (bfd_get_section (symbol) != &bfd_und_section + && ! bfd_is_com_section (bfd_get_section (symbol))) + val -= (input_section->output_section->vma + + input_section->output_offset + + reloc_entry->address); + + /* The low order 16 bits are always treated as a signed value. + Therefore, a negative value in the low order bits requires an + adjustment in the high order bits. We need to make this + adjustment in two ways: once for the bits we took from the + data, and once for the bits we are putting back in to the + data. */ + if ((vallo & 0x8000) != 0) + val -= 0x10000; + if ((val & 0x8000) != 0) + val += 0x10000; + + insn = (insn &~ 0xffff) | ((val >> 16) & 0xffff); + bfd_put_32 (abfd, insn, mips_relhi_addr); + + mips_relhi_addr = (bfd_byte *) NULL; + } + + /* If this is a reloc against a section symbol, then it is correct + in the object file. The only time we want to change this case is + when we are relaxing, and that is handled entirely by + mips_relocate_section and never calls this function. */ + if ((symbol->flags & BSF_SECTION_SYM) != 0) + { + if (output_bfd != (bfd *) NULL) + reloc_entry->address += input_section->output_offset; + return bfd_reloc_ok; + } + + /* bfd_perform_relocation does not handle pcrel_offset relocations + correctly when generating a relocateable file, so handle them + directly here. */ + if (output_bfd != (bfd *) NULL) + { + reloc_entry->address += input_section->output_offset; + return bfd_reloc_ok; + } + + /* Now do the RELLO reloc in the usual way. */ + return mips_generic_reloc (abfd, reloc_entry, symbol, data, + input_section, output_bfd, error_message); +} + /* This is the special function for the MIPS_R_SWITCH reloc. This special reloc is normally correct in the object file, and only requires special handling when relaxing. We don't want @@ -851,6 +1099,12 @@ mips_bfd_reloc_type_lookup (abfd, code) case BFD_RELOC_16_PCREL_S2: mips_type = MIPS_R_PCREL16; break; + case BFD_RELOC_PCREL_HI16_S: + mips_type = MIPS_R_RELHI; + break; + case BFD_RELOC_PCREL_LO16: + mips_type = MIPS_R_RELLO; + break; case BFD_RELOC_GPREL32: mips_type = MIPS_R_SWITCH; break; @@ -862,13 +1116,13 @@ mips_bfd_reloc_type_lookup (abfd, code) } /* A helper routine for mips_relocate_section which handles the REFHI - relocation. The REFHI relocation must be followed by a REFLO - relocation, and the addend used is formed from the addends of both - instructions. */ + and RELHI relocations. The REFHI relocation must be followed by a + REFLO relocation (and RELHI by a RELLO), and the addend used is + formed from the addends of both instructions. */ static void -mips_relocate_refhi (refhi, reflo, input_bfd, input_section, contents, - adjust, relocation) +mips_relocate_hi (refhi, reflo, input_bfd, input_section, contents, adjust, + relocation, pcrel) struct internal_reloc *refhi; struct internal_reloc *reflo; bfd *input_bfd; @@ -876,6 +1130,7 @@ mips_relocate_refhi (refhi, reflo, input_bfd, input_section, contents, bfd_byte *contents; size_t adjust; bfd_vma relocation; + boolean pcrel; { unsigned long insn; unsigned long val; @@ -896,6 +1151,12 @@ mips_relocate_refhi (refhi, reflo, input_bfd, input_section, contents, and once for the bits we are putting back in to the data. */ if ((vallo & 0x8000) != 0) val -= 0x10000; + + if (pcrel) + val -= (input_section->output_section->vma + + input_section->output_offset + + (reflo->r_vaddr - input_section->vma + adjust)); + if ((val & 0x8000) != 0) val += 0x10000; @@ -925,8 +1186,8 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, struct external_reloc *ext_rel; struct external_reloc *ext_rel_end; unsigned int i; - boolean got_reflo; - struct internal_reloc reflo_int_rel; + boolean got_lo; + struct internal_reloc lo_int_rel; BFD_ASSERT (input_bfd->xvec->header_byteorder_big_p == output_bfd->xvec->header_byteorder_big_p); @@ -984,7 +1245,7 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, else gp_undefined = false; - got_reflo = false; + got_lo = false; adjust = 0; @@ -1005,29 +1266,33 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, bfd_vma relocation; bfd_reloc_status_type r; - if (! got_reflo) + if (! got_lo) mips_ecoff_swap_reloc_in (input_bfd, (PTR) ext_rel, &int_rel); else { - int_rel = reflo_int_rel; - got_reflo = false; + int_rel = lo_int_rel; + got_lo = false; } BFD_ASSERT (int_rel.r_type < sizeof mips_howto_table / sizeof mips_howto_table[0]); - /* The REFHI reloc requires special handling. It must be - followed by a REFLO reloc, and the addend is formed from both - fields. */ - if (int_rel.r_type == MIPS_R_REFHI) + /* The REFHI and RELHI relocs requires special handling. they + must be followed by a REFLO or RELLO reloc, respectively, and + the addend is formed from both relocs. */ + if (int_rel.r_type == MIPS_R_REFHI + || int_rel.r_type == MIPS_R_RELHI) { BFD_ASSERT ((ext_rel + 1) < ext_rel_end); mips_ecoff_swap_reloc_in (input_bfd, (PTR) (ext_rel + 1), - &reflo_int_rel); - BFD_ASSERT (reflo_int_rel.r_type == MIPS_R_REFLO - && int_rel.r_extern == reflo_int_rel.r_extern - && int_rel.r_symndx == reflo_int_rel.r_symndx); - got_reflo = true; + &lo_int_rel); + BFD_ASSERT ((lo_int_rel.r_type + == (int_rel.r_type == MIPS_R_REFHI + ? MIPS_R_REFLO + : MIPS_R_RELLO)) + && int_rel.r_extern == lo_int_rel.r_extern + && int_rel.r_symndx == lo_int_rel.r_symndx); + got_lo = true; } howto = &mips_howto_table[int_rel.r_type]; @@ -1139,17 +1404,17 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, && offsets[i] != 0) { BFD_ASSERT (! info->relocateable); - BFD_ASSERT (int_rel.r_type == MIPS_R_PCREL16); + BFD_ASSERT (int_rel.r_type == MIPS_R_PCREL16 + || int_rel.r_type == MIPS_R_RELHI + || int_rel.r_type == MIPS_R_RELLO); if (offsets[i] != 1) - { - BFD_ASSERT (! int_rel.r_extern); - addend += offsets[i]; - } + addend += offsets[i]; else { bfd_byte *here; - BFD_ASSERT (int_rel.r_extern); + BFD_ASSERT (int_rel.r_extern + && int_rel.r_type == MIPS_R_PCREL16); /* Move the rest of the instructions up. */ here = (contents @@ -1254,7 +1519,43 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, currently holds just the addend. We must adjust by the address to get the right value. */ if (howto->pc_relative) - relocation -= int_rel.r_vaddr - input_section->vma; + { + relocation -= int_rel.r_vaddr - input_section->vma; + + /* If we are converting a RELHI or RELLO reloc + from being against an external symbol to + being against a section, we must put a + special value into the r_offset field. This + value is the old addend. The r_offset for + both the RELOHI and RELLO relocs are the + same, and we set both when we see RELHI. */ + if (int_rel.r_type == MIPS_R_RELHI) + { + long addhi, addlo; + + addhi = bfd_get_32 (input_bfd, + (contents + + adjust + + int_rel.r_vaddr + - input_section->vma)); + addhi &= 0xffff; + if (addhi & 0x8000) + addhi -= 0x10000; + addhi <<= 16; + + addlo = bfd_get_32 (input_bfd, + (contents + + adjust + + lo_int_rel.r_vaddr + - input_section->vma)); + addlo &= 0xffff; + if (addlo & 0x8000) + addlo -= 0x10000; + + int_rel.r_offset = addhi + addlo; + lo_int_rel.r_offset = int_rel.r_offset; + } + } h = NULL; } @@ -1289,8 +1590,14 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, /* Adjust a PC relative relocation by removing the reference to the original address in the section and including the - reference to the new address. */ - if (howto->pc_relative) + reference to the new address. However, external RELHI + and RELLO relocs are PC relative, but don't include any + reference to the address. The addend is merely an + addend. */ + if (howto->pc_relative + && (! int_rel.r_extern + || (int_rel.r_type != MIPS_R_RELHI + && int_rel.r_type != MIPS_R_RELLO))) relocation -= (input_section->output_section->vma + input_section->output_offset - input_section->vma); @@ -1300,7 +1607,8 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, r = bfd_reloc_ok; else { - if (int_rel.r_type != MIPS_R_REFHI) + if (int_rel.r_type != MIPS_R_REFHI + && int_rel.r_type != MIPS_R_RELHI) r = _bfd_relocate_contents (howto, input_bfd, relocation, (contents + adjust @@ -1308,9 +1616,10 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, - input_section->vma)); else { - mips_relocate_refhi (&int_rel, &reflo_int_rel, - input_bfd, input_section, contents, - adjust, relocation); + mips_relocate_hi (&int_rel, &lo_int_rel, + input_bfd, input_section, contents, + adjust, relocation, + int_rel.r_type == MIPS_R_RELHI); r = bfd_reloc_ok; } } @@ -1359,10 +1668,16 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, file. Make it look like a pcrel_offset relocation by adding in the start address. */ if (howto->pc_relative) - relocation += int_rel.r_vaddr + adjust; + { + if (int_rel.r_type != MIPS_R_RELHI) + relocation += int_rel.r_vaddr + adjust; + else + relocation += lo_int_rel.r_vaddr + adjust; + } } - if (int_rel.r_type != MIPS_R_REFHI) + if (int_rel.r_type != MIPS_R_REFHI + && int_rel.r_type != MIPS_R_RELHI) r = _bfd_final_link_relocate (howto, input_bfd, input_section, @@ -1374,9 +1689,10 @@ mips_relocate_section (output_bfd, info, input_bfd, input_section, addend); else { - mips_relocate_refhi (&int_rel, &reflo_int_rel, input_bfd, - input_section, contents, adjust, - relocation); + mips_relocate_hi (&int_rel, &lo_int_rel, input_bfd, + input_section, contents, adjust, + relocation, + int_rel.r_type == MIPS_R_RELHI); r = bfd_reloc_ok; } } @@ -1652,46 +1968,25 @@ mips_relax_section (abfd, sec, info, again) offsets[i] = 1; - /* Now look for all PC relative branches or switch table entries - that cross this reloc and adjust their offsets. We will turn - the single branch instruction into a four instruction - sequence. In this loop we are only interested in local PC - relative branches. */ + /* Now look for all PC relative references that cross this reloc + and adjust their offsets. */ adj_ext_rel = (struct external_reloc *) section_tdata->external_relocs; for (adj_i = 0; adj_ext_rel < ext_rel_end; adj_ext_rel++, adj_i++) { - int r_type; struct internal_reloc adj_int_rel; - - /* Quickly check that this reloc is internal PCREL16 or - SWITCH. */ - if (abfd->xvec->header_byteorder_big_p) - { - if ((adj_ext_rel->r_bits[3] & RELOC_BITS3_EXTERN_BIG) != 0) - continue; - r_type = ((adj_ext_rel->r_bits[3] & RELOC_BITS3_TYPE_BIG) - >> RELOC_BITS3_TYPE_SH_BIG); - if (r_type != MIPS_R_PCREL16 - && r_type != MIPS_R_SWITCH) - continue; - } - else - { - if ((adj_ext_rel->r_bits[3] & RELOC_BITS3_EXTERN_LITTLE) != 0) - continue; - r_type = ((adj_ext_rel->r_bits[3] & RELOC_BITS3_TYPE_LITTLE) - >> RELOC_BITS3_TYPE_SH_LITTLE); - if (r_type != MIPS_R_PCREL16 - && r_type != MIPS_R_SWITCH) - continue; - } + bfd_vma start, stop; + int change; mips_ecoff_swap_reloc_in (abfd, (PTR) adj_ext_rel, &adj_int_rel); - if (r_type == MIPS_R_PCREL16) + if (adj_int_rel.r_type == MIPS_R_PCREL16) { unsigned long insn; - bfd_vma dst; + + /* We only care about local references. External ones + will be relocated correctly anyhow. */ + if (adj_int_rel.r_extern) + continue; /* We are only interested in a PC relative reloc within this section. FIXME: Cross section PC relative @@ -1700,29 +1995,76 @@ mips_relax_section (abfd, sec, info, again) if (adj_int_rel.r_symndx != RELOC_SECTION_TEXT) continue; - /* Fetch the branch instruction. */ + start = adj_int_rel.r_vaddr; + insn = bfd_get_32 (abfd, contents + adj_int_rel.r_vaddr - sec->vma); - /* Work out the destination address. */ - dst = (insn & 0xffff) << 2; - if ((dst & 0x20000) != 0) - dst -= 0x40000; - dst += adj_int_rel.r_vaddr + 4; - - /* If this branch crosses the branch we just decided to - expand, adjust the offset appropriately. */ - if (adj_int_rel.r_vaddr <= int_rel.r_vaddr - && dst > int_rel.r_vaddr) - offsets[adj_i] += PCREL16_EXPANSION_ADJUSTMENT; - else if (adj_int_rel.r_vaddr > int_rel.r_vaddr - && dst <= int_rel.r_vaddr) - offsets[adj_i] -= PCREL16_EXPANSION_ADJUSTMENT; + stop = (insn & 0xffff) << 2; + if ((stop & 0x20000) != 0) + stop -= 0x40000; + stop += adj_int_rel.r_vaddr + 4; } - else + else if (adj_int_rel.r_type == MIPS_R_RELHI) + { + struct internal_reloc rello; + long addhi, addlo; + + /* The next reloc must be MIPS_R_RELLO, and we handle + them together. */ + BFD_ASSERT (adj_ext_rel + 1 < ext_rel_end); + + mips_ecoff_swap_reloc_in (abfd, (PTR) (adj_ext_rel + 1), &rello); + + BFD_ASSERT (rello.r_type == MIPS_R_RELLO); + + addhi = bfd_get_32 (abfd, + contents + adj_int_rel.r_vaddr - sec->vma); + addhi &= 0xffff; + if (addhi & 0x8000) + addhi -= 0x10000; + addhi <<= 16; + + addlo = bfd_get_32 (abfd, contents + rello.r_vaddr - sec->vma); + addlo &= 0xffff; + if (addlo & 0x8000) + addlo -= 0x10000; + + if (adj_int_rel.r_extern) + { + /* The value we want here is + sym - RELLOaddr + addend + which we can express as + sym - (RELLOaddr - addend) + Therefore if we are expanding the area between + RELLOaddr and RELLOaddr - addend we must adjust + the addend. This is admittedly ambiguous, since + we might mean (sym + addend) - RELLOaddr, but in + practice we don't, and there is no way to handle + that case correctly since at this point we have + no idea whether any reloc is being expanded + between sym and sym + addend. */ + start = rello.r_vaddr - (addhi + addlo); + stop = rello.r_vaddr; + } + else + { + /* An internal RELHI/RELLO pair represents the + difference between two addresses, $LC0 - foo. + The symndx value is actually the difference + between the reloc address and $LC0. This lets us + compute $LC0, and, by considering the addend, + foo. If the reloc we are expanding falls between + those two relocs, we must adjust the addend. At + this point, the symndx value is actually in the + r_offset field, where it was put by + mips_ecoff_swap_reloc_in. */ + start = rello.r_vaddr - adj_int_rel.r_offset; + stop = start + addhi + addlo; + } + } + else if (adj_int_rel.r_type == MIPS_R_SWITCH) { - bfd_vma start, stop; - /* A MIPS_R_SWITCH reloc represents a word of the form .word $L3-$LS12 The value in the object file is correct, assuming the @@ -1736,20 +2078,34 @@ mips_relax_section (abfd, sec, info, again) this point, the symndx value is actually found in the r_offset field, since it was moved by mips_ecoff_swap_reloc_in. */ - start = adj_int_rel.r_vaddr - adj_int_rel.r_offset; stop = start + bfd_get_32 (abfd, (contents + adj_int_rel.r_vaddr - sec->vma)); + } + else + continue; + + /* If the range expressed by this reloc, which is the + distance between START and STOP crosses the reloc we are + expanding, we must adjust the offset. The sign of the + adjustment depends upon the direction in which the range + crosses the reloc being expanded. */ + if (start <= int_rel.r_vaddr && stop > int_rel.r_vaddr) + change = PCREL16_EXPANSION_ADJUSTMENT; + else if (start > int_rel.r_vaddr && stop <= int_rel.r_vaddr) + change = - PCREL16_EXPANSION_ADJUSTMENT; + else + change = 0; - /* The value we want in the object file is stop - start. - If the expanded branch lies between start and stop, - we must adjust the offset. */ - if (start <= int_rel.r_vaddr && stop > int_rel.r_vaddr) - offsets[adj_i] += PCREL16_EXPANSION_ADJUSTMENT; - else if (start > int_rel.r_vaddr && stop <= int_rel.r_vaddr) - offsets[adj_i] -= PCREL16_EXPANSION_ADJUSTMENT; + offsets[adj_i] += change; + + if (adj_int_rel.r_type == MIPS_R_RELHI) + { + adj_ext_rel++; + adj_i++; + offsets[adj_i] += change; } } -- 2.30.2