Files
retrobsd/tools/virtualmips/mips-dis.c
Serge Vakulenko b259f58533 Virtualmips: merge changes from alexfru.
Fix bug in mips16 disassembler.
2015-10-30 10:47:11 -07:00

1288 lines
38 KiB
C

/*
* Print mips instructions.
*
* Copyright 1989, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
* 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009
* Free Software Foundation, Inc.
* Contributed by Nobuyuki Hikichi (hikichi@sra.co.jp).
* Rewritten for VirtualMIPS by Serge Vakulenko.
*
* This file is part of the virtualmips distribution.
* See LICENSE file for terms of the license.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "mips-opcode.h"
#include "mips-opc.c"
#include "mips16-opc.c"
/* FIXME: These are needed to figure out if the code is mips16 or
not. The low bit of the address is often a good indicator. No
symbol table is available when this code runs out in an embedded
system as when it is used for disassembler support in a monitor. */
/* Mips instructions are at maximum this many bytes long. */
#define INSNLEN 4
struct mips_cp0sel_name {
unsigned int cp0reg;
unsigned int sel;
const char *const name;
};
/* The mips16 registers. */
static const unsigned int mips16_to_32_reg_map[] = {
16, 17, 2, 3, 4, 5, 6, 7
};
#define mips16_reg_names(rn) mips_gpr_names[mips16_to_32_reg_map[rn]]
static const char *const mips_gpr_names[32] = {
"zero", "at", "v0", "v1", "a0", "a1", "a2", "a3",
"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
"t8", "t9", "k0", "k1", "gp", "sp", "s8", "ra"
};
static const char *const mips_fpr_names[32] = {
"$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7",
"$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15",
"$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23",
"$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31"
};
static const char *const mips_cp0_names[32] = {
"c0_index", "c0_random", "c0_entrylo0", "c0_entrylo1",
"c0_context", "c0_pagemask", "c0_wired", "c0_hwrena",
"c0_badvaddr", "c0_count", "c0_entryhi", "c0_compare",
"c0_status", "c0_cause", "c0_epc", "c0_prid",
"c0_config", "c0_lladdr", "c0_watchlo", "c0_watchhi",
"c0_xcontext", "$21", "$22", "c0_debug",
"c0_depc", "c0_perfcnt", "c0_errctl", "c0_cacheerr",
"c0_taglo", "c0_taghi", "c0_errorepc", "c0_desave",
};
static const struct mips_cp0sel_name mips_cp0sel_names[] = {
{4, 1, "c0_contextconfig"},
{0, 1, "c0_mvpcontrol"},
{0, 2, "c0_mvpconf0"},
{0, 3, "c0_mvpconf1"},
{1, 1, "c0_vpecontrol"},
{1, 2, "c0_vpeconf0"},
{1, 3, "c0_vpeconf1"},
{1, 4, "c0_yqmask"},
{1, 5, "c0_vpeschedule"},
{1, 6, "c0_vpeschefback"},
{2, 1, "c0_tcstatus"},
{2, 2, "c0_tcbind"},
{2, 3, "c0_tcrestart"},
{2, 4, "c0_tchalt"},
{2, 5, "c0_tccontext"},
{2, 6, "c0_tcschedule"},
{2, 7, "c0_tcschefback"},
{5, 1, "c0_pagegrain"},
{6, 1, "c0_srsconf0"},
{6, 2, "c0_srsconf1"},
{6, 3, "c0_srsconf2"},
{6, 4, "c0_srsconf3"},
{6, 5, "c0_srsconf4"},
{12, 1, "c0_intctl"},
{12, 2, "c0_srsctl"},
{12, 3, "c0_srsmap"},
{15, 1, "c0_ebase"},
{16, 1, "c0_config1"},
{16, 2, "c0_config2"},
{16, 3, "c0_config3"},
{18, 1, "c0_watchlo,1"},
{18, 2, "c0_watchlo,2"},
{18, 3, "c0_watchlo,3"},
{18, 4, "c0_watchlo,4"},
{18, 5, "c0_watchlo,5"},
{18, 6, "c0_watchlo,6"},
{18, 7, "c0_watchlo,7"},
{19, 1, "c0_watchhi,1"},
{19, 2, "c0_watchhi,2"},
{19, 3, "c0_watchhi,3"},
{19, 4, "c0_watchhi,4"},
{19, 5, "c0_watchhi,5"},
{19, 6, "c0_watchhi,6"},
{19, 7, "c0_watchhi,7"},
{23, 1, "c0_tracecontrol"},
{23, 2, "c0_tracecontrol2"},
{23, 3, "c0_usertracedata"},
{23, 4, "c0_tracebpc"},
{25, 1, "c0_perfcnt,1"},
{25, 2, "c0_perfcnt,2"},
{25, 3, "c0_perfcnt,3"},
{25, 4, "c0_perfcnt,4"},
{25, 5, "c0_perfcnt,5"},
{25, 6, "c0_perfcnt,6"},
{25, 7, "c0_perfcnt,7"},
{27, 1, "c0_cacheerr,1"},
{27, 2, "c0_cacheerr,2"},
{27, 3, "c0_cacheerr,3"},
{28, 1, "c0_datalo"},
{28, 2, "c0_taglo1"},
{28, 3, "c0_datalo1"},
{28, 4, "c0_taglo2"},
{28, 5, "c0_datalo2"},
{28, 6, "c0_taglo3"},
{28, 7, "c0_datalo3"},
{29, 1, "c0_datahi"},
{29, 2, "c0_taghi1"},
{29, 3, "c0_datahi1"},
{29, 4, "c0_taghi2"},
{29, 5, "c0_datahi2"},
{29, 6, "c0_taghi3"},
{29, 7, "c0_datahi3"},
};
static const int mips_cp0sel_names_len = sizeof (mips_cp0sel_names) / sizeof (*mips_cp0sel_names);
static const char *const mips_hwr_names[32] = {
"$0", "$1", "$2", "$3", "$4", "$5", "$6", "$7",
"$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15",
"$16", "$17", "$18", "$19", "$20", "$21", "$22", "$23",
"$24", "$25", "$26", "$27", "$28", "$29", "$30", "$31"
};
/*
* If set disassemble as most general inst.
*/
static int no_aliases = 0;
static unsigned target;
static void
print_address (unsigned address, FILE *stream)
{
if (address < 16)
fprintf (stream, "%d", address);
else
fprintf (stream, "0x%x", address);
}
static const struct mips_cp0sel_name *
lookup_mips_cp0sel_name (const struct
mips_cp0sel_name *names, unsigned int len, unsigned int cp0reg,
unsigned int sel)
{
unsigned int i;
for (i = 0; i < len; i++)
if (names[i].cp0reg == cp0reg && names[i].sel == sel)
return &names[i];
return NULL;
}
/*
* 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.
*/
const char *cp0reg_name (unsigned cp0reg, unsigned sel)
{
const struct mips_cp0sel_name *n;
static char name [32];
if (sel == 0)
return mips_cp0_names[cp0reg];
n = lookup_mips_cp0sel_name (mips_cp0sel_names,
mips_cp0sel_names_len, cp0reg, sel);
if (n != NULL)
return n->name;
sprintf (name, "CP0_R[%d,%d]", cp0reg, sel);
return name;
}
/* Print insn arguments for 32/64-bit code. */
static void
print_insn_args (const char *d,
register unsigned long int l,
unsigned pc, FILE *stream, const struct mips_opcode *opp)
{
int op, delta;
unsigned int lsb, msb, msbd;
lsb = 0;
for (; *d != '\0'; d++) {
switch (*d) {
case ',':
case '(':
case ')':
case '[':
case ']':
fprintf (stream, "%c", *d);
break;
case '+':
/* Extension character; switch for second char. */
d++;
switch (*d) {
case '\0':
/* xgettext:c-format */
fprintf (stream,
"# internal error, incomplete extension sequence (+)");
return;
case 'A':
lsb = (l >> OP_SH_SHAMT) & OP_MASK_SHAMT;
fprintf (stream, "0x%x", lsb);
break;
case 'B':
msb = (l >> OP_SH_INSMSB) & OP_MASK_INSMSB;
fprintf (stream, "0x%x", msb - lsb + 1);
break;
case '1':
fprintf (stream, "0x%lx",
(l >> OP_SH_UDI1) & OP_MASK_UDI1);
break;
case '2':
fprintf (stream, "0x%lx",
(l >> OP_SH_UDI2) & OP_MASK_UDI2);
break;
case '3':
fprintf (stream, "0x%lx",
(l >> OP_SH_UDI3) & OP_MASK_UDI3);
break;
case '4':
fprintf (stream, "0x%lx",
(l >> OP_SH_UDI4) & OP_MASK_UDI4);
break;
case 'C':
case 'H':
msbd = (l >> OP_SH_EXTMSBD) & OP_MASK_EXTMSBD;
fprintf (stream, "0x%x", msbd + 1);
break;
case 'D':
{
const struct mips_cp0sel_name *n;
unsigned int cp0reg, sel;
cp0reg = (l >> OP_SH_RD) & OP_MASK_RD;
sel = (l >> OP_SH_SEL) & OP_MASK_SEL;
/* CP0 register including 'sel' code for mtcN (et al.), 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, cp0reg, sel);
if (n != NULL)
fprintf (stream, "%s", n->name);
else
fprintf (stream, "$%d,%d", cp0reg,
sel);
break;
}
case 'E':
lsb = ((l >> OP_SH_SHAMT) & OP_MASK_SHAMT) + 32;
fprintf (stream, "0x%x", lsb);
break;
case 'F':
msb = ((l >> OP_SH_INSMSB) & OP_MASK_INSMSB) + 32;
fprintf (stream, "0x%x", msb - lsb + 1);
break;
case 'G':
msbd = ((l >> OP_SH_EXTMSBD) & OP_MASK_EXTMSBD) + 32;
fprintf (stream, "0x%x", msbd + 1);
break;
case 't': /* Coprocessor 0 reg name */
fprintf (stream, "%s",
mips_cp0_names[(l >> OP_SH_RT) & OP_MASK_RT]);
break;
case 'T': /* Coprocessor 0 reg name */
{
const struct mips_cp0sel_name *n;
unsigned int cp0reg, sel;
cp0reg = (l >> OP_SH_RT) & OP_MASK_RT;
sel = (l >> OP_SH_SEL) & OP_MASK_SEL;
/* 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, cp0reg, sel);
if (n != NULL)
fprintf (stream, "%s", n->name);
else
fprintf (stream, "$%d,%d", cp0reg,
sel);
break;
}
case 'x': /* bbit bit index */
fprintf (stream, "0x%lx",
(l >> OP_SH_BBITIND) & OP_MASK_BBITIND);
break;
case 'p': /* cins, cins32, exts and exts32 position */
fprintf (stream, "0x%lx",
(l >> OP_SH_CINSPOS) & OP_MASK_CINSPOS);
break;
case 's': /* cins and exts length-minus-one */
fprintf (stream, "0x%lx",
(l >> OP_SH_CINSLM1) & OP_MASK_CINSLM1);
break;
case 'S': /* cins32 and exts32 length-minus-one field */
fprintf (stream, "0x%lx",
(l >> OP_SH_CINSLM1) & OP_MASK_CINSLM1);
break;
case 'Q': /* seqi/snei immediate field */
op = (l >> OP_SH_SEQI) & OP_MASK_SEQI;
/* Sign-extend it. */
op = (op ^ 512) - 512;
fprintf (stream, "%d", op);
break;
default:
/* xgettext:c-format */
fprintf (stream, "# internal error, undefined extension sequence (+%c)", *d);
return;
}
break;
case '2':
fprintf (stream, "0x%lx",
(l >> OP_SH_BP) & OP_MASK_BP);
break;
case '3':
fprintf (stream, "0x%lx",
(l >> OP_SH_SA3) & OP_MASK_SA3);
break;
case '4':
fprintf (stream, "0x%lx",
(l >> OP_SH_SA4) & OP_MASK_SA4);
break;
case '5':
fprintf (stream, "0x%lx",
(l >> OP_SH_IMM8) & OP_MASK_IMM8);
break;
case '6':
fprintf (stream, "0x%lx",
(l >> OP_SH_RS) & OP_MASK_RS);
break;
case '7':
fprintf (stream, "$ac%ld",
(l >> OP_SH_DSPACC) & OP_MASK_DSPACC);
break;
case '8':
fprintf (stream, "0x%lx",
(l >> OP_SH_WRDSP) & OP_MASK_WRDSP);
break;
case '9':
fprintf (stream, "$ac%ld",
(l >> OP_SH_DSPACC_S) & OP_MASK_DSPACC_S);
break;
case '0': /* dsp 6-bit signed immediate in bit 20 */
delta = ((l >> OP_SH_DSPSFT) & OP_MASK_DSPSFT);
if (delta & 0x20) /* test sign bit */
delta |= ~OP_MASK_DSPSFT;
fprintf (stream, "%d", delta);
break;
case ':': /* dsp 7-bit signed immediate in bit 19 */
delta = ((l >> OP_SH_DSPSFT_7) & OP_MASK_DSPSFT_7);
if (delta & 0x40) /* test sign bit */
delta |= ~OP_MASK_DSPSFT_7;
fprintf (stream, "%d", delta);
break;
case '\'':
fprintf (stream, "0x%lx",
(l >> OP_SH_RDDSP) & OP_MASK_RDDSP);
break;
case '@': /* dsp 10-bit signed immediate in bit 16 */
delta = ((l >> OP_SH_IMM10) & OP_MASK_IMM10);
if (delta & 0x200) /* test sign bit */
delta |= ~OP_MASK_IMM10;
fprintf (stream, "%d", delta);
break;
case '!':
fprintf (stream, "%ld",
(l >> OP_SH_MT_U) & OP_MASK_MT_U);
break;
case '$':
fprintf (stream, "%ld",
(l >> OP_SH_MT_H) & OP_MASK_MT_H);
break;
case '*':
fprintf (stream, "$ac%ld",
(l >> OP_SH_MTACC_T) & OP_MASK_MTACC_T);
break;
case '&':
fprintf (stream, "$ac%ld",
(l >> OP_SH_MTACC_D) & OP_MASK_MTACC_D);
break;
case 'g':
/* Coprocessor register for CTTC1, MTTC2, MTHC2, CTTC2. */
fprintf (stream, "$%ld",
(l >> OP_SH_RD) & OP_MASK_RD);
break;
case 's':
case 'b':
case 'r':
case 'v':
fprintf (stream, "%s",
mips_gpr_names[(l >> OP_SH_RS) & OP_MASK_RS]);
break;
case 't':
case 'w':
fprintf (stream, "%s",
mips_gpr_names[(l >> OP_SH_RT) & OP_MASK_RT]);
break;
case 'i':
case 'u':
fprintf (stream, "0x%lx",
(l >> OP_SH_IMMEDIATE) & OP_MASK_IMMEDIATE);
break;
case 'j': /* Same as i, but sign-extended. */
case 'o':
delta = (l >> OP_SH_DELTA) & OP_MASK_DELTA;
if (delta & 0x8000)
delta |= ~0xffff;
fprintf (stream, "%d", delta);
break;
case 'h':
fprintf (stream, "0x%x",
(unsigned int) ((l >> OP_SH_PREFX)
& OP_MASK_PREFX));
break;
case 'k':
fprintf (stream, "0x%x",
(unsigned int) ((l >> OP_SH_CACHE)
& OP_MASK_CACHE));
break;
case 'a':
target = (((pc + 4) & ~(unsigned) 0x0fffffff)
| (((l >> OP_SH_TARGET) & OP_MASK_TARGET) << 2));
/* For gdb disassembler, force odd address on jalx. */
if (strcmp (opp->name, "jalx") == 0)
target |= 1;
print_address (target, stream);
break;
case 'p':
/* Sign extend the displacement. */
delta = (l >> OP_SH_DELTA) & OP_MASK_DELTA;
if (delta & 0x8000)
delta |= ~0xffff;
target = (delta << 2) + pc + INSNLEN;
print_address (target, stream);
break;
case 'd':
fprintf (stream, "%s",
mips_gpr_names[(l >> OP_SH_RD) & OP_MASK_RD]);
break;
case 'U':
{
/* First check for both rd and rt being equal. */
unsigned int reg = (l >> OP_SH_RD) & OP_MASK_RD;
if (reg == ((l >> OP_SH_RT) & OP_MASK_RT))
fprintf (stream, "%s",
mips_gpr_names[reg]);
else {
/* If one is zero use the other. */
if (reg == 0)
fprintf (stream, "%s",
mips_gpr_names[(l >> OP_SH_RT) & OP_MASK_RT]);
else if (((l >> OP_SH_RT) & OP_MASK_RT) == 0)
fprintf (stream, "%s",
mips_gpr_names[reg]);
else /* Bogus, result depends on processor. */
fprintf (stream, "%s or %s",
mips_gpr_names[reg],
mips_gpr_names[(l >> OP_SH_RT) & OP_MASK_RT]);
}
}
break;
case 'z':
fprintf (stream, "%s", mips_gpr_names[0]);
break;
case '<':
case '1':
fprintf (stream, "0x%lx",
(l >> OP_SH_SHAMT) & OP_MASK_SHAMT);
break;
case 'c':
fprintf (stream, "0x%lx",
(l >> OP_SH_CODE) & OP_MASK_CODE);
break;
case 'q':
fprintf (stream, "0x%lx",
(l >> OP_SH_CODE2) & OP_MASK_CODE2);
break;
case 'C':
fprintf (stream, "0x%lx",
(l >> OP_SH_COPZ) & OP_MASK_COPZ);
break;
case 'B':
fprintf (stream, "0x%lx",
(l >> OP_SH_CODE20) & OP_MASK_CODE20);
break;
case 'J':
fprintf (stream, "0x%lx",
(l >> OP_SH_CODE19) & OP_MASK_CODE19);
break;
case 'S':
case 'V':
fprintf (stream, "%s",
mips_fpr_names[(l >> OP_SH_FS) & OP_MASK_FS]);
break;
case 'T':
case 'W':
fprintf (stream, "%s",
mips_fpr_names[(l >> OP_SH_FT) & OP_MASK_FT]);
break;
case 'D':
fprintf (stream, "%s",
mips_fpr_names[(l >> OP_SH_FD) & OP_MASK_FD]);
break;
case 'R':
fprintf (stream, "%s",
mips_fpr_names[(l >> OP_SH_FR) & OP_MASK_FR]);
break;
case 'E':
/* Coprocessor register for lwcN instructions, et al.
*
* Note that there is no load/store cp0 instructions, and
* that FPU (cp1) instructions disassemble this field using
* 'T' format. Therefore, until we gain understanding of
* cp2 register names, we can simply print the register
* numbers. */
fprintf (stream, "$%ld",
(l >> OP_SH_RT) & OP_MASK_RT);
break;
case 'G':
/* Coprocessor register for mtcN instructions, et al. Note
* that FPU (cp1) instructions disassemble this field using
* 'S' format. Therefore, we only need to worry about cp0,
* cp2, and cp3. */
op = (l >> OP_SH_OP) & OP_MASK_OP;
if (op == OP_OP_COP0)
fprintf (stream, "%s",
mips_cp0_names[(l >> OP_SH_RD) & OP_MASK_RD]);
else
fprintf (stream, "$%ld",
(l >> OP_SH_RD) & OP_MASK_RD);
break;
case 'K':
fprintf (stream, "%s",
mips_hwr_names[(l >> OP_SH_RD) & OP_MASK_RD]);
break;
case 'N':
fprintf (stream,
((opp->pinfo & (FP_D | FP_S)) != 0
? "$fcc%ld" : "$cc%ld"), (l >> OP_SH_BCC) & OP_MASK_BCC);
break;
case 'M':
fprintf (stream, "$fcc%ld",
(l >> OP_SH_CCC) & OP_MASK_CCC);
break;
case 'P':
fprintf (stream, "%ld",
(l >> OP_SH_PERFREG) & OP_MASK_PERFREG);
break;
case 'e':
fprintf (stream, "%ld",
(l >> OP_SH_VECBYTE) & OP_MASK_VECBYTE);
break;
case '%':
fprintf (stream, "%ld",
(l >> OP_SH_VECALIGN) & OP_MASK_VECALIGN);
break;
case 'H':
fprintf (stream, "%ld",
(l >> OP_SH_SEL) & OP_MASK_SEL);
break;
case 'O':
fprintf (stream, "%ld",
(l >> OP_SH_ALN) & OP_MASK_ALN);
break;
case 'Q':
{
unsigned int vsel = (l >> OP_SH_VSEL) & OP_MASK_VSEL;
if ((vsel & 0x10) == 0) {
int fmt;
vsel &= 0x0f;
for (fmt = 0; fmt < 3; fmt++, vsel >>= 1)
if ((vsel & 1) == 0)
break;
fprintf (stream, "$v%ld[%d]",
(l >> OP_SH_FT) & OP_MASK_FT, vsel >> 1);
} else if ((vsel & 0x08) == 0) {
fprintf (stream, "$v%ld",
(l >> OP_SH_FT) & OP_MASK_FT);
} else {
fprintf (stream, "0x%lx",
(l >> OP_SH_FT) & OP_MASK_FT);
}
}
break;
case 'X':
fprintf (stream, "$v%ld",
(l >> OP_SH_FD) & OP_MASK_FD);
break;
case 'Y':
fprintf (stream, "$v%ld",
(l >> OP_SH_FS) & OP_MASK_FS);
break;
case 'Z':
fprintf (stream, "$v%ld",
(l >> OP_SH_FT) & OP_MASK_FT);
break;
default:
/* xgettext:c-format */
fprintf (stream, "# internal error, undefined modifier (%c)\n", *d);
return;
}
}
}
/*
* Print the mips instruction at address MEMADDR.
*/
static void
print_insn_mips (unsigned memaddr,
unsigned long int word, FILE *stream)
{
const struct mips_opcode *op;
static int init = 0;
static const struct mips_opcode *mips_hash[OP_MASK_OP + 1];
/* Build a hash table to shorten the search time. */
if (! init) {
unsigned int i;
for (i = 0; i <= OP_MASK_OP; i++) {
for (op = mips_opcodes; op < &mips_opcodes[mips_num_opcodes]; op++) {
if (op->pinfo == INSN_MACRO
|| (no_aliases && (op->pinfo2 & INSN2_ALIAS)))
continue;
if (i == ((op->match >> OP_SH_OP) & OP_MASK_OP)) {
mips_hash[i] = op;
break;
}
}
}
init = 1;
}
target = 0;
op = mips_hash[(word >> OP_SH_OP) & OP_MASK_OP];
if (op != NULL) {
for (; op < &mips_opcodes[mips_num_opcodes]; op++) {
if (op->pinfo != INSN_MACRO
&& !(no_aliases && (op->pinfo2 & INSN2_ALIAS))
&& (word & op->mask) == op->match) {
const char *d;
fprintf (stream, "%s", op->name);
d = op->args;
if (d != NULL && *d != '\0') {
fprintf (stream, "\t");
print_insn_args (d, word, memaddr, stream, op);
}
return;
}
}
}
/* Handle undefined instructions. */
fprintf (stream, "0x%lx", word);
}
/*
* Disassemble an operand for a mips16 instruction.
*/
static void
print_mips16_insn_arg (char type,
const struct mips_opcode *op,
int l,
int use_extend,
int extend, unsigned memaddr, FILE *stream)
{
switch (type) {
case ',':
case '(':
case ')':
fprintf (stream, "%c", type);
break;
case 'y':
case 'w':
fprintf (stream, "%s",
mips16_reg_names (((l >> MIPS16OP_SH_RY)
& MIPS16OP_MASK_RY)));
break;
case 'x':
case 'v':
fprintf (stream, "%s",
mips16_reg_names (((l >> MIPS16OP_SH_RX)
& MIPS16OP_MASK_RX)));
break;
case 'z':
fprintf (stream, "%s",
mips16_reg_names (((l >> MIPS16OP_SH_RZ)
& MIPS16OP_MASK_RZ)));
break;
case 'Z':
fprintf (stream, "%s",
mips16_reg_names (((l >> MIPS16OP_SH_MOVE32Z)
& MIPS16OP_MASK_MOVE32Z)));
break;
case '0':
fprintf (stream, "%s", mips_gpr_names[0]);
break;
case 'S':
fprintf (stream, "%s", mips_gpr_names[29]);
break;
case 'P':
fprintf (stream, "$pc");
break;
case 'R':
fprintf (stream, "%s", mips_gpr_names[31]);
break;
case 'X':
fprintf (stream, "%s",
mips_gpr_names[((l >> MIPS16OP_SH_REGR32)
& MIPS16OP_MASK_REGR32)]);
break;
case 'Y':
fprintf (stream, "%s",
mips_gpr_names[MIPS16OP_EXTRACT_REG32R (l)]);
break;
case '<':
case '>':
case '[':
case ']':
case '4':
case '5':
case 'H':
case 'W':
case 'D':
case 'j':
case '6':
case '8':
case 'V':
case 'C':
case 'U':
case 'k':
case 'K':
case 'p':
case 'q':
case 'A':
case 'B':
case 'E':
{
int immed, nbits, shift, signedp, extbits, pcrel, extu, branch;
shift = 0;
signedp = 0;
extbits = 16;
pcrel = 0;
extu = 0;
branch = 0;
switch (type) {
case '<':
nbits = 3;
immed = (l >> MIPS16OP_SH_RZ) & MIPS16OP_MASK_RZ;
extbits = 5;
extu = 1;
break;
case '>':
nbits = 3;
immed = (l >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX;
extbits = 5;
extu = 1;
break;
case '[':
nbits = 3;
immed = (l >> MIPS16OP_SH_RZ) & MIPS16OP_MASK_RZ;
extbits = 6;
extu = 1;
break;
case ']':
nbits = 3;
immed = (l >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX;
extbits = 6;
extu = 1;
break;
case '4':
nbits = 4;
immed = (l >> MIPS16OP_SH_IMM4) & MIPS16OP_MASK_IMM4;
signedp = 1;
extbits = 15;
break;
case '5':
nbits = 5;
immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
break;
case 'H':
nbits = 5;
shift = 1;
immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
break;
case 'W':
nbits = 5;
shift = 2;
immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
break;
case 'D':
nbits = 5;
shift = 3;
immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
break;
case 'j':
nbits = 5;
immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
signedp = 1;
break;
case '6':
nbits = 6;
immed = (l >> MIPS16OP_SH_IMM6) & MIPS16OP_MASK_IMM6;
break;
case '8':
nbits = 8;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
break;
case 'V':
nbits = 8;
shift = 2;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
/* FIXME: This might be lw, or it might be addiu to $sp or
* $pc. We assume it's load. */
break;
case 'C':
nbits = 8;
shift = 3;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
break;
case 'U':
nbits = 8;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
extu = 1;
break;
case 'k':
nbits = 8;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
signedp = 1;
break;
case 'K':
nbits = 8;
shift = 3;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
signedp = 1;
break;
case 'p':
nbits = 8;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
signedp = 1;
pcrel = 1;
branch = 1;
break;
case 'q':
nbits = 11;
immed = (l >> MIPS16OP_SH_IMM11) & MIPS16OP_MASK_IMM11;
signedp = 1;
pcrel = 1;
branch = 1;
break;
case 'A':
nbits = 8;
shift = 2;
immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
pcrel = 1;
/* FIXME: This can be lw or la. We assume it is lw. */
break;
case 'B':
nbits = 5;
shift = 3;
immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
pcrel = 1;
break;
case 'E':
nbits = 5;
shift = 2;
immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
pcrel = 1;
break;
default:
fprintf (stream, "# internal disassembler error, unrecognised mips16 type (%c)", type);
abort ();
}
if (!use_extend) {
if (signedp && immed >= (1 << (nbits - 1)))
immed -= 1 << nbits;
immed <<= shift;
if ((type == '<' || type == '>' || type == '[' || type == ']')
&& immed == 0)
immed = 8;
} else {
if (extbits == 16)
immed |= ((extend & 0x1f) << 11) | (extend & 0x7e0);
else if (extbits == 15)
immed |= ((extend & 0xf) << 11) | (extend & 0x7f0);
else
immed = ((extend >> 6) & 0x1f) | (extend & 0x20);
immed &= (1 << extbits) - 1;
if (!extu && immed >= (1 << (extbits - 1)))
immed -= 1 << extbits;
}
if (!pcrel)
fprintf (stream, "%d", immed);
else {
unsigned baseaddr;
if (branch) {
immed *= 2;
baseaddr = memaddr + 2;
} else if (use_extend) {
baseaddr = memaddr - 2;
} else {
baseaddr = memaddr;
}
target = (baseaddr & ~((1 << shift) - 1)) + immed;
if (pcrel && branch)
/* For gdb disassembler, maintain odd address. */
target |= 1;
print_address (target, stream);
}
}
break;
case 'a':
{
int jalx = l & 0x400;
if (!use_extend)
extend = 0;
l = ((l & 0x1f) << 23) | ((l & 0x3e0) << 13) | (extend << 2);
if (! jalx)
/* For gdb disassembler, maintain odd address. */
l |= 1;
}
target = ((memaddr + 4) & ~(unsigned) 0x0fffffff) | l;
print_address (target, stream);
break;
case 'l':
case 'L':
{
int need_comma, amask, smask;
need_comma = 0;
l = (l >> MIPS16OP_SH_IMM6) & MIPS16OP_MASK_IMM6;
amask = (l >> 3) & 7;
if (amask > 0 && amask < 5) {
fprintf (stream, "%s", mips_gpr_names[4]);
if (amask > 1)
fprintf (stream, "-%s",
mips_gpr_names[amask + 3]);
need_comma = 1;
}
smask = (l >> 1) & 3;
if (smask == 3) {
fprintf (stream, "%s??",
need_comma ? "," : "");
need_comma = 1;
} else if (smask > 0) {
fprintf (stream, "%s%s",
need_comma ? "," : "", mips_gpr_names[16]);
if (smask > 1)
fprintf (stream, "-%s",
mips_gpr_names[smask + 15]);
need_comma = 1;
}
if (l & 1) {
fprintf (stream, "%s%s",
need_comma ? "," : "", mips_gpr_names[31]);
need_comma = 1;
}
if (amask == 5 || amask == 6) {
fprintf (stream, "%s$f0",
need_comma ? "," : "");
if (amask == 6)
fprintf (stream, "-$f1");
}
}
break;
case 'm':
case 'M':
/* MIPS16e save/restore. */
{
int need_comma = 0;
int amask, args, statics;
int nsreg, smask;
int framesz;
int i, j;
l = l & 0x7f;
if (use_extend)
l |= extend << 16;
amask = (l >> 16) & 0xf;
if (amask == MIPS16_ALL_ARGS) {
args = 4;
statics = 0;
} else if (amask == MIPS16_ALL_STATICS) {
args = 0;
statics = 4;
} else {
args = amask >> 2;
statics = amask & 3;
}
if (args > 0) {
fprintf (stream, "%s", mips_gpr_names[4]);
if (args > 1)
fprintf (stream, "-%s",
mips_gpr_names[4 + args - 1]);
need_comma = 1;
}
framesz = (((l >> 16) & 0xf0) | (l & 0x0f)) * 8;
if (framesz == 0 && !use_extend)
framesz = 128;
fprintf (stream, "%s%d",
need_comma ? "," : "", framesz);
if (l & 0x40) /* $ra */
fprintf (stream, ",%s",
mips_gpr_names[31]);
nsreg = (l >> 24) & 0x7;
smask = 0;
if (l & 0x20) /* $s0 */
smask |= 1 << 0;
if (l & 0x10) /* $s1 */
smask |= 1 << 1;
if (nsreg > 0) /* $s2-$s8 */
smask |= ((1 << nsreg) - 1) << 2;
/* Find first set static reg bit. */
for (i = 0; i < 9; i++) {
if (smask & (1 << i)) {
fprintf (stream, ",%s",
mips_gpr_names[i == 8 ? 30 : (16 + i)]);
/* Skip over string of set bits. */
for (j = i; smask & (2 << j); j++)
continue;
if (j > i)
fprintf (stream, "-%s",
mips_gpr_names[j == 8 ? 30 : (16 + j)]);
i = j + 1;
}
}
/* Statics $ax - $a3. */
if (statics == 1)
fprintf (stream, ",%s",
mips_gpr_names[7]);
else if (statics > 0)
fprintf (stream, ",%s-%s",
mips_gpr_names[7 - statics + 1], mips_gpr_names[7]);
}
break;
default:
/* xgettext:c-format */
fprintf (stream, "# internal disassembler error, unrecognised modifier (%c)\n", type);
abort ();
}
}
/*
* Disassemble mips16 instructions.
*/
static void print_insn_mips16 (unsigned memaddr,
unsigned int opcode, int nbytes, FILE *stream)
{
int insn, use_extend, extend;
const struct mips_opcode *op, *opend;
insn = opcode;
if (nbytes == 2) {
use_extend = 0;
extend = 0;
insn = opcode;
} else {
if ((opcode & 0xf8000000) == 0xf0000000) {
/* Handle the extend opcode specially. */
use_extend = 1;
insn = opcode & 0xffff;
extend = (opcode >> 16) & 0x7ff;
} else {
/* jal, jalx */
use_extend = 0;
insn = opcode >> 16;
extend = opcode & 0xffff;
}
}
/* FIXME: Should probably use a hash table on the major opcode here. */
opend = mips16_opcodes + mips16_num_opcodes;
for (op = mips16_opcodes; op < opend; op++) {
if (op->pinfo != INSN_MACRO
&& (insn & op->mask) == op->match) {
const char *s;
fprintf (stream, "%s", op->name);
if (op->args[0] != '\0')
fprintf (stream, "\t");
for (s = op->args; *s != '\0'; s++) {
if (*s == ','
&& s[1] == 'w'
&& (((insn >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX)
== ((insn >> MIPS16OP_SH_RY) & MIPS16OP_MASK_RY))) {
/* Skip the register and the comma. */
++s;
continue;
}
if (*s == ','
&& s[1] == 'v'
&& (((insn >> MIPS16OP_SH_RZ) & MIPS16OP_MASK_RZ)
== ((insn >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX))) {
/* Skip the register and the comma. */
++s;
continue;
}
print_mips16_insn_arg (*s, op, insn,
use_extend, extend, memaddr, stream);
}
return;
}
}
fprintf (stream, "0x");
if (use_extend)
fprintf (stream, "%04x", extend | 0xf000);
fprintf (stream, "%04x", insn);
}
/*
* Disassemble and print the instruction mnemonics.
* Opcode size can be 4 or 2 bytes.
* Parameter `isa' defines an instruction set architecture:
* 0 - mips32 encoding
* 1 - mips16e encoding
* 2 - micromips encoding (someday)
*/
void print_mips (unsigned memaddr, unsigned int opcode, int nbytes,
int isa, FILE *stream)
{
switch (isa) {
default:
case 0:
print_insn_mips (memaddr, opcode, stream);
break;
case 1:
print_insn_mips16 (memaddr, opcode, nbytes, stream);
break;
case 2:
/* TODO: micromips encoding */
break;
}
}