+ asection **symndx_to_section;
+ struct ecoff_link_hash_entry **sym_hashes;
+ bfd_vma gp;
+ boolean gp_undefined;
+ size_t adjust;
+ long *offsets;
+ struct external_reloc *ext_rel;
+ struct external_reloc *ext_rel_end;
+ unsigned int i;
+ boolean got_lo;
+ struct internal_reloc lo_int_rel;
+
+ BFD_ASSERT (input_bfd->xvec->byteorder
+ == output_bfd->xvec->byteorder);
+
+ /* We keep a table mapping the symndx found in an internal reloc to
+ the appropriate section. This is faster than looking up the
+ section by name each time. */
+ symndx_to_section = ecoff_data (input_bfd)->symndx_to_section;
+ if (symndx_to_section == (asection **) NULL)
+ {
+ symndx_to_section = ((asection **)
+ bfd_alloc (input_bfd,
+ (NUM_RELOC_SECTIONS
+ * sizeof (asection *))));
+ if (!symndx_to_section)
+ return false;
+
+ symndx_to_section[RELOC_SECTION_NONE] = NULL;
+ symndx_to_section[RELOC_SECTION_TEXT] =
+ bfd_get_section_by_name (input_bfd, ".text");
+ symndx_to_section[RELOC_SECTION_RDATA] =
+ bfd_get_section_by_name (input_bfd, ".rdata");
+ symndx_to_section[RELOC_SECTION_DATA] =
+ bfd_get_section_by_name (input_bfd, ".data");
+ symndx_to_section[RELOC_SECTION_SDATA] =
+ bfd_get_section_by_name (input_bfd, ".sdata");
+ symndx_to_section[RELOC_SECTION_SBSS] =
+ bfd_get_section_by_name (input_bfd, ".sbss");
+ symndx_to_section[RELOC_SECTION_BSS] =
+ bfd_get_section_by_name (input_bfd, ".bss");
+ symndx_to_section[RELOC_SECTION_INIT] =
+ bfd_get_section_by_name (input_bfd, ".init");
+ symndx_to_section[RELOC_SECTION_LIT8] =
+ bfd_get_section_by_name (input_bfd, ".lit8");
+ symndx_to_section[RELOC_SECTION_LIT4] =
+ bfd_get_section_by_name (input_bfd, ".lit4");
+ symndx_to_section[RELOC_SECTION_XDATA] = NULL;
+ symndx_to_section[RELOC_SECTION_PDATA] = NULL;
+ symndx_to_section[RELOC_SECTION_FINI] =
+ bfd_get_section_by_name (input_bfd, ".fini");
+ symndx_to_section[RELOC_SECTION_LITA] = NULL;
+ symndx_to_section[RELOC_SECTION_ABS] = NULL;
+
+ ecoff_data (input_bfd)->symndx_to_section = symndx_to_section;
+ }
+
+ sym_hashes = ecoff_data (input_bfd)->sym_hashes;
+
+ gp = _bfd_get_gp_value (output_bfd);
+ if (gp == 0)
+ gp_undefined = true;
+ else
+ gp_undefined = false;
+
+ got_lo = false;
+
+ adjust = 0;
+
+ if (ecoff_section_data (input_bfd, input_section) == NULL)
+ offsets = NULL;
+ else
+ offsets = ecoff_section_data (input_bfd, input_section)->offsets;
+
+ ext_rel = (struct external_reloc *) external_relocs;
+ ext_rel_end = ext_rel + input_section->reloc_count;
+ for (i = 0; ext_rel < ext_rel_end; ext_rel++, i++)
+ {
+ struct internal_reloc int_rel;
+ boolean use_lo = false;
+ bfd_vma addend;
+ reloc_howto_type *howto;
+ struct ecoff_link_hash_entry *h = NULL;
+ asection *s = NULL;
+ bfd_vma relocation;
+ bfd_reloc_status_type r;
+
+ if (! got_lo)
+ mips_ecoff_swap_reloc_in (input_bfd, (PTR) ext_rel, &int_rel);
+ else
+ {
+ 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 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)
+ {
+ struct external_reloc *lo_ext_rel;
+
+ /* As a GNU extension, permit an arbitrary number of REFHI
+ or RELHI relocs before the REFLO or RELLO reloc. This
+ permits gcc to emit the HI and LO relocs itself. */
+ for (lo_ext_rel = ext_rel + 1;
+ lo_ext_rel < ext_rel_end;
+ lo_ext_rel++)
+ {
+ mips_ecoff_swap_reloc_in (input_bfd, (PTR) lo_ext_rel,
+ &lo_int_rel);
+ if (lo_int_rel.r_type != int_rel.r_type)
+ break;
+ }
+
+ if (lo_ext_rel < ext_rel_end
+ && (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)
+ {
+ use_lo = true;
+ if (lo_ext_rel == ext_rel + 1)
+ got_lo = true;
+ }
+ }
+
+ howto = &mips_howto_table[int_rel.r_type];
+
+ /* The SWITCH reloc must be handled specially. This reloc is
+ marks the location of a difference between two portions of an
+ object file. The symbol index does not reference a symbol,
+ but is actually the offset from the reloc to the subtrahend
+ of the difference. This reloc is correct in the object file,
+ and needs no further adjustment, unless we are relaxing. If
+ we are relaxing, we may have to add in an offset. Since no
+ symbols are involved in this reloc, we handle it completely
+ here. */
+ if (int_rel.r_type == MIPS_R_SWITCH)
+ {
+ if (offsets != NULL
+ && offsets[i] != 0)
+ {
+ r = _bfd_relocate_contents (howto, input_bfd,
+ (bfd_vma) offsets[i],
+ (contents
+ + adjust
+ + int_rel.r_vaddr
+ - input_section->vma));
+ BFD_ASSERT (r == bfd_reloc_ok);
+ }
+
+ continue;
+ }
+
+ if (int_rel.r_extern)
+ {
+ h = sym_hashes[int_rel.r_symndx];
+ /* If h is NULL, that means that there is a reloc against an
+ external symbol which we thought was just a debugging
+ symbol. This should not happen. */
+ if (h == (struct ecoff_link_hash_entry *) NULL)
+ abort ();
+ }
+ else
+ {
+ if (int_rel.r_symndx < 0 || int_rel.r_symndx >= NUM_RELOC_SECTIONS)
+ s = NULL;
+ else
+ s = symndx_to_section[int_rel.r_symndx];
+
+ if (s == (asection *) NULL)
+ abort ();
+ }
+
+ /* The GPREL reloc uses an addend: the difference in the GP
+ values. */
+ if (int_rel.r_type != MIPS_R_GPREL
+ && int_rel.r_type != MIPS_R_LITERAL)
+ addend = 0;
+ else
+ {
+ if (gp_undefined)
+ {
+ if (! ((*info->callbacks->reloc_dangerous)
+ (info, _("GP relative relocation when GP not defined"),
+ input_bfd, input_section,
+ int_rel.r_vaddr - input_section->vma)))
+ return false;
+ /* Only give the error once per link. */
+ gp = 4;
+ _bfd_set_gp_value (output_bfd, gp);
+ gp_undefined = false;
+ }
+ if (! int_rel.r_extern)
+ {
+ /* This is a relocation against a section. The current
+ addend in the instruction is the difference between
+ INPUT_SECTION->vma and the GP value of INPUT_BFD. We
+ must change this to be the difference between the
+ final definition (which will end up in RELOCATION)
+ and the GP value of OUTPUT_BFD (which is in GP). */
+ addend = ecoff_data (input_bfd)->gp - gp;
+ }
+ else if (! info->relocateable
+ || h->root.type == bfd_link_hash_defined
+ || h->root.type == bfd_link_hash_defweak)
+ {
+ /* This is a relocation against a defined symbol. The
+ current addend in the instruction is simply the
+ desired offset into the symbol (normally zero). We
+ are going to change this into a relocation against a
+ defined symbol, so we want the instruction to hold
+ the difference between the final definition of the
+ symbol (which will end up in RELOCATION) and the GP
+ value of OUTPUT_BFD (which is in GP). */
+ addend = - gp;
+ }
+ else
+ {
+ /* This is a relocation against an undefined or common
+ symbol. The current addend in the instruction is
+ simply the desired offset into the symbol (normally
+ zero). We are generating relocateable output, and we
+ aren't going to define this symbol, so we just leave
+ the instruction alone. */
+ addend = 0;
+ }
+ }
+
+ /* If we are relaxing, mips_relax_section may have set
+ offsets[i] to some value. A value of 1 means we must expand
+ a PC relative branch into a multi-instruction of sequence,
+ and any other value is an addend. */
+ if (offsets != NULL
+ && offsets[i] != 0)
+ {
+ BFD_ASSERT (! info->relocateable);
+ 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)
+ addend += offsets[i];
+ else
+ {
+ bfd_byte *here;
+
+ BFD_ASSERT (int_rel.r_extern
+ && int_rel.r_type == MIPS_R_PCREL16);
+
+ /* Move the rest of the instructions up. */
+ here = (contents
+ + adjust
+ + int_rel.r_vaddr
+ - input_section->vma);
+ memmove (here + PCREL16_EXPANSION_ADJUSTMENT, here,
+ (size_t) (input_section->_raw_size
+ - (int_rel.r_vaddr - input_section->vma)));
+
+ /* Generate the new instructions. */
+ if (! mips_relax_pcrel16 (info, input_bfd, input_section,
+ h, here,
+ (input_section->output_section->vma
+ + input_section->output_offset
+ + (int_rel.r_vaddr
+ - input_section->vma)
+ + adjust)))
+ return false;
+
+ /* We must adjust everything else up a notch. */
+ adjust += PCREL16_EXPANSION_ADJUSTMENT;
+
+ /* mips_relax_pcrel16 handles all the details of this
+ relocation. */
+ continue;
+ }
+ }
+
+ /* If we are relaxing, and this is a reloc against the .text
+ segment, we may need to adjust it if some branches have been
+ expanded. The reloc types which are likely to occur in the
+ .text section are handled efficiently by mips_relax_section,
+ and thus do not need to be handled here. */
+ if (ecoff_data (input_bfd)->debug_info.adjust != NULL
+ && ! int_rel.r_extern
+ && int_rel.r_symndx == RELOC_SECTION_TEXT
+ && (strcmp (bfd_get_section_name (input_bfd, input_section),
+ ".text") != 0
+ || (int_rel.r_type != MIPS_R_PCREL16
+ && int_rel.r_type != MIPS_R_SWITCH
+ && int_rel.r_type != MIPS_R_RELHI
+ && int_rel.r_type != MIPS_R_RELLO)))
+ {
+ bfd_vma adr;
+ struct ecoff_value_adjust *a;
+
+ /* We need to get the addend so that we know whether we need
+ to adjust the address. */
+ BFD_ASSERT (int_rel.r_type == MIPS_R_REFWORD);
+
+ adr = bfd_get_32 (input_bfd,
+ (contents
+ + adjust
+ + int_rel.r_vaddr
+ - input_section->vma));
+
+ for (a = ecoff_data (input_bfd)->debug_info.adjust;
+ a != (struct ecoff_value_adjust *) NULL;
+ a = a->next)
+ {
+ if (adr >= a->start && adr < a->end)
+ addend += a->adjust;
+ }
+ }
+
+ if (info->relocateable)
+ {
+ /* We are generating relocateable output, and must convert
+ the existing reloc. */
+ if (int_rel.r_extern)
+ {
+ if ((h->root.type == bfd_link_hash_defined
+ || h->root.type == bfd_link_hash_defweak)
+ && ! bfd_is_abs_section (h->root.u.def.section))
+ {
+ const char *name;
+
+ /* This symbol is defined in the output. Convert
+ the reloc from being against the symbol to being
+ against the section. */
+
+ /* Clear the r_extern bit. */
+ int_rel.r_extern = 0;
+
+ /* Compute a new r_symndx value. */
+ s = h->root.u.def.section;
+ name = bfd_get_section_name (output_bfd,
+ s->output_section);
+
+ int_rel.r_symndx = -1;
+ switch (name[1])
+ {
+ case 'b':
+ if (strcmp (name, ".bss") == 0)
+ int_rel.r_symndx = RELOC_SECTION_BSS;
+ break;
+ case 'd':
+ if (strcmp (name, ".data") == 0)
+ int_rel.r_symndx = RELOC_SECTION_DATA;
+ break;
+ case 'f':
+ if (strcmp (name, ".fini") == 0)
+ int_rel.r_symndx = RELOC_SECTION_FINI;
+ break;
+ case 'i':
+ if (strcmp (name, ".init") == 0)
+ int_rel.r_symndx = RELOC_SECTION_INIT;
+ break;
+ case 'l':
+ if (strcmp (name, ".lit8") == 0)
+ int_rel.r_symndx = RELOC_SECTION_LIT8;
+ else if (strcmp (name, ".lit4") == 0)
+ int_rel.r_symndx = RELOC_SECTION_LIT4;
+ break;
+ case 'r':
+ if (strcmp (name, ".rdata") == 0)
+ int_rel.r_symndx = RELOC_SECTION_RDATA;
+ break;
+ case 's':
+ if (strcmp (name, ".sdata") == 0)
+ int_rel.r_symndx = RELOC_SECTION_SDATA;
+ else if (strcmp (name, ".sbss") == 0)
+ int_rel.r_symndx = RELOC_SECTION_SBSS;
+ break;
+ case 't':
+ if (strcmp (name, ".text") == 0)
+ int_rel.r_symndx = RELOC_SECTION_TEXT;
+ break;
+ }
+
+ if (int_rel.r_symndx == -1)
+ abort ();
+
+ /* Add the section VMA and the symbol value. */
+ relocation = (h->root.u.def.value
+ + s->output_section->vma
+ + s->output_offset);
+
+ /* For a PC relative relocation, the object file
+ 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;
+
+ /* 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 RELHI 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;
+
+ if (! use_lo)
+ addlo = 0;
+ else
+ {
+ addlo = bfd_get_32 (input_bfd,
+ (contents
+ + adjust
+ + lo_int_rel.r_vaddr
+ - input_section->vma));
+ addlo &= 0xffff;
+ if (addlo & 0x8000)
+ addlo -= 0x10000;
+
+ lo_int_rel.r_offset = addhi + addlo;
+ }
+
+ int_rel.r_offset = addhi + addlo;
+ }
+ }
+
+ h = NULL;
+ }
+ else
+ {
+ /* Change the symndx value to the right one for the
+ output BFD. */
+ int_rel.r_symndx = h->indx;
+ if (int_rel.r_symndx == -1)
+ {
+ /* This symbol is not being written out. */
+ if (! ((*info->callbacks->unattached_reloc)
+ (info, h->root.root.string, input_bfd,
+ input_section,
+ int_rel.r_vaddr - input_section->vma)))
+ return false;
+ int_rel.r_symndx = 0;
+ }
+ relocation = 0;
+ }
+ }
+ else
+ {
+ /* This is a relocation against a section. Adjust the
+ value by the amount the section moved. */
+ relocation = (s->output_section->vma
+ + s->output_offset
+ - s->vma);
+ }
+
+ relocation += addend;
+ addend = 0;
+
+ /* Adjust a PC relative relocation by removing the reference
+ to the original address in the section and including the
+ 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);
+
+ /* Adjust the contents. */
+ if (relocation == 0)
+ r = bfd_reloc_ok;
+ else
+ {
+ 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
+ + int_rel.r_vaddr
+ - input_section->vma));
+ else
+ {
+ mips_relocate_hi (&int_rel,
+ use_lo ? &lo_int_rel : NULL,
+ input_bfd, input_section, contents,
+ adjust, relocation,
+ int_rel.r_type == MIPS_R_RELHI);
+ r = bfd_reloc_ok;
+ }
+ }
+
+ /* Adjust the reloc address. */
+ int_rel.r_vaddr += (input_section->output_section->vma
+ + input_section->output_offset
+ - input_section->vma);
+
+ /* Save the changed reloc information. */
+ mips_ecoff_swap_reloc_out (input_bfd, &int_rel, (PTR) ext_rel);
+ }
+ else
+ {
+ /* We are producing a final executable. */
+ if (int_rel.r_extern)
+ {
+ /* This is a reloc against a symbol. */
+ if (h->root.type == bfd_link_hash_defined
+ || h->root.type == bfd_link_hash_defweak)
+ {
+ asection *hsec;
+
+ hsec = h->root.u.def.section;
+ relocation = (h->root.u.def.value
+ + hsec->output_section->vma
+ + hsec->output_offset);
+ }
+ else
+ {
+ if (! ((*info->callbacks->undefined_symbol)
+ (info, h->root.root.string, input_bfd,
+ input_section,
+ int_rel.r_vaddr - input_section->vma, true)))
+ return false;
+ relocation = 0;
+ }
+ }
+ else
+ {
+ /* This is a reloc against a section. */
+ relocation = (s->output_section->vma
+ + s->output_offset
+ - s->vma);
+
+ /* A PC relative reloc is already correct in the object
+ file. Make it look like a pcrel_offset relocation by
+ adding in the start address. */
+ if (howto->pc_relative)
+ {
+ if (int_rel.r_type != MIPS_R_RELHI || ! use_lo)
+ relocation += int_rel.r_vaddr + adjust;
+ else
+ relocation += lo_int_rel.r_vaddr + adjust;
+ }
+ }
+
+ 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,
+ contents,
+ (int_rel.r_vaddr
+ - input_section->vma
+ + adjust),
+ relocation,
+ addend);
+ else
+ {
+ mips_relocate_hi (&int_rel,
+ use_lo ? &lo_int_rel : NULL,
+ input_bfd, input_section, contents, adjust,
+ relocation,
+ int_rel.r_type == MIPS_R_RELHI);
+ r = bfd_reloc_ok;
+ }
+ }
+
+ /* MIPS_R_JMPADDR requires peculiar overflow detection. The
+ instruction provides a 28 bit address (the two lower bits are
+ implicit zeroes) which is combined with the upper four bits
+ of the instruction address. */
+ if (r == bfd_reloc_ok
+ && int_rel.r_type == MIPS_R_JMPADDR
+ && (((relocation
+ + addend
+ + (int_rel.r_extern ? 0 : s->vma))
+ & 0xf0000000)
+ != ((input_section->output_section->vma
+ + input_section->output_offset
+ + (int_rel.r_vaddr - input_section->vma)
+ + adjust)
+ & 0xf0000000)))
+ r = bfd_reloc_overflow;
+
+ if (r != bfd_reloc_ok)
+ {
+ switch (r)
+ {
+ default:
+ case bfd_reloc_outofrange:
+ abort ();
+ case bfd_reloc_overflow:
+ {
+ const char *name;
+
+ if (int_rel.r_extern)
+ name = h->root.root.string;
+ else
+ name = bfd_section_name (input_bfd, s);
+ if (! ((*info->callbacks->reloc_overflow)
+ (info, name, howto->name, (bfd_vma) 0,
+ input_bfd, input_section,
+ int_rel.r_vaddr - input_section->vma)))
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+ return true;