+ struct ecoff_section_tdata *section_tdata;
+ bfd_byte *contents = NULL;
+ long *offsets;
+ struct external_reloc *ext_rel;
+ struct external_reloc *ext_rel_end;
+ unsigned int i;
+
+ /* Assume we are not going to need another pass. */
+ *again = false;
+
+ /* If we are not generating an ECOFF file, this is much too
+ confusing to deal with. */
+ if (info->hash->creator->flavour != bfd_get_flavour (abfd))
+ return true;
+
+ /* If there are no relocs, there is nothing to do. */
+ if (sec->reloc_count == 0)
+ return true;
+
+ /* We are only interested in PC relative relocs, and why would there
+ ever be one from anything but the .text section? */
+ if (strcmp (bfd_get_section_name (abfd, sec), ".text") != 0)
+ return true;
+
+ /* Read in the relocs, if we haven't already got them. */
+ section_tdata = ecoff_section_data (abfd, sec);
+ if (section_tdata == (struct ecoff_section_tdata *) NULL
+ || section_tdata->external_relocs == NULL)
+ {
+ if (! mips_read_relocs (abfd, sec))
+ goto error_return;
+ section_tdata = ecoff_section_data (abfd, sec);
+ }
+
+ if (sec->_cooked_size == 0)
+ {
+ /* We must initialize _cooked_size only the first time we are
+ called. */
+ sec->_cooked_size = sec->_raw_size;
+ }
+
+ contents = section_tdata->contents;
+ offsets = section_tdata->offsets;
+
+ /* Look for any external PC relative relocs. Internal PC relative
+ relocs are already correct in the object file, so they certainly
+ can not overflow. */
+ ext_rel = (struct external_reloc *) section_tdata->external_relocs;
+ ext_rel_end = ext_rel + sec->reloc_count;
+ for (i = 0; ext_rel < ext_rel_end; ext_rel++, i++)
+ {
+ struct internal_reloc int_rel;
+ struct ecoff_link_hash_entry *h;
+ asection *hsec;
+ bfd_signed_vma relocation;
+ struct external_reloc *adj_ext_rel;
+ unsigned int adj_i;
+ unsigned long ext_count;
+ struct ecoff_link_hash_entry **adj_h_ptr;
+ struct ecoff_link_hash_entry **adj_h_ptr_end;
+ struct ecoff_value_adjust *adjust;
+
+ /* If we have already expanded this reloc, we certainly don't
+ need to do it again. */
+ if (offsets != (long *) NULL && offsets[i] == 1)
+ continue;
+
+ /* Quickly check that this reloc is external PCREL16. */
+ if (bfd_header_big_endian (abfd))
+ {
+ if ((ext_rel->r_bits[3] & RELOC_BITS3_EXTERN_BIG) == 0
+ || (((ext_rel->r_bits[3] & RELOC_BITS3_TYPE_BIG)
+ >> RELOC_BITS3_TYPE_SH_BIG)
+ != MIPS_R_PCREL16))
+ continue;
+ }
+ else
+ {
+ if ((ext_rel->r_bits[3] & RELOC_BITS3_EXTERN_LITTLE) == 0
+ || (((ext_rel->r_bits[3] & RELOC_BITS3_TYPE_LITTLE)
+ >> RELOC_BITS3_TYPE_SH_LITTLE)
+ != MIPS_R_PCREL16))
+ continue;
+ }
+
+ mips_ecoff_swap_reloc_in (abfd, (PTR) ext_rel, &int_rel);
+
+ h = ecoff_data (abfd)->sym_hashes[int_rel.r_symndx];
+ if (h == (struct ecoff_link_hash_entry *) NULL)
+ abort ();
+
+ if (h->root.type != bfd_link_hash_defined
+ && h->root.type != bfd_link_hash_defweak)
+ {
+ /* Just ignore undefined symbols. These will presumably
+ generate an error later in the link. */
+ continue;
+ }
+
+ /* Get the value of the symbol. */
+ hsec = h->root.u.def.section;
+ relocation = (h->root.u.def.value
+ + hsec->output_section->vma
+ + hsec->output_offset);
+
+ /* Subtract out the current address. */
+ relocation -= (sec->output_section->vma
+ + sec->output_offset
+ + (int_rel.r_vaddr - sec->vma));
+
+ /* The addend is stored in the object file. In the normal case
+ of ``bal symbol'', the addend will be -4. It will only be
+ different in the case of ``bal symbol+constant''. To avoid
+ always reading in the section contents, we don't check the
+ addend in the object file (we could easily check the contents
+ if we happen to have already read them in, but I fear that
+ this could be confusing). This means we will screw up if
+ there is a branch to a symbol that is in range, but added to
+ a constant which puts it out of range; in such a case the
+ link will fail with a reloc overflow error. Since the
+ compiler will never generate such code, it should be easy
+ enough to work around it by changing the assembly code in the
+ source file. */
+ relocation -= 4;
+
+ /* Now RELOCATION is the number we want to put in the object
+ file. See whether it fits. */
+ if (relocation >= -0x20000 && relocation < 0x20000)
+ continue;
+
+ /* Now that we know this reloc needs work, which will rarely
+ happen, go ahead and grab the section contents. */
+ if (contents == (bfd_byte *) NULL)
+ {
+ if (info->keep_memory)
+ contents = (bfd_byte *) bfd_alloc (abfd, sec->_raw_size);
+ else
+ contents = (bfd_byte *) bfd_malloc ((size_t) sec->_raw_size);
+ if (contents == (bfd_byte *) NULL)
+ goto error_return;
+ if (! bfd_get_section_contents (abfd, sec, (PTR) contents,
+ (file_ptr) 0, sec->_raw_size))
+ goto error_return;
+ if (info->keep_memory)
+ section_tdata->contents = contents;
+ }
+
+ /* We only support changing the bal instruction. It would be
+ possible to handle other PC relative branches, but some of
+ them (the conditional branches) would require a different
+ length instruction sequence which would complicate both this
+ routine and mips_relax_pcrel16. It could be written if
+ somebody felt it were important. Ignoring this reloc will
+ presumably cause a reloc overflow error later on. */
+ if (bfd_get_32 (abfd, contents + int_rel.r_vaddr - sec->vma)
+ != 0x0411ffff) /* bgezal $0,. == bal . */
+ continue;
+
+ /* Bother. We need to expand this reloc, and we will need to
+ make another relaxation pass since this change may put other
+ relocs out of range. We need to examine the local branches
+ and we need to allocate memory to hold the offsets we must
+ add to them. We also need to adjust the values of all
+ symbols in the object file following this location. */
+
+ sec->_cooked_size += PCREL16_EXPANSION_ADJUSTMENT;
+ *again = true;
+
+ if (offsets == (long *) NULL)
+ {
+ size_t size;
+
+ size = sec->reloc_count * sizeof (long);
+ offsets = (long *) bfd_alloc (abfd, size);
+ if (offsets == (long *) NULL)
+ goto error_return;
+ memset (offsets, 0, size);
+ section_tdata->offsets = offsets;
+ }
+
+ offsets[i] = 1;
+
+ /* 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++)
+ {
+ struct internal_reloc adj_int_rel;
+ bfd_vma start, stop;
+ int change;
+
+ mips_ecoff_swap_reloc_in (abfd, (PTR) adj_ext_rel, &adj_int_rel);
+
+ if (adj_int_rel.r_type == MIPS_R_PCREL16)
+ {
+ unsigned long insn;
+
+ /* 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
+ relocs may not be handled correctly; does anybody
+ care? */
+ if (adj_int_rel.r_symndx != RELOC_SECTION_TEXT)
+ continue;
+
+ start = adj_int_rel.r_vaddr;
+
+ insn = bfd_get_32 (abfd,
+ contents + adj_int_rel.r_vaddr - sec->vma);
+
+ stop = (insn & 0xffff) << 2;
+ if ((stop & 0x20000) != 0)
+ stop -= 0x40000;
+ stop += adj_int_rel.r_vaddr + 4;
+ }
+ 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)
+ {
+ /* A MIPS_R_SWITCH reloc represents a word of the form
+ .word $L3-$LS12
+ The value in the object file is correct, assuming the
+ original value of $L3. The symndx value is actually
+ the difference between the reloc address and $LS12.
+ This lets us compute the original value of $LS12 as
+ vaddr - symndx
+ and the original value of $L3 as
+ vaddr - symndx + addend
+ where addend is the value from the object file. At
+ 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;
+
+ offsets[adj_i] += change;
+
+ if (adj_int_rel.r_type == MIPS_R_RELHI)
+ {
+ adj_ext_rel++;
+ adj_i++;
+ offsets[adj_i] += change;
+ }
+ }
+
+ /* Find all symbols in this section defined by this object file
+ and adjust their values. Note that we decide whether to
+ adjust the value based on the value stored in the ECOFF EXTR
+ structure, because the value stored in the hash table may
+ have been changed by an earlier expanded reloc and thus may
+ no longer correctly indicate whether the symbol is before or
+ after the expanded reloc. */
+ ext_count = ecoff_data (abfd)->debug_info.symbolic_header.iextMax;
+ adj_h_ptr = ecoff_data (abfd)->sym_hashes;
+ adj_h_ptr_end = adj_h_ptr + ext_count;
+ for (; adj_h_ptr < adj_h_ptr_end; adj_h_ptr++)
+ {
+ struct ecoff_link_hash_entry *adj_h;
+
+ adj_h = *adj_h_ptr;
+ if (adj_h != (struct ecoff_link_hash_entry *) NULL
+ && (adj_h->root.type == bfd_link_hash_defined
+ || adj_h->root.type == bfd_link_hash_defweak)
+ && adj_h->root.u.def.section == sec
+ && adj_h->esym.asym.value > int_rel.r_vaddr)
+ adj_h->root.u.def.value += PCREL16_EXPANSION_ADJUSTMENT;
+ }
+
+ /* Add an entry to the symbol value adjust list. This is used
+ by bfd_ecoff_debug_accumulate to adjust the values of
+ internal symbols and FDR's. */
+ adjust = ((struct ecoff_value_adjust *)
+ bfd_alloc (abfd, sizeof (struct ecoff_value_adjust)));
+ if (adjust == (struct ecoff_value_adjust *) NULL)
+ goto error_return;
+
+ adjust->start = int_rel.r_vaddr;
+ adjust->end = sec->vma + sec->_raw_size;
+ adjust->adjust = PCREL16_EXPANSION_ADJUSTMENT;
+
+ adjust->next = ecoff_data (abfd)->debug_info.adjust;
+ ecoff_data (abfd)->debug_info.adjust = adjust;
+ }
+
+ if (contents != (bfd_byte *) NULL && ! info->keep_memory)
+ free (contents);
+
+ return true;
+
+ error_return:
+ if (contents != (bfd_byte *) NULL && ! info->keep_memory)
+ free (contents);
+ return false;