+#undef GET_OP
+
+ /* Handle undefined instructions. */
+ info->insn_type = dis_noninsn;
+ infprintf (is, "0x%x", word);
+ return INSNLEN;
+}
+\f
+/* Disassemble an operand for a mips16 instruction. */
+
+static void
+print_mips16_insn_arg (struct disassemble_info *info,
+ struct mips_print_arg_state *state,
+ const struct mips_opcode *opcode,
+ char type, bfd_vma memaddr,
+ unsigned insn, bfd_boolean use_extend,
+ unsigned extend, bfd_boolean is_offset)
+{
+ const fprintf_ftype infprintf = info->fprintf_func;
+ void *is = info->stream;
+ const struct mips_operand *operand, *ext_operand;
+ unsigned short ext_size;
+ unsigned int uval;
+ bfd_vma baseaddr;
+
+ if (!use_extend)
+ extend = 0;
+
+ switch (type)
+ {
+ case ',':
+ case '(':
+ case ')':
+ infprintf (is, "%c", type);
+ break;
+
+ default:
+ operand = decode_mips16_operand (type, FALSE);
+ if (!operand)
+ {
+ /* xgettext:c-format */
+ infprintf (is, _("# internal error, undefined operand in `%s %s'"),
+ opcode->name, opcode->args);
+ return;
+ }
+
+ if (operand->type == OP_SAVE_RESTORE_LIST)
+ {
+ /* Handle this case here because of the complex interaction
+ with the EXTEND opcode. */
+ unsigned int amask = extend & 0xf;
+ unsigned int nsreg = (extend >> 8) & 0x7;
+ unsigned int ra = insn & 0x40; /* $ra */
+ unsigned int s0 = insn & 0x20; /* $s0 */
+ unsigned int s1 = insn & 0x10; /* $s1 */
+ unsigned int frame_size = ((extend & 0xf0) | (insn & 0x0f)) * 8;
+ if (frame_size == 0 && !use_extend)
+ frame_size = 128;
+ mips_print_save_restore (info, amask, nsreg, ra, s0, s1, frame_size);
+ break;
+ }
+
+ if (is_offset && operand->type == OP_INT)
+ {
+ const struct mips_int_operand *int_op;
+
+ int_op = (const struct mips_int_operand *) operand;
+ info->insn_type = dis_dref;
+ info->data_size = 1 << int_op->shift;
+ }
+
+ ext_size = 0;
+ if (use_extend)
+ {
+ ext_operand = decode_mips16_operand (type, TRUE);
+ if (ext_operand != operand
+ || (operand->type == OP_INT && operand->lsb == 0
+ && mips_opcode_32bit_p (opcode)))
+ {
+ ext_size = ext_operand->size;
+ operand = ext_operand;
+ }
+ }
+ if (operand->size == 26)
+ uval = ((extend & 0x1f) << 21) | ((extend & 0x3e0) << 11) | insn;
+ else if (ext_size == 16 || ext_size == 9)
+ uval = ((extend & 0x1f) << 11) | (extend & 0x7e0) | (insn & 0x1f);
+ else if (ext_size == 15)
+ uval = ((extend & 0xf) << 11) | (extend & 0x7f0) | (insn & 0xf);
+ else if (ext_size == 6)
+ uval = ((extend >> 6) & 0x1f) | (extend & 0x20);
+ else
+ uval = mips_extract_operand (operand, (extend << 16) | insn);
+ if (ext_size == 9)
+ uval &= (1U << ext_size) - 1;
+
+ baseaddr = memaddr + 2;
+ if (operand->type == OP_PCREL)
+ {
+ const struct mips_pcrel_operand *pcrel_op;
+
+ pcrel_op = (const struct mips_pcrel_operand *) operand;
+ if (!pcrel_op->include_isa_bit && use_extend)
+ baseaddr = memaddr - 2;
+ else if (!pcrel_op->include_isa_bit)
+ {
+ bfd_byte buffer[2];
+
+ /* If this instruction is in the delay slot of a JAL/JALX
+ instruction, the base address is the address of the
+ JAL/JALX instruction. If it is in the delay slot of
+ a JR/JALR instruction, the base address is the address
+ of the JR/JALR instruction. This test is unreliable:
+ we have no way of knowing whether the previous word is
+ instruction or data. */
+ if (info->read_memory_func (memaddr - 4, buffer, 2, info) == 0
+ && (((info->endian == BFD_ENDIAN_BIG
+ ? bfd_getb16 (buffer)
+ : bfd_getl16 (buffer))
+ & 0xf800) == 0x1800))
+ baseaddr = memaddr - 4;
+ else if (info->read_memory_func (memaddr - 2, buffer, 2,
+ info) == 0
+ && (((info->endian == BFD_ENDIAN_BIG
+ ? bfd_getb16 (buffer)
+ : bfd_getl16 (buffer))
+ & 0xf89f) == 0xe800)
+ && (((info->endian == BFD_ENDIAN_BIG
+ ? bfd_getb16 (buffer)
+ : bfd_getl16 (buffer))
+ & 0x0060) != 0x0060))
+ baseaddr = memaddr - 2;
+ else
+ baseaddr = memaddr;
+ }
+ }
+
+ print_insn_arg (info, state, opcode, operand, baseaddr + 1, uval);
+ break;
+ }
+}
+
+
+/* Check if the given address is the last word of a MIPS16 PLT entry.
+ This word is data and depending on the value it may interfere with
+ disassembly of further PLT entries. We make use of the fact PLT
+ symbols are marked BSF_SYNTHETIC. */
+static bfd_boolean
+is_mips16_plt_tail (struct disassemble_info *info, bfd_vma addr)
+{
+ if (info->symbols
+ && info->symbols[0]
+ && (info->symbols[0]->flags & BSF_SYNTHETIC)
+ && addr == bfd_asymbol_value (info->symbols[0]) + 12)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Whether none, a 32-bit or a 16-bit instruction match has been done. */
+
+enum match_kind
+{
+ MATCH_NONE,
+ MATCH_FULL,
+ MATCH_SHORT
+};
+
+/* Disassemble mips16 instructions. */
+
+static int
+print_insn_mips16 (bfd_vma memaddr, struct disassemble_info *info)
+{
+ const fprintf_ftype infprintf = info->fprintf_func;
+ int status;
+ bfd_byte buffer[4];
+ const struct mips_opcode *op, *opend;
+ struct mips_print_arg_state state;
+ void *is = info->stream;
+ bfd_boolean have_second;
+ bfd_boolean extend_only;
+ unsigned int second;
+ unsigned int first;
+ unsigned int full;
+
+ info->bytes_per_chunk = 2;
+ info->display_endian = info->endian;
+ info->insn_info_valid = 1;
+ info->branch_delay_insns = 0;
+ info->data_size = 0;
+ info->target = 0;
+ info->target2 = 0;
+
+#define GET_OP(insn, field) \
+ (((insn) >> MIPS16OP_SH_##field) & MIPS16OP_MASK_##field)
+ /* Decode PLT entry's GOT slot address word. */
+ if (is_mips16_plt_tail (info, memaddr))
+ {
+ info->insn_type = dis_noninsn;
+ status = (*info->read_memory_func) (memaddr, buffer, 4, info);
+ if (status == 0)
+ {
+ unsigned int gotslot;
+
+ if (info->endian == BFD_ENDIAN_BIG)
+ gotslot = bfd_getb32 (buffer);
+ else
+ gotslot = bfd_getl32 (buffer);
+ infprintf (is, ".word\t0x%x", gotslot);
+
+ return 4;
+ }
+ }
+ else
+ {
+ info->insn_type = dis_nonbranch;
+ status = (*info->read_memory_func) (memaddr, buffer, 2, info);
+ }
+ if (status != 0)
+ {
+ (*info->memory_error_func) (status, memaddr, info);
+ return -1;
+ }
+
+ extend_only = FALSE;
+
+ if (info->endian == BFD_ENDIAN_BIG)
+ first = bfd_getb16 (buffer);
+ else
+ first = bfd_getl16 (buffer);
+
+ status = (*info->read_memory_func) (memaddr + 2, buffer, 2, info);
+ if (status == 0)
+ {
+ have_second = TRUE;
+ if (info->endian == BFD_ENDIAN_BIG)
+ second = bfd_getb16 (buffer);
+ else
+ second = bfd_getl16 (buffer);
+ full = (first << 16) | second;
+ }
+ else
+ {
+ have_second = FALSE;
+ second = 0;
+ full = first;
+ }
+
+ /* FIXME: Should probably use a hash table on the major opcode here. */
+
+ opend = mips16_opcodes + bfd_mips16_num_opcodes;
+ for (op = mips16_opcodes; op < opend; op++)
+ {
+ enum match_kind match;
+
+ if (!opcode_is_member (op, mips_isa, mips_ase, mips_processor))
+ continue;
+
+ if (op->pinfo == INSN_MACRO
+ || (no_aliases && (op->pinfo2 & INSN2_ALIAS)))
+ match = MATCH_NONE;
+ else if (mips_opcode_32bit_p (op))
+ {
+ if (have_second
+ && (full & op->mask) == op->match)
+ match = MATCH_FULL;
+ else
+ match = MATCH_NONE;
+ }
+ else if ((first & op->mask) == op->match)
+ {
+ match = MATCH_SHORT;
+ second = 0;
+ full = first;
+ }
+ else if ((first & 0xf800) == 0xf000
+ && have_second
+ && !extend_only
+ && (second & op->mask) == op->match)
+ {
+ if (op->pinfo2 & INSN2_SHORT_ONLY)
+ {
+ match = MATCH_NONE;
+ extend_only = TRUE;
+ }
+ else
+ match = MATCH_FULL;
+ }
+ else
+ match = MATCH_NONE;
+
+ if (match != MATCH_NONE)
+ {
+ const char *s;
+
+ infprintf (is, "%s", op->name);
+ if (op->args[0] != '\0')
+ infprintf (is, "\t");
+
+ init_print_arg_state (&state);
+ for (s = op->args; *s != '\0'; s++)
+ {
+ if (*s == ','
+ && s[1] == 'w'
+ && GET_OP (full, RX) == GET_OP (full, RY))
+ {
+ /* Skip the register and the comma. */
+ ++s;
+ continue;
+ }
+ if (*s == ','
+ && s[1] == 'v'
+ && GET_OP (full, RZ) == GET_OP (full, RX))
+ {
+ /* Skip the register and the comma. */
+ ++s;
+ continue;
+ }
+ if (s[0] == 'N'
+ && s[1] == ','
+ && s[2] == 'O'
+ && op->name[strlen (op->name) - 1] == '0')
+ {
+ /* Coprocessor register 0 with sel field. */
+ const struct mips_cp0sel_name *n;
+ const struct mips_operand *operand;
+ unsigned int reg, sel;
+
+ operand = decode_mips16_operand (*s, TRUE);
+ reg = mips_extract_operand (operand, (first << 16) | second);
+ s += 2;
+ operand = decode_mips16_operand (*s, TRUE);
+ sel = mips_extract_operand (operand, (first << 16) | second);
+
+ /* CP0 register including 'sel' code for mftc0, to be
+ printed textually if known. If not known, print both
+ CP0 register name and sel numerically since CP0 register
+ with sel 0 may have a name unrelated to register being
+ printed. */
+ n = lookup_mips_cp0sel_name (mips_cp0sel_names,
+ mips_cp0sel_names_len,
+ reg, sel);
+ if (n != NULL)
+ infprintf (is, "%s", n->name);
+ else
+ infprintf (is, "$%d,%d", reg, sel);
+ }
+ else
+ switch (match)
+ {
+ case MATCH_FULL:
+ print_mips16_insn_arg (info, &state, op, *s, memaddr + 2,
+ second, TRUE, first, s[1] == '(');
+ break;
+ case MATCH_SHORT:
+ print_mips16_insn_arg (info, &state, op, *s, memaddr,
+ first, FALSE, 0, s[1] == '(');
+ break;
+ case MATCH_NONE: /* Stop the compiler complaining. */
+ break;
+ }
+ }
+
+ /* Figure out branch instruction type and delay slot information. */
+ if ((op->pinfo & INSN_UNCOND_BRANCH_DELAY) != 0)
+ info->branch_delay_insns = 1;
+ if ((op->pinfo & INSN_UNCOND_BRANCH_DELAY) != 0
+ || (op->pinfo2 & INSN2_UNCOND_BRANCH) != 0)
+ {
+ if ((op->pinfo & INSN_WRITE_GPR_31) != 0)
+ info->insn_type = dis_jsr;
+ else
+ info->insn_type = dis_branch;
+ }
+ else if ((op->pinfo2 & INSN2_COND_BRANCH) != 0)
+ info->insn_type = dis_condbranch;
+
+ return match == MATCH_FULL ? 4 : 2;
+ }
+ }
+#undef GET_OP