540 lines
14 KiB
C++
540 lines
14 KiB
C++
/*
|
|
* Interactive disassembler (IDA).
|
|
* Copyright (c) 1990-98 by Ilfak Guilfanov.
|
|
* ALL RIGHTS RESERVED.
|
|
* E-mail: ig@estar.msk.su, ig@datarescue.com
|
|
* FIDO: 2:5020/209
|
|
*
|
|
*/
|
|
|
|
#include "arc.hpp"
|
|
|
|
// generic condition codes
|
|
static const char *const ccode[] =
|
|
{
|
|
"", ".z", ".nz", ".p",
|
|
".n", ".c", ".nc", ".v",
|
|
".nv", ".gt", ".ge", ".lt",
|
|
".le", ".hi", ".ls", ".pnz",
|
|
".ss", ".sc", ".c0x12", ".c0x13",
|
|
".c0x14", ".c0x15", ".c0x16", ".c0x17",
|
|
".c0x18", ".c0x19", ".c0x1A", ".c0x1B",
|
|
".c0x1C", ".c0x1D", ".c0x1E", ".c0x1F",
|
|
};
|
|
|
|
// generic condition codes for ARCv2
|
|
static const char *const ccode_v2[] =
|
|
{
|
|
"", ".eq", ".ne", ".p",
|
|
".n", ".c", ".nc", ".v",
|
|
".nv", ".gt", ".ge", ".lt",
|
|
".le", ".hi", ".ls", ".pnz",
|
|
".c0x10", ".c0x11", ".c0x12", ".c0x13",
|
|
".c0x14", ".c0x15", ".c0x16", ".c0x17",
|
|
".c0x18", ".c0x19", ".c0x1A", ".c0x1B",
|
|
".c0x1C", ".c0x1D", ".c0x1E", ".c0x1F",
|
|
};
|
|
|
|
|
|
// condition codes for branches
|
|
static const char *const ccode_b[] =
|
|
{
|
|
"", "eq", "ne", "pl",
|
|
"mi", "lo", "hs", "vs",
|
|
"vc", "gt", "ge", "lt",
|
|
"le", "hi", "ls", "pnz",
|
|
"ss", "sc", "c0x12", "c0x13",
|
|
"c0x14", "c0x15", "c0x16", "c0x17",
|
|
"c0x18", "c0x19", "c0x1A", "c0x1B",
|
|
"c0x1C", "c0x1D", "c0x1E", "c0x1F",
|
|
};
|
|
|
|
// condition codes for ARCv2 branches
|
|
static const char *const ccode_b_v2[] =
|
|
{
|
|
"", "eq", "ne", "p",
|
|
"n", "lo", "hs", "v",
|
|
"nv", "gt", "ge", "lt",
|
|
"le", "hi", "ls", "pnz",
|
|
"c0x10", "c0x11", "c0x12", "c0x13",
|
|
"c0x14", "c0x15", "c0x16", "c0x17",
|
|
"c0x18", "c0x19", "c0x1A", "c0x1B",
|
|
"c0x1C", "c0x1D", "c0x1E", "c0x1F",
|
|
};
|
|
|
|
|
|
/* jump delay slot codes */
|
|
static const char ncode[][4] = { "", ".d", ".jd", ".d?" };
|
|
|
|
//----------------------------------------------------------------------
|
|
class out_arc_t : public outctx_t
|
|
{
|
|
out_arc_t(void) = delete; // not used
|
|
void set_gr_cmt(const char *cmt) { user_data = (void *)cmt; }
|
|
const char *get_gr_cmt(void) const { return (const char *)user_data; }
|
|
public:
|
|
void outreg(int rn);
|
|
|
|
bool out_operand(const op_t &x);
|
|
void out_insn(void);
|
|
void out_proc_mnem(void);
|
|
void out_specreg(const ioports_t &table, const op_t &x);
|
|
void out_aux(const op_t &x)
|
|
{
|
|
arc_t &pm = *static_cast<arc_t *>(procmod);
|
|
out_specreg(pm.auxregs, x);
|
|
}
|
|
};
|
|
CASSERT(sizeof(out_arc_t) == sizeof(outctx_t));
|
|
|
|
DECLARE_OUT_FUNCS(out_arc_t)
|
|
|
|
//----------------------------------------------------------------------
|
|
void out_arc_t::outreg(int rn)
|
|
{
|
|
const char *regname = (rn < ph.regs_num) ? ph.reg_names[rn] : "<bad register>";
|
|
out_register(regname);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
void out_arc_t::out_specreg(const ioports_t &table, const op_t &x)
|
|
{
|
|
const ioport_t *reg = find_ioport(table, x.value);
|
|
if ( reg == NULL )
|
|
{
|
|
out_symbol('[');
|
|
out_value(x, OOFS_IFSIGN | OOFW_32);
|
|
out_symbol(']');
|
|
}
|
|
else
|
|
{
|
|
out_register(reg->name.c_str());
|
|
if ( !reg->cmt.empty() && !has_cmt(F) )
|
|
set_gr_cmt(reg->cmt.c_str());
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
/* outputs an operand 'x' */
|
|
bool out_arc_t::out_operand(const op_t & x)
|
|
{
|
|
arc_t &pm = *static_cast<arc_t *>(procmod);
|
|
ea_t v;
|
|
switch ( x.type )
|
|
{
|
|
case o_reg:
|
|
outreg(x.reg);
|
|
break;
|
|
|
|
case o_phrase:
|
|
{
|
|
int hidden_base = is_hidden_base_reg(x.reg);
|
|
if ( hidden_base != -1 )
|
|
out_symbol('[');
|
|
if ( hidden_base == 0 )
|
|
{
|
|
outreg(x.reg);
|
|
out_symbol(',');
|
|
}
|
|
outreg(x.secreg);
|
|
if ( hidden_base != -1 )
|
|
out_symbol(']');
|
|
}
|
|
break;
|
|
|
|
case o_imm:
|
|
// check for: LR <dest>, [aux]
|
|
// SR <src>, [aux]
|
|
if ( x.n == 1
|
|
&& !is_defarg(F, x.n) // don't use aux register if op type is set
|
|
&& (insn.itype == ARC_lr || insn.itype == ARC_sr) )
|
|
out_aux(x);
|
|
else
|
|
out_value(x, OOFS_IFSIGN | OOFW_IMM);
|
|
break;
|
|
|
|
case o_mem:
|
|
{
|
|
ea_t ea = to_ea(insn.cs, x.addr);
|
|
if ( (insn.auxpref & aux_pcload) != 0 )
|
|
{
|
|
// A little hack to make the output
|
|
// more readable...
|
|
op_t y;
|
|
if ( pm.copy_insn_optype(insn, x, ea, &y.value) )
|
|
{
|
|
y.dtype = x.dtype;
|
|
y.flags = OF_SHOW;
|
|
out_symbol('=');
|
|
set_dlbind_opnd();
|
|
ea_t insn_ea_sav = insn_ea;
|
|
flags_t savedF = F;
|
|
insn_ea = ea; // change context
|
|
F = get_flags(ea);
|
|
out_value(y, OOFS_IFSIGN|OOFW_IMM);
|
|
insn_ea = insn_ea_sav; // restore context
|
|
F = savedF;
|
|
break;
|
|
}
|
|
}
|
|
out_symbol('[');
|
|
if ( insn.itype != ARC_lr && insn.itype != ARC_sr )
|
|
{
|
|
if ( !out_name_expr(x, ea, x.addr) )
|
|
{
|
|
out_tagon(COLOR_ERROR);
|
|
out_btoa(uint32(x.addr), 16);
|
|
out_tagoff(COLOR_ERROR);
|
|
remember_problem(PR_NONAME, insn.ea);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
out_btoa(uint32(x.addr), 16);
|
|
}
|
|
if ( x.immdisp != 0 )
|
|
{
|
|
out_symbol('-');
|
|
out_btoa(uint32(x.immdisp * get_scale_factor(insn)), 16);
|
|
out_symbol(',');
|
|
out_btoa(uint32(x.immdisp), 16);
|
|
}
|
|
out_symbol(']');
|
|
}
|
|
break;
|
|
|
|
case o_near:
|
|
v = to_ea(insn.cs, x.addr);
|
|
if ( !out_name_expr(x, v, x.addr) )
|
|
{
|
|
out_value(x, OOF_ADDR|OOF_NUMBER|OOFS_NOSIGN|OOFW_32);
|
|
remember_problem(PR_NONAME, insn.ea);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case o_displ:
|
|
{
|
|
// membase=0: [reg, #addr]
|
|
// membase=1: [#addr, reg]
|
|
int hidden_base = is_hidden_base_reg(x.reg);
|
|
if ( hidden_base != -1 )
|
|
out_symbol('[');
|
|
if ( x.membase == 0 && hidden_base == 0 )
|
|
outreg(x.reg);
|
|
if ( x.addr != 0
|
|
|| hidden_base != 0
|
|
|| is_off(F, x.n)
|
|
|| is_stkvar(F, x.n)
|
|
|| is_enum(F, x.n)
|
|
|| is_stroff(F, x.n) )
|
|
{
|
|
if ( x.membase == 0 && hidden_base == 0 )
|
|
out_symbol(',');
|
|
out_value(x, OOF_ADDR|OOFS_IFSIGN|OOF_SIGNED|OOFW_32);
|
|
if ( x.membase != 0 )
|
|
out_symbol(',');
|
|
}
|
|
if ( x.membase != 0 )
|
|
outreg(x.reg);
|
|
if ( hidden_base != -1 )
|
|
out_symbol(']');
|
|
}
|
|
break;
|
|
|
|
case o_reglist:
|
|
{
|
|
out_symbol('{');
|
|
bool need_comma = false;
|
|
int regs = x.reglist & REGLIST_REGS;
|
|
if ( regs > REGLISTR_MAX )
|
|
{
|
|
out_tagon(COLOR_ERROR);
|
|
out_btoa(regs, 16);
|
|
out_tagoff(COLOR_ERROR);
|
|
need_comma = true;
|
|
}
|
|
else if ( regs > 0 )
|
|
{
|
|
outreg(R13);
|
|
if ( regs > 1 )
|
|
{
|
|
out_symbol('-');
|
|
outreg(R13 + regs - 1);
|
|
}
|
|
need_comma = true;
|
|
}
|
|
if ( (x.reglist & REGLIST_FP) != 0 )
|
|
{
|
|
if ( need_comma )
|
|
out_symbol(',');
|
|
outreg(FP);
|
|
need_comma = true;
|
|
}
|
|
if ( (x.reglist & REGLIST_BLINK) != 0 )
|
|
{
|
|
if ( need_comma )
|
|
out_symbol(',');
|
|
outreg(BLINK);
|
|
need_comma = true;
|
|
}
|
|
if ( (x.reglist & REGLIST_PCL) != 0 )
|
|
{
|
|
if ( need_comma )
|
|
out_symbol(',');
|
|
outreg(PCL);
|
|
need_comma = true;
|
|
}
|
|
out_symbol('}');
|
|
}
|
|
break;
|
|
|
|
default:
|
|
out_symbol('?');
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
inline bool is_branch(const insn_t &insn)
|
|
{
|
|
switch ( insn.itype )
|
|
{
|
|
case ARC_b:
|
|
case ARC_lp:
|
|
case ARC_bl:
|
|
case ARC_j:
|
|
case ARC_jl:
|
|
case ARC_br:
|
|
case ARC_bbit0:
|
|
case ARC_bbit1:
|
|
return true;
|
|
}
|
|
#ifndef NDEBUG
|
|
// delay slot bits must be only set for branches
|
|
QASSERT(10184, !has_dslot(insn));
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
void out_arc_t::out_proc_mnem(void)
|
|
{
|
|
arc_t &pm = *static_cast<arc_t *>(procmod);
|
|
char postfix[MAXSTR];
|
|
postfix[0] = '\0';
|
|
if ( insn.itype == ARC_null )
|
|
{
|
|
uint32 code = get_dword(insn.ea);
|
|
|
|
int i = (code>>27)&31;
|
|
if ( i == 3 )
|
|
{
|
|
int c = (code>>9)&63;
|
|
qsnprintf(postfix, sizeof(postfix), "ext%02X_%02X", i, c);
|
|
}
|
|
else
|
|
{
|
|
qsnprintf(postfix, sizeof(postfix), "ext%02X", i);
|
|
}
|
|
}
|
|
|
|
/* if we have a load or store instruction, flags are used a bit different */
|
|
if ( insn.itype <= ARC_store_instructions )
|
|
{
|
|
switch ( insn.auxpref & aux_zmask )
|
|
{
|
|
case 0:
|
|
break;
|
|
case aux_b:
|
|
qstrncat(postfix, "b", sizeof(postfix));
|
|
break;
|
|
case aux_w:
|
|
qstrncat(postfix, pm.is_arcv2() ? "h" : "w", sizeof(postfix));
|
|
break;
|
|
default:
|
|
qstrncat(postfix, "?", sizeof(postfix));
|
|
break;
|
|
}
|
|
if ( (insn.auxpref & aux_s) != 0 )
|
|
qstrncat(postfix, "_s", sizeof(postfix));
|
|
if ( insn.auxpref & aux_x )
|
|
qstrncat(postfix, ".x", sizeof(postfix));
|
|
switch ( insn.auxpref & aux_amask )
|
|
{
|
|
case 0:
|
|
break;
|
|
case aux_a:
|
|
qstrncat(postfix, ".a", sizeof(postfix));
|
|
break;
|
|
case aux_as:
|
|
qstrncat(postfix, ".as", sizeof(postfix));
|
|
break;
|
|
case aux_ab:
|
|
qstrncat(postfix, ".ab", sizeof(postfix));
|
|
break;
|
|
default:
|
|
qstrncat(postfix, "?", sizeof(postfix));
|
|
break;
|
|
}
|
|
if ( insn.auxpref & aux_di )
|
|
qstrncat(postfix, ".di", sizeof(postfix));
|
|
}
|
|
else
|
|
{
|
|
uint8 cond = insn.auxpref & aux_cmask;
|
|
if ( cond != cAL && is_branch(insn) )
|
|
{
|
|
if ( pm.is_arcv2() )
|
|
qstrncat(postfix, ccode_b_v2[cond], sizeof(postfix));
|
|
else
|
|
qstrncat(postfix, ccode_b[cond], sizeof(postfix));
|
|
}
|
|
|
|
if ( (insn.auxpref & aux_s) != 0 )
|
|
qstrncat(postfix, "_s", sizeof(postfix));
|
|
|
|
if ( cond != cAL && !is_branch(insn) )
|
|
{
|
|
if ( pm.is_arcv2() )
|
|
qstrncat(postfix, ccode_v2[cond], sizeof(postfix));
|
|
else
|
|
qstrncat(postfix, ccode[cond], sizeof(postfix));
|
|
}
|
|
}
|
|
if ( is_branch(insn) ) // delay slot code
|
|
qstrncat(postfix, ncode[(insn.auxpref >> 5) & 3], sizeof(postfix));
|
|
else if ( (insn.auxpref & aux_f) != 0 )
|
|
{
|
|
// for these load/store like instructions, the f bit is used for the .di
|
|
if ( insn.itype == ARC_ex
|
|
|| insn.itype == ARC_llock
|
|
|| insn.itype == ARC_scond )
|
|
{
|
|
qstrncat(postfix, ".di", sizeof(postfix));
|
|
}
|
|
else if ( insn.itype != ARC_flag // flag implicitly sets this bit
|
|
&& insn.itype != ARC_rcmp ) // rcmp implicitly sets this bit
|
|
{
|
|
qstrncat(postfix, ".f", sizeof(postfix));
|
|
}
|
|
}
|
|
|
|
if ( pm.is_arcv2() && ( insn.auxpref & aux_bhint ) != 0 )
|
|
{
|
|
// print static prediction hint
|
|
/*
|
|
from "Assembler Syntax for Static Branch Predictions"
|
|
The default static prediction, in the absence of any <.T> syntax, is always BTFN (Backwards Taken, Forwards Not Taken).
|
|
Therefore, a BRcc instruction always has the Y bit set to 0 by default, whereas a BBITn instruction always has the Y bit set to 1
|
|
by default.
|
|
*/
|
|
bool backwards = insn.Op3.addr <= insn.ea;
|
|
const char *suf = NULL;
|
|
if ( insn.itype == ARC_br )
|
|
suf = backwards ? ".t" : ".nt";
|
|
else
|
|
suf = backwards ? ".nt" : ".t";
|
|
qstrncat(postfix, suf, sizeof(postfix));
|
|
}
|
|
|
|
out_mnem(8, postfix); // output instruction mnemonics
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
void out_arc_t::out_insn(void)
|
|
{
|
|
arc_t &pm = *static_cast<arc_t *>(procmod);
|
|
out_mnemonic();
|
|
if ( insn.Op1.type != o_void )
|
|
out_one_operand(0); // output the first operand
|
|
|
|
for ( int i = 1; i < PROC_MAXOP; ++i )
|
|
{
|
|
if ( insn.ops[i].type != o_void )
|
|
{
|
|
if ( !(insn.ops[i].type == o_reg && insn.ops[i].regpair) )
|
|
{
|
|
out_symbol(',');
|
|
out_char(' ');
|
|
}
|
|
out_one_operand(i); // output the current operand
|
|
}
|
|
}
|
|
|
|
// output a character representation of the immediate values
|
|
// embedded in the instruction as comments
|
|
out_immchar_cmts();
|
|
|
|
// add comments for indirect calls or calculated data xrefs
|
|
nodeidx_t callee = pm.get_callee(insn.ea);
|
|
if ( callee == BADADDR )
|
|
callee = pm.get_dxref(insn.ea);
|
|
if ( callee != BADADDR )
|
|
set_comment_addr(callee & ~1);
|
|
const char *gr_cmt = get_gr_cmt();
|
|
if ( gr_cmt != NULL )
|
|
{
|
|
out_char(' ');
|
|
out_line(ash.cmnt, COLOR_AUTOCMT);
|
|
out_char(' ');
|
|
|
|
out_line(gr_cmt, COLOR_AUTOCMT);
|
|
if ( ash.cmnt2 != NULL )
|
|
{
|
|
out_char(' ');
|
|
out_line(ash.cmnt2, COLOR_AUTOCMT);
|
|
}
|
|
}
|
|
flush_outbuf();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// generate start of the disassembly
|
|
|
|
void idaapi arc_header(outctx_t &ctx)
|
|
{
|
|
ctx.gen_header(GH_PRINT_ALL_BUT_BYTESEX);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// generate start of a segment
|
|
//lint -esym(1764, ctx) could be made const
|
|
//lint -esym(818, Sarea) could be made const
|
|
void arc_t::arc_segstart(outctx_t &ctx, segment_t *Sarea) const
|
|
{
|
|
qstring name;
|
|
get_visible_segm_name(&name, Sarea);
|
|
ctx.gen_printf(0, COLSTR(".section %s", SCOLOR_ASMDIR), name.c_str());
|
|
if ( (inf_get_outflags() & OFLG_GEN_ORG) != 0 )
|
|
{
|
|
adiff_t org = ctx.insn_ea - get_segm_base(Sarea);
|
|
|
|
if ( org != 0 )
|
|
{
|
|
char buf[MAX_NUMBUF];
|
|
|
|
btoa(buf, sizeof(buf), org);
|
|
ctx.gen_printf(0, COLSTR("%s %s", SCOLOR_ASMDIR), ash.origin, buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// generate end of the disassembly
|
|
void idaapi arc_footer(outctx_t &ctx)
|
|
{
|
|
ctx.gen_empty_line();
|
|
|
|
ctx.out_line(".end", COLOR_ASMDIR);
|
|
|
|
qstring name;
|
|
if ( get_colored_name(&name, inf_get_start_ea()) > 0 )
|
|
{
|
|
ctx.out_line(" #");
|
|
ctx.out_line(name.begin());
|
|
}
|
|
ctx.flush_outbuf(DEFAULT_INDENT);
|
|
}
|