+/* Swap out a MIPS 64-bit Rel reloc. */
+
+static void
+mips_elf64_be_swap_reloc_out (bfd *abfd, const Elf_Internal_Rela *src,
+ bfd_byte *dst)
+{
+ Elf64_Mips_Internal_Rela mirel;
+
+ mirel.r_offset = src[0].r_offset;
+ BFD_ASSERT(src[0].r_offset == src[1].r_offset);
+
+ mirel.r_type = ELF64_MIPS_R_TYPE (src[0].r_info);
+ mirel.r_sym = ELF64_R_SYM (src[0].r_info);
+ mirel.r_type2 = ELF64_MIPS_R_TYPE (src[1].r_info);
+ mirel.r_ssym = ELF64_MIPS_R_SSYM (src[1].r_info);
+ mirel.r_type3 = ELF64_MIPS_R_TYPE (src[2].r_info);
+
+ mips_elf64_swap_reloc_out (abfd, &mirel,
+ (Elf64_Mips_External_Rel *) dst);
+}
+
+/* Swap out a MIPS 64-bit Rela reloc. */
+
+static void
+mips_elf64_be_swap_reloca_out (bfd *abfd, const Elf_Internal_Rela *src,
+ bfd_byte *dst)
+{
+ Elf64_Mips_Internal_Rela mirela;
+
+ mirela.r_offset = src[0].r_offset;
+ BFD_ASSERT(src[0].r_offset == src[1].r_offset);
+ BFD_ASSERT(src[0].r_offset == src[2].r_offset);
+
+ mirela.r_type = ELF64_MIPS_R_TYPE (src[0].r_info);
+ mirela.r_sym = ELF64_R_SYM (src[0].r_info);
+ mirela.r_addend = src[0].r_addend;
+ BFD_ASSERT(src[1].r_addend == 0);
+ BFD_ASSERT(src[2].r_addend == 0);
+
+ mirela.r_type2 = ELF64_MIPS_R_TYPE (src[1].r_info);
+ mirela.r_ssym = ELF64_MIPS_R_SSYM (src[1].r_info);
+ mirela.r_type3 = ELF64_MIPS_R_TYPE (src[2].r_info);
+
+ mips_elf64_swap_reloca_out (abfd, &mirela,
+ (Elf64_Mips_External_Rela *) dst);
+}
+\f
+/* Set the GP value for OUTPUT_BFD. Returns FALSE if this is a
+ dangerous relocation. */
+
+static bfd_boolean
+mips_elf64_assign_gp (bfd *output_bfd, bfd_vma *pgp)
+{
+ unsigned int count;
+ asymbol **sym;
+ unsigned int i;
+
+ /* If we've already figured out what GP will be, just return it. */
+ *pgp = _bfd_get_gp_value (output_bfd);
+ if (*pgp)
+ return TRUE;
+
+ count = bfd_get_symcount (output_bfd);
+ sym = bfd_get_outsymbols (output_bfd);
+
+ /* The linker script will have created a symbol named `_gp' with the
+ appropriate value. */
+ if (sym == NULL)
+ i = count;
+ else
+ {
+ for (i = 0; i < count; i++, sym++)
+ {
+ register const char *name;
+
+ name = bfd_asymbol_name (*sym);
+ if (*name == '_' && strcmp (name, "_gp") == 0)
+ {
+ *pgp = bfd_asymbol_value (*sym);
+ _bfd_set_gp_value (output_bfd, *pgp);
+ break;
+ }
+ }
+ }
+
+ if (i >= count)
+ {
+ /* Only get the error once. */
+ *pgp = 4;
+ _bfd_set_gp_value (output_bfd, *pgp);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* We have to figure out the gp value, so that we can adjust the
+ symbol value correctly. We look up the symbol _gp in the output
+ BFD. If we can't find it, we're stuck. We cache it in the ELF
+ target data. We don't need to adjust the symbol value for an
+ external symbol if we are producing relocatable output. */
+
+static bfd_reloc_status_type
+mips_elf64_final_gp (bfd *output_bfd, asymbol *symbol, bfd_boolean relocatable,
+ char **error_message, bfd_vma *pgp)
+{
+ if (bfd_is_und_section (symbol->section)
+ && ! relocatable)
+ {
+ *pgp = 0;
+ return bfd_reloc_undefined;
+ }
+
+ *pgp = _bfd_get_gp_value (output_bfd);
+ if (*pgp == 0
+ && (! relocatable
+ || (symbol->flags & BSF_SECTION_SYM) != 0))
+ {
+ if (relocatable)
+ {
+ /* Make up a value. */
+ *pgp = symbol->section->output_section->vma /*+ 0x4000*/;
+ _bfd_set_gp_value (output_bfd, *pgp);
+ }
+ else if (!mips_elf64_assign_gp (output_bfd, pgp))
+ {
+ *error_message =
+ (char *) _("GP relative relocation when _gp not defined");
+ return bfd_reloc_dangerous;
+ }
+ }
+
+ return bfd_reloc_ok;
+}
+
+/* Do a R_MIPS_GPREL16 relocation. This is a 16 bit value which must
+ become the offset from the gp register. */
+
+static bfd_reloc_status_type
+mips_elf64_gprel16_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
+ void *data, asection *input_section, bfd *output_bfd,
+ char **error_message)
+{
+ bfd_boolean relocatable;
+ bfd_reloc_status_type ret;
+ bfd_vma gp;
+
+ /* If we're relocating, and this is an external symbol, we don't want
+ to change anything. */
+ if (output_bfd != NULL
+ && (symbol->flags & BSF_SECTION_SYM) == 0
+ && (symbol->flags & BSF_LOCAL) != 0)
+ {
+ reloc_entry->address += input_section->output_offset;
+ return bfd_reloc_ok;
+ }
+
+ if (output_bfd != NULL)
+ relocatable = TRUE;
+ else
+ {
+ relocatable = FALSE;
+ output_bfd = symbol->section->output_section->owner;
+ }
+
+ ret = mips_elf64_final_gp (output_bfd, symbol, relocatable, error_message,
+ &gp);
+ if (ret != bfd_reloc_ok)
+ return ret;
+
+ return _bfd_mips_elf_gprel16_with_gp (abfd, symbol, reloc_entry,
+ input_section, relocatable,
+ data, gp);
+}
+
+/* Do a R_MIPS_LITERAL relocation. */
+
+static bfd_reloc_status_type
+mips_elf64_literal_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
+ void *data, asection *input_section, bfd *output_bfd,
+ char **error_message)
+{
+ bfd_boolean relocatable;
+ bfd_reloc_status_type ret;
+ bfd_vma gp;
+
+ /* R_MIPS_LITERAL relocations are defined for local symbols only. */
+ if (output_bfd != NULL
+ && (symbol->flags & BSF_SECTION_SYM) == 0
+ && (symbol->flags & BSF_LOCAL) != 0)
+ {
+ *error_message = (char *)
+ _("literal relocation occurs for an external symbol");
+ return bfd_reloc_outofrange;
+ }
+
+ /* FIXME: The entries in the .lit8 and .lit4 sections should be merged. */
+ if (output_bfd != NULL)
+ relocatable = TRUE;
+ else
+ {
+ relocatable = FALSE;
+ output_bfd = symbol->section->output_section->owner;
+ }
+
+ ret = mips_elf64_final_gp (output_bfd, symbol, relocatable, error_message,
+ &gp);
+ if (ret != bfd_reloc_ok)
+ return ret;
+
+ return _bfd_mips_elf_gprel16_with_gp (abfd, symbol, reloc_entry,
+ input_section, relocatable,
+ data, gp);
+}
+
+/* Do a R_MIPS_GPREL32 relocation. This is a 32 bit value which must
+ become the offset from the gp register. */
+
+static bfd_reloc_status_type
+mips_elf64_gprel32_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
+ void *data, asection *input_section, bfd *output_bfd,
+ char **error_message)
+{
+ bfd_boolean relocatable;
+ bfd_reloc_status_type ret;
+ bfd_vma gp;
+ bfd_vma relocation;
+ bfd_vma val;
+
+ /* R_MIPS_GPREL32 relocations are defined for local symbols only. */
+ if (output_bfd != NULL
+ && (symbol->flags & BSF_SECTION_SYM) == 0
+ && (symbol->flags & BSF_LOCAL) != 0)
+ {
+ *error_message = (char *)
+ _("32bits gp relative relocation occurs for an external symbol");
+ return bfd_reloc_outofrange;
+ }
+
+ if (output_bfd != NULL)
+ relocatable = TRUE;
+ else
+ {
+ relocatable = FALSE;
+ output_bfd = symbol->section->output_section->owner;
+ }
+
+ ret = mips_elf64_final_gp (output_bfd, symbol, relocatable,
+ error_message, &gp);
+ if (ret != bfd_reloc_ok)
+ return ret;
+
+ if (bfd_is_com_section (symbol->section))
+ relocation = 0;
+ else
+ relocation = symbol->value;
+
+ relocation += symbol->section->output_section->vma;
+ relocation += symbol->section->output_offset;
+
+ if (reloc_entry->address > bfd_get_section_limit (abfd, input_section))
+ return bfd_reloc_outofrange;
+
+ /* Set val to the offset into the section or symbol. */
+ val = reloc_entry->addend;
+
+ if (reloc_entry->howto->partial_inplace)
+ val += bfd_get_32 (abfd, (bfd_byte *) data + reloc_entry->address);
+
+ /* Adjust val for the final section location and GP value. If we
+ are producing relocatable output, we don't want to do this for
+ an external symbol. */
+ if (! relocatable
+ || (symbol->flags & BSF_SECTION_SYM) != 0)
+ val += relocation - gp;
+
+ if (reloc_entry->howto->partial_inplace)
+ bfd_put_32 (abfd, val, (bfd_byte *) data + reloc_entry->address);
+ else
+ reloc_entry->addend = val;
+
+ if (relocatable)
+ reloc_entry->address += input_section->output_offset;
+
+ return bfd_reloc_ok;
+}
+
+/* Do a R_MIPS_SHIFT6 relocation. The MSB of the shift is stored at bit 2,
+ the rest is at bits 6-10. The bitpos already got right by the howto. */
+
+static bfd_reloc_status_type
+mips_elf64_shift6_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
+ void *data, asection *input_section, bfd *output_bfd,
+ char **error_message)
+{
+ if (reloc_entry->howto->partial_inplace)
+ {
+ reloc_entry->addend = ((reloc_entry->addend & 0x00007c0)
+ | (reloc_entry->addend & 0x00000800) >> 9);
+ }
+
+ return _bfd_mips_elf_generic_reloc (abfd, reloc_entry, symbol, data,
+ input_section, output_bfd,
+ error_message);
+}
+
+/* Handle a mips16 GP relative reloc. */
+
+static bfd_reloc_status_type
+mips16_gprel_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
+ void *data, asection *input_section, bfd *output_bfd,
+ char **error_message)
+{
+ bfd_boolean relocatable;
+ bfd_reloc_status_type ret;
+ bfd_byte *location;
+ bfd_vma gp;
+
+ /* If we're relocating, and this is an external symbol, we don't want
+ to change anything. */
+ if (output_bfd != NULL
+ && (symbol->flags & BSF_SECTION_SYM) == 0
+ && (symbol->flags & BSF_LOCAL) != 0)
+ {
+ reloc_entry->address += input_section->output_offset;
+ return bfd_reloc_ok;
+ }
+
+ if (output_bfd != NULL)
+ relocatable = TRUE;
+ else
+ {
+ relocatable = FALSE;
+ output_bfd = symbol->section->output_section->owner;
+ }
+
+ ret = mips_elf64_final_gp (output_bfd, symbol, relocatable, error_message,
+ &gp);
+ if (ret != bfd_reloc_ok)
+ return ret;
+
+ location = (bfd_byte *) data + reloc_entry->address;
+ _bfd_mips16_elf_reloc_unshuffle (abfd, reloc_entry->howto->type, FALSE,
+ location);
+ ret = _bfd_mips_elf_gprel16_with_gp (abfd, symbol, reloc_entry,
+ input_section, relocatable,
+ data, gp);
+ _bfd_mips16_elf_reloc_shuffle (abfd, reloc_entry->howto->type, !relocatable,
+ location);
+
+ return ret;
+}
+\f
+/* A mapping from BFD reloc types to MIPS ELF reloc types. */
+
+struct elf_reloc_map {
+ bfd_reloc_code_real_type bfd_val;
+ enum elf_mips_reloc_type elf_val;
+};
+
+static const struct elf_reloc_map mips_reloc_map[] =
+{
+ { BFD_RELOC_NONE, R_MIPS_NONE },
+ { BFD_RELOC_16, R_MIPS_16 },
+ { BFD_RELOC_32, R_MIPS_32 },
+ /* There is no BFD reloc for R_MIPS_REL32. */
+ { BFD_RELOC_64, R_MIPS_64 },
+ { BFD_RELOC_CTOR, R_MIPS_64 },
+ { BFD_RELOC_16_PCREL_S2, R_MIPS_PC16 },
+ { BFD_RELOC_HI16_S, R_MIPS_HI16 },
+ { BFD_RELOC_LO16, R_MIPS_LO16 },
+ { BFD_RELOC_GPREL16, R_MIPS_GPREL16 },
+ { BFD_RELOC_GPREL32, R_MIPS_GPREL32 },
+ { BFD_RELOC_MIPS_JMP, R_MIPS_26 },
+ { BFD_RELOC_MIPS_LITERAL, R_MIPS_LITERAL },
+ { BFD_RELOC_MIPS_GOT16, R_MIPS_GOT16 },
+ { BFD_RELOC_MIPS_CALL16, R_MIPS_CALL16 },
+ { BFD_RELOC_MIPS_SHIFT5, R_MIPS_SHIFT5 },
+ { BFD_RELOC_MIPS_SHIFT6, R_MIPS_SHIFT6 },
+ { BFD_RELOC_MIPS_GOT_DISP, R_MIPS_GOT_DISP },
+ { BFD_RELOC_MIPS_GOT_PAGE, R_MIPS_GOT_PAGE },
+ { BFD_RELOC_MIPS_GOT_OFST, R_MIPS_GOT_OFST },
+ { BFD_RELOC_MIPS_GOT_HI16, R_MIPS_GOT_HI16 },
+ { BFD_RELOC_MIPS_GOT_LO16, R_MIPS_GOT_LO16 },
+ { BFD_RELOC_MIPS_SUB, R_MIPS_SUB },
+ { BFD_RELOC_MIPS_INSERT_A, R_MIPS_INSERT_A },
+ { BFD_RELOC_MIPS_INSERT_B, R_MIPS_INSERT_B },
+ { BFD_RELOC_MIPS_DELETE, R_MIPS_DELETE },
+ { BFD_RELOC_MIPS_HIGHEST, R_MIPS_HIGHEST },
+ { BFD_RELOC_MIPS_HIGHER, R_MIPS_HIGHER },
+ { BFD_RELOC_MIPS_CALL_HI16, R_MIPS_CALL_HI16 },
+ { BFD_RELOC_MIPS_CALL_LO16, R_MIPS_CALL_LO16 },
+ { BFD_RELOC_MIPS_SCN_DISP, R_MIPS_SCN_DISP },
+ { BFD_RELOC_MIPS_REL16, R_MIPS_REL16 },
+ /* Use of R_MIPS_ADD_IMMEDIATE and R_MIPS_PJUMP is deprecated. */
+ { BFD_RELOC_MIPS_RELGOT, R_MIPS_RELGOT },
+ { BFD_RELOC_MIPS_JALR, R_MIPS_JALR },
+ { BFD_RELOC_MIPS_TLS_DTPMOD32, R_MIPS_TLS_DTPMOD32 },
+ { BFD_RELOC_MIPS_TLS_DTPREL32, R_MIPS_TLS_DTPREL32 },
+ { BFD_RELOC_MIPS_TLS_DTPMOD64, R_MIPS_TLS_DTPMOD64 },
+ { BFD_RELOC_MIPS_TLS_DTPREL64, R_MIPS_TLS_DTPREL64 },
+ { BFD_RELOC_MIPS_TLS_GD, R_MIPS_TLS_GD },
+ { BFD_RELOC_MIPS_TLS_LDM, R_MIPS_TLS_LDM },
+ { BFD_RELOC_MIPS_TLS_DTPREL_HI16, R_MIPS_TLS_DTPREL_HI16 },
+ { BFD_RELOC_MIPS_TLS_DTPREL_LO16, R_MIPS_TLS_DTPREL_LO16 },
+ { BFD_RELOC_MIPS_TLS_GOTTPREL, R_MIPS_TLS_GOTTPREL },
+ { BFD_RELOC_MIPS_TLS_TPREL32, R_MIPS_TLS_TPREL32 },
+ { BFD_RELOC_MIPS_TLS_TPREL64, R_MIPS_TLS_TPREL64 },
+ { BFD_RELOC_MIPS_TLS_TPREL_HI16, R_MIPS_TLS_TPREL_HI16 },
+ { BFD_RELOC_MIPS_TLS_TPREL_LO16, R_MIPS_TLS_TPREL_LO16 }
+};
+
+static const struct elf_reloc_map mips16_reloc_map[] =
+{
+ { BFD_RELOC_MIPS16_JMP, R_MIPS16_26 - R_MIPS16_min },
+ { BFD_RELOC_MIPS16_GPREL, R_MIPS16_GPREL - R_MIPS16_min },
+ { BFD_RELOC_MIPS16_HI16_S, R_MIPS16_HI16 - R_MIPS16_min },
+ { BFD_RELOC_MIPS16_LO16, R_MIPS16_LO16 - R_MIPS16_min },
+};
+
+/* Given a BFD reloc type, return a howto structure. */
+
+static reloc_howto_type *
+bfd_elf64_bfd_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
+ bfd_reloc_code_real_type code)
+{
+ unsigned int i;
+ /* FIXME: We default to RELA here instead of choosing the right
+ relocation variant. */
+ reloc_howto_type *howto_table = mips_elf64_howto_table_rela;
+ reloc_howto_type *howto16_table = mips16_elf64_howto_table_rela;
+
+ for (i = 0; i < sizeof (mips_reloc_map) / sizeof (struct elf_reloc_map);
+ i++)
+ {
+ if (mips_reloc_map[i].bfd_val == code)
+ return &howto_table[(int) mips_reloc_map[i].elf_val];
+ }
+
+ for (i = 0; i < sizeof (mips16_reloc_map) / sizeof (struct elf_reloc_map);
+ i++)
+ {
+ if (mips16_reloc_map[i].bfd_val == code)
+ return &howto16_table[(int) mips16_reloc_map[i].elf_val];
+ }
+
+ switch (code)
+ {
+ case BFD_RELOC_VTABLE_INHERIT:
+ return &elf_mips_gnu_vtinherit_howto;
+ case BFD_RELOC_VTABLE_ENTRY:
+ return &elf_mips_gnu_vtentry_howto;
+ default:
+ bfd_set_error (bfd_error_bad_value);
+ return NULL;
+ }
+}
+
+static reloc_howto_type *
+bfd_elf64_bfd_reloc_name_lookup (bfd *abfd ATTRIBUTE_UNUSED,
+ const char *r_name)
+{
+ unsigned int i;
+
+ for (i = 0;
+ i < (sizeof (mips_elf64_howto_table_rela)
+ / sizeof (mips_elf64_howto_table_rela[0])); i++)
+ if (mips_elf64_howto_table_rela[i].name != NULL
+ && strcasecmp (mips_elf64_howto_table_rela[i].name, r_name) == 0)
+ return &mips_elf64_howto_table_rela[i];
+
+ for (i = 0;
+ i < (sizeof (mips16_elf64_howto_table_rela)
+ / sizeof (mips16_elf64_howto_table_rela[0]));
+ i++)
+ if (mips16_elf64_howto_table_rela[i].name != NULL
+ && strcasecmp (mips16_elf64_howto_table_rela[i].name, r_name) == 0)
+ return &mips16_elf64_howto_table_rela[i];
+
+ if (strcasecmp (elf_mips_gnu_vtinherit_howto.name, r_name) == 0)
+ return &elf_mips_gnu_vtinherit_howto;
+ if (strcasecmp (elf_mips_gnu_vtentry_howto.name, r_name) == 0)
+ return &elf_mips_gnu_vtentry_howto;
+ if (strcasecmp (elf_mips_gnu_rel16_s2.name, r_name) == 0)
+ return &elf_mips_gnu_rel16_s2;
+ if (strcasecmp (elf_mips_gnu_rela16_s2.name, r_name) == 0)
+ return &elf_mips_gnu_rela16_s2;
+
+ return NULL;
+}
+
+/* Given a MIPS Elf_Internal_Rel, fill in an arelent structure. */
+
+static reloc_howto_type *
+mips_elf64_rtype_to_howto (unsigned int r_type, bfd_boolean rela_p)
+{
+ switch (r_type)
+ {
+ case R_MIPS_GNU_VTINHERIT:
+ return &elf_mips_gnu_vtinherit_howto;
+ case R_MIPS_GNU_VTENTRY:
+ return &elf_mips_gnu_vtentry_howto;
+ case R_MIPS_GNU_REL16_S2:
+ if (rela_p)
+ return &elf_mips_gnu_rela16_s2;
+ else
+ return &elf_mips_gnu_rel16_s2;
+ default:
+ if (r_type >= R_MIPS16_min && r_type < R_MIPS16_max)
+ {
+ if (rela_p)
+ return &mips16_elf64_howto_table_rela[r_type - R_MIPS16_min];
+ else
+ return &mips16_elf64_howto_table_rel[r_type - R_MIPS16_min];
+ }
+ BFD_ASSERT (r_type < (unsigned int) R_MIPS_max);
+ if (rela_p)
+ return &mips_elf64_howto_table_rela[r_type];
+ else
+ return &mips_elf64_howto_table_rel[r_type];
+ break;
+ }
+}
+
+/* Prevent relocation handling by bfd for MIPS ELF64. */